Fold a Gliese star into its Gaia source in Gaia's photometry, wherever HYG already put it, and place two naked-eye stars by SIMBAD's name for them
The Gliese fold (030447b) kept HYG's magnitude, band and colour over the Gaia entry's, putting CNS3's V at Gaia's distance. Of the 49 folded, 15 lost a measured BP-RP and 6 their temperature and radius (Gl 700.1C, Gl 632.2B, GJ 9800B, Gl 734B, Gl 323B, GJ 3605), and GJ 4285, V 11.45 where SIMBAD has G 13.05, was drawn five times too luminous. A folded star now keeps the photometry of the entry already there; HYG's name, id and type still come with the fold. Published catalogue, before -> after: GJ 4285 V 11.45, 3 850 K, 0.491 R☉, 4.76e-2 L☉ -> G 13.05, BP-RP 2.74, 3 291 K, 0.297 R☉, 9.29e-3 L☉; GJ 3207 0.574 -> 0.278 R☉; Gl 700.1C no temperature, 4.54 L☉ -> 5 155 K, 1.214 R☉, 0.937 L☉; Gl 632.2B -> 9 044 K, 0.018 R☉; Gl 734B -> 3 360 K, 0.381 R☉. The fold also looked its target up by name, so it saw only Gaia entries still bare. Where a Hipparcos row of HYG's had already taken the source, the Gliese-only row of the same star stayed beside it and the check counted none: Gl 251 at 5.76 pc beside HD 265866 (the host of GJ 251 b and c) at 5.58, Gl 422 beside HD 304043. Gaia entries now carry their DR3 designation through the merge (ETL only; the assets do not store it) and the fold looks it up; into an entry a HYG star already describes it folds only where the two V agree within 0.5, so a companion SIMBAD gives its primary's source stays. 14 more rows fold, 63 in all, each within 0.47 of the star it joins: Gl 251, Gl 422, Gl 162, Gl 794, Gl 225.2C, Gl 905.2B, GJ 3232, GJ 4046, GJ 9490C, GJ 9608, 69 Tau Oph (Gl 700.1A) and the second rows of HD 65277, HD 120237 and HD 336196. Stars within 10 pc 367 -> 366, within 25 pc 5 479 -> 5 468; distances without an error 346 -> 332; HYG survivors 11 478 -> 11 465. validateMerge's count of Gliese rows beside their source took SIMBAD's map from the function the fold takes it from, so a slip in the map turned both off: with it empty, GJ 2097 and GJ 4285 were back inside 10 pc and the ETL passed. It now also asks for GJ 2097 and GJ 4285 beyond 20 pc and for no Gl 251 or Gl 422 by name, and glieseGaiaDesignations refuses fewer than 3 200 HYG rows matched to SIMBAD (3 352 of HYG's 3 801 measured).4d47896left 22 naked-eye HYG rows out as having no distance anywhere; two had one. HD 45951 (K0 III, V 6.2) was on the map only as "Gaia DR3 3369454521490604416", HYG's declination being 31.7' out, and θ¹ Ori A (HD 37020, HIP 26220) sits 0.1" from Gaia DR3 3017364132050194688, 2.643 ± 0.072 mas, which the magnitude test refused because HYG gives V 4.98 against G 6.63. One cached SIMBAD query now names the Gaia DR3 source of each naked-eye HYG row with no distance (199 of 206 HD numbers), and a row nothing else places takes that source from the bright list, at its position: HD 45951 at 112.0 pc, under its name, and θ¹ Ori A at 378.4 pc. HD 45951 joins the required names. The twenty still left out are named in build.ts; none has a bright source with a parallax five times its error within a minute of arc nor under its SIMBAD name. Naked-eye stars 8 898 -> 8 899; 455 532 -> 455 519 stars. The fold and the identity lookup are tested in star-merge.spec.ts. Guarded mutants (1 of 833 failed unless noted): - the Gliese row's photometry kept: caught by "...in Gaia's photometry" (and the Hipparcos-row fold test, 2 failed) - combine dropping the designation: caught by "folds one into a Gaia entry a HYG star of the same brightness..." - folding whatever the brightness; tolerance 2 magnitudes: caught by "leaves a star with a Hipparcos error alone..." - folding into bare entries only: caught by the Hipparcos-row fold test (and the leave-alone test, 2 failed) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+49
-16
@@ -10,6 +10,7 @@ import { fetchSolarSystem } from './fetchSolarSystem';
|
||||
import { BYTES_PER_STAR_META, BYTES_PER_STAR_POSITION, decodeStarCatalog, encodeStarCatalog, isDesignation } from '../../src/app/shared/models/star-catalog';
|
||||
import { fetchStars, glieseGaiaDesignations } from './fetchStars';
|
||||
import { ARCHIVE_EPOCH, archiveStarId, CATALOGUE_EPOCH } from '../../src/app/shared/astro/host-star-matching';
|
||||
import { foldsInto } from '../../src/app/shared/astro/star-merge';
|
||||
import { propagateProperMotion, raDegDecDistanceToXyz } from '../../src/app/shared/astro/coordinates';
|
||||
import { describeSources } from './sources/registry';
|
||||
import { dataPath } from './lib/paths';
|
||||
@@ -24,7 +25,7 @@ function assertCondition(condition: boolean, message: string): void {
|
||||
|
||||
/**
|
||||
* Stars whose magnitude is a stand-in, and distances published without an error. Measured 309 and
|
||||
* 346: the 44 Gaia sources with no G and the 265 archive hosts with neither V nor G; the 264 Gliese
|
||||
* 332: the 44 Gaia sources with no G and the 265 archive hosts with neither V nor G; the 250 Gliese
|
||||
* distances (no Hipparcos parallax behind them) and 82 archive hosts the archive gives no error
|
||||
* for. Losing either field loses it for hundreds of thousands of stars.
|
||||
*/
|
||||
@@ -73,16 +74,23 @@ const MIN_COLOURS_FROM_TEMPERATURE = 40;
|
||||
*/
|
||||
const MAX_ARCHIVE_IDS_OFF_THEIR_NAME = 5;
|
||||
/**
|
||||
* Every star the naked eye sees, kept at any distance (c64eea0): measured 8 898 of V 6.5 or
|
||||
* brighter, 1 663 of them past 250 pc. With no magnitude handed to placementDistancePc, 7 379 are
|
||||
* Every star the naked eye sees, kept at any distance (c64eea0): measured 8 899 of V 6.5 or
|
||||
* brighter, 1 664 of them past 250 pc. With no magnitude handed to placementDistancePc, 7 379 are
|
||||
* left and Rigel, Deneb and Alnilam are gone — and every other check passed, the HYG survivors
|
||||
* going down rather than up. HD 197770 and HD 45291 are two of the twelve only Gaia's bright
|
||||
* sources place, by position; without that lookup they are gone and the count drops by twelve,
|
||||
* which the floor alone would not see.
|
||||
* which the floor alone would not see. HD 45951 is one of the two only SIMBAD's name for its Gaia
|
||||
* source places, HYG's declination for it being 31.7′ out; θ¹ Ori A (HD 37020) is the other.
|
||||
*
|
||||
* Twenty HYG rows of V 6.5 or brighter are still left out, for want of a distance: neither HYG nor
|
||||
* Hipparcos gives one, and Gaia DR3 has no source brighter than G 7.5 with a parallax five times
|
||||
* its error within a minute of arc, nor under the name SIMBAD gives. They are β Phe, φ Cas, χ Aur,
|
||||
* ο¹ Cen, Polis, 16 Sgr, ρ Cas, η Car, and HD 47240, 50820, 90772, 97534, 100198, 101205, 101947,
|
||||
* 129092, 151804, 185936, 202214 and 212466.
|
||||
*/
|
||||
const MIN_NAKED_EYE_STARS = 8_800;
|
||||
const NAKED_EYE_MAGNITUDE_V = 6.5;
|
||||
const REQUIRED_NAKED_EYE_STARS = ['Rigel', 'Deneb', 'Alnilam', 'HD 197770', 'HD 45291'];
|
||||
const REQUIRED_NAKED_EYE_STARS = ['Rigel', 'Deneb', 'Alnilam', 'HD 197770', 'HD 45291', 'HD 45951'];
|
||||
|
||||
function validateStars(stars: StarRecord[]): void {
|
||||
assertCondition(stars.length > 0, 'No stars were produced.');
|
||||
@@ -218,37 +226,48 @@ function validateStars(stars: StarRecord[]): void {
|
||||
*
|
||||
* A star kept twice leaves its two entries near each other on the sky, from *different* sources —
|
||||
* one catalogue does not list a star twice. Under an arcsecond that is never two stars at this
|
||||
* depth, so every such pair is a miss. Twenty-three survive today; the nineteen first counted were
|
||||
* depth, so every such pair is a miss. Twenty-two survive today; the nineteen first counted were
|
||||
* all a second HYG row wanting a Gaia entry that already absorbed one (Gliese lists some doubles
|
||||
* twice), and the merge that trusted a Hipparcos parallax over direction left 1 112.
|
||||
*
|
||||
* The other failure leaves no close pair at all, because proper motion had already carried the
|
||||
* two entries tens of arcseconds apart — the 2026-08-24 refresh, where HYG sat at epoch 2000.0
|
||||
* and Gaia at J2016.0. What it does leave is HYG rows that found no counterpart: 36 056 of them
|
||||
* against the 11 478 today, and no counterpart was possible for most of those. 8 307 of them are
|
||||
* every star beyond 250 pc but the archive's planet hosts, which the main query never downloads: 1 660
|
||||
* against the 11 465 today, and no counterpart was possible for most of those. 8 308 of them are
|
||||
* every star beyond 250 pc but the archive's planet hosts, which the main query never downloads: 1 661
|
||||
* naked-eye stars kept at any distance, and 6 647 fainter ones that Hipparcos put inside
|
||||
* `ETL_STAR_DISTANCE_PC` while Gaia's parallax puts them past `ETL_GAIA_DISTANCE_PC`. The other
|
||||
* 3 170 are what Gaia genuinely lacks: 1 204 brighter than V 8, which it saturates on or measures
|
||||
* poorly, 1 804 between 8 and 12, and 162 fainter, 112 of them Gliese stars within 50 pc that
|
||||
* 3 156 are what Gaia genuinely lacks: 1 202 brighter than V 8, which it saturates on or measures
|
||||
* poorly, 1 793 between 8 and 12, and 161 fainter, 111 of them Gliese stars within 50 pc that
|
||||
* neither of its queries holds. So the headroom left to the ceiling tracks the gap between those two cutoffs as
|
||||
* much as Gaia's completeness.
|
||||
*
|
||||
* This bounds a merge that went wrong, and — loosely — a Gaia download that came back short: a
|
||||
* truncated answer leaves the HYG rows whose counterpart it dropped without one, so survivors go
|
||||
* *up*, not down. Measured on the main query when it was the only one, with 10 886 survivors
|
||||
* against today's 11 478: 11 004 at nine tenths of its rows, 12 711 at half, 16 258 at a third. So
|
||||
* against today's 11 465: 11 004 at nine tenths of its rows, 12 711 at half, 16 258 at a third. So
|
||||
* this ceiling only catches a deep truncation, and `fetchGaiaStars` catches the shallower ones
|
||||
* with a row floor on each query.
|
||||
*/
|
||||
const MAX_UNMERGED_TWINS = 100;
|
||||
/**
|
||||
* Gliese-only rows beside the Gaia entry SIMBAD names as the same star, which no geometry saw:
|
||||
* 49 before `foldByIdentity`, two of them false stars inside 10 pc (GJ 2097 at 6.41 pc and GJ 4285
|
||||
* at 6.80, which Gaia has at 24.47 and 28.25). The twin count above does not see them, being up to
|
||||
* minutes of arc apart.
|
||||
* 49 beside a bare one before `foldByIdentity`, two of them false stars inside 10 pc (GJ 2097 at
|
||||
* 6.41 pc and GJ 4285 at 6.80, which Gaia has at 24.47 and 28.25), and 10 beside one a Hipparcos row
|
||||
* already described, Gl 251 at 5.76 pc beside HD 265866. The twin count above does not see them,
|
||||
* being up to minutes of arc apart.
|
||||
*
|
||||
* That count takes SIMBAD's map from the function the fold takes it from, so a slip in the map
|
||||
* turns both off together: with it empty, GJ 2097 and GJ 4285 were back inside 10 pc and the count
|
||||
* read none. These are checked by name and distance instead.
|
||||
*/
|
||||
const MAX_GLIESE_ROWS_BESIDE_THEIR_GAIA_SOURCE = 0;
|
||||
const FOLDED_GLIESE_STARS = [
|
||||
{ name: 'GJ 2097', beyondPc: 20 },
|
||||
{ name: 'GJ 4285', beyondPc: 20 }
|
||||
];
|
||||
/** Gliese-only rows of stars HYG also lists by their Hipparcos row, HD 265866 and HD 304043. */
|
||||
const ABSORBED_GLIESE_ROWS = ['Gl 251', 'Gl 422'];
|
||||
const MAX_HYG_SURVIVORS = 15_000;
|
||||
const TWIN_TOLERANCE_RAD = (1 / 3600) * (Math.PI / 180);
|
||||
|
||||
@@ -296,12 +315,26 @@ function validateMerge(stars: StarRecord[], gaiaDesignationById: ReadonlyMap<num
|
||||
twins <= MAX_UNMERGED_TWINS,
|
||||
`${twins} stars from different catalogues sit within an arcsecond of each other (at most ${MAX_UNMERGED_TWINS} expected), starting with ${example} — the merge is keeping the same star twice.`
|
||||
);
|
||||
const bare = new Set(stars.filter((star) => star.source === 'gaia' && isDesignation(star)).map((star) => star.name));
|
||||
const beside = stars.filter((star) => star.source === 'hyg' && star.distanceError === undefined && bare.has(gaiaDesignationById.get(star.id) ?? ''));
|
||||
const byDesignation = new Map(stars.filter((star) => star.gaiaDesignation !== undefined).map((star) => [star.gaiaDesignation!, star]));
|
||||
const beside = stars.filter((star) => {
|
||||
const target = star.source === 'hyg' && star.distanceError === undefined ? byDesignation.get(gaiaDesignationById.get(star.id) ?? '') : undefined;
|
||||
return target !== undefined && foldsInto(target, star);
|
||||
});
|
||||
assertCondition(
|
||||
beside.length <= MAX_GLIESE_ROWS_BESIDE_THEIR_GAIA_SOURCE,
|
||||
`${beside.length} Gliese stars are drawn beside the Gaia source SIMBAD names them as, starting with ${beside[0]?.name} — the identity fold is not being made.`
|
||||
);
|
||||
for (const expected of FOLDED_GLIESE_STARS) {
|
||||
const star = stars.find((candidate) => candidate.name === expected.name);
|
||||
const distancePc = star && Math.hypot(star.x, star.y, star.z);
|
||||
assertCondition(
|
||||
distancePc !== undefined && distancePc > expected.beyondPc,
|
||||
`${expected.name} is at ${distancePc?.toFixed(2)} pc, not beyond ${expected.beyondPc} where Gaia measures it — Gliese stars are no longer folded into their Gaia source.`
|
||||
);
|
||||
}
|
||||
for (const name of ABSORBED_GLIESE_ROWS) {
|
||||
assertCondition(!stars.some((star) => star.name === name), `${name} is drawn beside the Hipparcos star it is — Gliese stars are no longer folded into their Gaia source.`);
|
||||
}
|
||||
console.log(` ${survivors} HYG stars have no Gaia counterpart; ${twins} unmerged cross-catalogue pairs within an arcsecond; ${beside.length} Gliese stars beside their own Gaia source.`);
|
||||
}
|
||||
|
||||
|
||||
+34
-10
@@ -1,11 +1,11 @@
|
||||
import { writeFileSync } from 'node:fs';
|
||||
|
||||
import { foldByIdentity, hipparcosDistancePc, mergeStarCatalogues, NAKED_EYE_MAGNITUDE, placementDistancePc } from '../../src/app/shared/astro/star-merge';
|
||||
import { foldByIdentity, hipparcosDistancePc, HYG_UNKNOWN_DISTANCE_PC, mergeStarCatalogues, NAKED_EYE_MAGNITUDE, placementDistancePc } from '../../src/app/shared/astro/star-merge';
|
||||
import { encodeStarCatalog } from '../../src/app/shared/models/star-catalog';
|
||||
import { StarRecord, SUN_STAR_ID } from '../../src/app/shared/models/star.model';
|
||||
import { BrightGaiaSource, fetchBrightGaiaSources, fetchGaiaDistancesByHip, fetchHipparcosParallaxErrors, GaiaAnswerError } from './sources/gaia';
|
||||
import { positionalSources } from './sources/registry';
|
||||
import { fetchGaiaDesignationsByGj } from './sources/simbad';
|
||||
import { fetchGaiaDesignationsByGj, fetchGaiaDesignationsByHd } from './sources/simbad';
|
||||
import { PARALLAX_PRECISION_MAS } from './sources/star-sources';
|
||||
import { parseCsvObjects, parseOptionalNumber } from './lib/csv';
|
||||
import { fetchTextCached } from './lib/http';
|
||||
@@ -86,10 +86,15 @@ export async function fetchStars(): Promise<StarRecord[]> {
|
||||
const gaiaByHip = await fetchGaiaDistancesByHip();
|
||||
const hipparcosErrors = await fetchHipparcosParallaxErrors();
|
||||
const brightGaia = await fetchBrightGaiaSources();
|
||||
const brightByDesignation = new Map(brightGaia.map((source) => [source.designation, source]));
|
||||
const nakedEyeDesignations = await fetchGaiaDesignationsByHd(
|
||||
rows.filter((row) => row['hd'] && Number(row['dist']) >= HYG_UNKNOWN_DISTANCE_PC && (parseOptionalNumber(row['mag']) ?? Infinity) <= NAKED_EYE_MAGNITUDE).map((row) => row['hd'])
|
||||
);
|
||||
|
||||
const stars: StarRecord[] = [];
|
||||
let atGaiaDistance = 0;
|
||||
let pastCutoff = 0;
|
||||
let identified = 0;
|
||||
|
||||
for (const row of rows) {
|
||||
const id = Number(row['id']);
|
||||
@@ -104,9 +109,13 @@ export async function fetchStars(): Promise<StarRecord[]> {
|
||||
const hipparcosPc = hipparcosDistancePc(hygPc, hipparcos);
|
||||
const magnitudeV = parseOptionalNumber(row['mag']);
|
||||
const magnitude = magnitudeV ?? UNKNOWN_MAGNITUDE;
|
||||
const gaia =
|
||||
(row['hip'] ? gaiaByHip.get(Number(row['hip'])) : undefined) ??
|
||||
(hipparcosPc === undefined && magnitude <= NAKED_EYE_MAGNITUDE ? brightCounterpart(row, magnitude, brightGaia) : undefined);
|
||||
const unplaced = hipparcosPc === undefined && magnitude <= NAKED_EYE_MAGNITUDE;
|
||||
const crossMatched = row['hip'] ? gaiaByHip.get(Number(row['hip'])) : undefined;
|
||||
const positional = crossMatched === undefined && unplaced ? brightCounterpart(row, magnitude, brightGaia) : undefined;
|
||||
// Where HYG's position leads to no bright source, the one SIMBAD names it as: HD 45951, whose
|
||||
// declination HYG has 31.7′ out, and θ¹ Ori A (HD 37020), whose V it has 1.65 brighter than G.
|
||||
const byIdentity = crossMatched === undefined && positional === undefined && unplaced ? brightByDesignation.get(nakedEyeDesignations.get(`HD ${row['hd']}`) ?? '') : undefined;
|
||||
const gaia = crossMatched ?? positional ?? byIdentity;
|
||||
const gaiaPc = gaia?.distancePc;
|
||||
const hipparcosError = hipparcos?.relativeError;
|
||||
const distancePc = placementDistancePc(hipparcosPc, gaiaPc, magnitude, DISTANCE_CUTOFF_PC, hipparcosError, gaia?.relativeError);
|
||||
@@ -124,10 +133,9 @@ export async function fetchStars(): Promise<StarRecord[]> {
|
||||
// HIP 57146, has x/y/z 161″ from its own ra/dec and stays double).
|
||||
//
|
||||
// Only their direction is used. They sit at HYG's own distance, or at its 100 000 pc
|
||||
// placeholder where it has none, and are carried along that direction to the one chosen above.
|
||||
const x = Number(row['x']);
|
||||
const y = Number(row['y']);
|
||||
const z = Number(row['z']);
|
||||
// placeholder where it has none, and are carried along that direction to the one chosen above —
|
||||
// Gaia's, for a star found by its identity, where HYG's may be the thing that is wrong.
|
||||
const [x, y, z] = byIdentity ? [byIdentity.direction.x, byIdentity.direction.y, byIdentity.direction.z] : [Number(row['x']), Number(row['y']), Number(row['z'])];
|
||||
const length = Math.hypot(x, y, z);
|
||||
if (![x, y, z].every(Number.isFinite) || length === 0) {
|
||||
continue;
|
||||
@@ -136,6 +144,9 @@ export async function fetchStars(): Promise<StarRecord[]> {
|
||||
if (fromGaia) {
|
||||
atGaiaDistance++;
|
||||
}
|
||||
if (byIdentity) {
|
||||
identified++;
|
||||
}
|
||||
if (distancePc > DISTANCE_CUTOFF_PC) {
|
||||
pastCutoff++;
|
||||
}
|
||||
@@ -165,7 +176,9 @@ export async function fetchStars(): Promise<StarRecord[]> {
|
||||
});
|
||||
}
|
||||
|
||||
console.log(` kept ${stars.length} stars (of ${rows.length} in the catalog): ${atGaiaDistance} at Gaia's distance, ${pastCutoff} of them past ${DISTANCE_CUTOFF_PC} pc.`);
|
||||
console.log(
|
||||
` kept ${stars.length} stars (of ${rows.length} in the catalog): ${atGaiaDistance} at Gaia's distance, ${pastCutoff} of them past ${DISTANCE_CUTOFF_PC} pc; ${identified} naked-eye stars placed by the Gaia source SIMBAD names them as.`
|
||||
);
|
||||
|
||||
const { stars: merged, folded } = foldByIdentity(await mergeWithOtherSources(stars), await glieseGaiaDesignations(rows));
|
||||
console.log(` ${folded} Gliese entries folded into the Gaia source SIMBAD names them as.`);
|
||||
@@ -203,6 +216,14 @@ function simbadGliese(gl: string): string {
|
||||
return gl.trim().replace(/^Gl\s+/, 'GJ ').replace(/^(GJ \d+(?:\.\d+)?)\s*([A-Z]+)$/, '$1 $2');
|
||||
}
|
||||
|
||||
/**
|
||||
* How many HYG rows with a Gliese number SIMBAD names a Gaia source for: 3 352 of HYG's 3 801 on
|
||||
* 2026-09-30. SIMBAD's side has its own floor; this one is on the join, where HYG's "Gl 94" has to
|
||||
* become SIMBAD's "GJ 94". Joined on HYG's names as they stand, 38 rows were folded instead of 49
|
||||
* and the check in build.ts, which takes this map too, still counted none left.
|
||||
*/
|
||||
const MIN_GLIESE_IDENTITIES = 3_200;
|
||||
|
||||
/**
|
||||
* The Gaia DR3 designation SIMBAD gives each HYG star with a Gliese number, by HYG id: what the
|
||||
* merge folds its Gliese-only rows by, and what build.ts checks it did. `rows` are HYG's, read
|
||||
@@ -218,6 +239,9 @@ export async function glieseGaiaDesignations(rows?: Record<string, string>[]): P
|
||||
designations.set(Number(row['id']), designation);
|
||||
}
|
||||
}
|
||||
if (designations.size < MIN_GLIESE_IDENTITIES) {
|
||||
throw new Error(`Only ${designations.size} HYG stars with a Gliese number were matched to SIMBAD's (at least ${MIN_GLIESE_IDENTITIES} expected) — HYG's designations are no longer written as SIMBAD writes them.`);
|
||||
}
|
||||
return designations;
|
||||
}
|
||||
|
||||
|
||||
@@ -219,6 +219,7 @@ function rowsToStars(rows: readonly Record<string, string>[], idBase: number): S
|
||||
stars.push({
|
||||
id: idBase + index,
|
||||
name: `Gaia DR3 ${row['source_id']}`,
|
||||
gaiaDesignation: `Gaia DR3 ${row['source_id']}`,
|
||||
x,
|
||||
y,
|
||||
z,
|
||||
@@ -298,8 +299,9 @@ export async function fetchGaiaDistancesByHip(): Promise<Map<number, { distanceP
|
||||
const BRIGHT_MAGNITUDE_LIMIT = 7.5;
|
||||
const BRIGHT_QUERY_ROWS = 35_910;
|
||||
|
||||
/** A bright Gaia source's J2000 direction, G magnitude and distance. */
|
||||
/** A bright Gaia source's designation, J2000 direction, G magnitude and distance. */
|
||||
export interface BrightGaiaSource {
|
||||
designation: string;
|
||||
direction: { x: number; y: number; z: number };
|
||||
magnitudeG: number;
|
||||
distancePc: number;
|
||||
@@ -330,7 +332,7 @@ export async function fetchBrightGaiaSources(): Promise<BrightGaiaSource[]> {
|
||||
continue;
|
||||
}
|
||||
const j2000 = propagateProperMotion(raDeg, decDeg, parseOptionalNumber(row['pmra']) ?? 0, parseOptionalNumber(row['pmdec']) ?? 0, CATALOGUE_EPOCH - GAIA_DR3_EPOCH);
|
||||
sources.push({ direction: raDegDecDistanceToXyz(j2000.raDeg, j2000.decDeg, 1), magnitudeG, distancePc: 1000 / parallaxMas, relativeError: 1 / overError });
|
||||
sources.push({ designation: `Gaia DR3 ${row['source_id']}`, direction: raDegDecDistanceToXyz(j2000.raDeg, j2000.decDeg, 1), magnitudeG, distancePc: 1000 / parallaxMas, relativeError: 1 / overError });
|
||||
}
|
||||
return sources;
|
||||
}
|
||||
|
||||
+41
-11
@@ -4,7 +4,7 @@ import { parseCsvObjects } from '../lib/csv';
|
||||
import { fetchTextCached } from '../lib/http';
|
||||
|
||||
/**
|
||||
* SIMBAD, via its TAP service, for one thing: which Gaia DR3 source a Gliese star is.
|
||||
* SIMBAD, via its TAP service, for one thing: which Gaia DR3 source a star is.
|
||||
*
|
||||
* The merge decides identity on the sky, and for the HYG rows Gliese places without Hipparcos
|
||||
* astrometry the sky is not enough: their positions are off by up to minutes of arc, their
|
||||
@@ -24,25 +24,55 @@ const MIN_GJ_DESIGNATIONS = 4_000;
|
||||
* cache, do not change when those rows do.
|
||||
*/
|
||||
export async function fetchGaiaDesignationsByGj(): Promise<Map<string, string>> {
|
||||
const query =
|
||||
"select i1.id as gj, i2.id as gaia from ident as i1 join ident as i2 on i1.oidref = i2.oidref where i1.id like 'GJ %' and i2.id like 'Gaia DR3 %' order by gj, gaia";
|
||||
const designations = await fetchDesignations(
|
||||
"select i1.id as gj, i2.id as gaia from ident as i1 join ident as i2 on i1.oidref = i2.oidref where i1.id like 'GJ %' and i2.id like 'Gaia DR3 %' order by gj, gaia",
|
||||
'gj',
|
||||
'simbad-gj-gaia'
|
||||
);
|
||||
if (designations.size < MIN_GJ_DESIGNATIONS) {
|
||||
throw new Error(`SIMBAD gave ${designations.size} GJ stars a Gaia DR3 designation, not the ~4 868 it holds; delete tools/etl/.cache/simbad-gj-gaia-*.csv once it answers properly`);
|
||||
}
|
||||
return designations;
|
||||
}
|
||||
|
||||
/** Of the 206 HD numbers asked for below, SIMBAD gave 199 a Gaia DR3 designation on 2026-09-30. */
|
||||
const MIN_HD_DESIGNATION_SHARE = 0.9;
|
||||
|
||||
/**
|
||||
* SIMBAD's Gaia DR3 designation for each of `hdNumbers`, keyed "HD 45951": for the naked-eye HYG
|
||||
* rows with no distance, which HYG's own position does not always lead to — HD 45951's declination
|
||||
* there is 31.7′ off SIMBAD's. Asked by number, since the HD catalogue whole would be 360 000 rows;
|
||||
* the numbers are HYG's, so the answer and its cache change only when HYG does.
|
||||
*/
|
||||
export async function fetchGaiaDesignationsByHd(hdNumbers: readonly string[]): Promise<Map<string, string>> {
|
||||
const list = [...hdNumbers].sort().map((hd) => `'HD ${hd}'`).join(', ');
|
||||
const designations = await fetchDesignations(
|
||||
`select i1.id as hd, i2.id as gaia from ident as i1 join ident as i2 on i1.oidref = i2.oidref where i1.id in (${list}) and i2.id like 'Gaia DR3 %' order by hd, gaia`,
|
||||
'hd',
|
||||
'simbad-hd-gaia'
|
||||
);
|
||||
if (designations.size < hdNumbers.length * MIN_HD_DESIGNATION_SHARE) {
|
||||
throw new Error(`SIMBAD gave ${designations.size} of ${hdNumbers.length} HD stars a Gaia DR3 designation; delete tools/etl/.cache/simbad-hd-gaia-*.csv once it answers properly`);
|
||||
}
|
||||
return designations;
|
||||
}
|
||||
|
||||
/** The identifier in `column` → Gaia DR3 designation pairs a query answers, SIMBAD's padding collapsed ("HD 45951"). */
|
||||
async function fetchDesignations(query: string, column: string, cachePrefix: string): Promise<Map<string, string>> {
|
||||
const url = `${SIMBAD_TAP_URL}?REQUEST=doQuery&LANG=ADQL&FORMAT=csv&QUERY=${encodeURIComponent(query)}`;
|
||||
const rows = parseCsvObjects(await fetchTextCached(url, `simbad-gj-gaia-${createHash('sha1').update(url).digest('hex').slice(0, 8)}.csv`));
|
||||
const rows = parseCsvObjects(await fetchTextCached(url, `${cachePrefix}-${createHash('sha1').update(url).digest('hex').slice(0, 8)}.csv`));
|
||||
|
||||
const designations = new Map<string, string>();
|
||||
for (const row of rows) {
|
||||
const gj = collapse(row['gj'] ?? '');
|
||||
const identifier = collapse(row[column] ?? '');
|
||||
// An object SIMBAD gives two Gaia DR3 sources is one it has not resolved; neither is the star.
|
||||
designations.set(gj, designations.has(gj) ? '' : collapse(row['gaia'] ?? ''));
|
||||
designations.set(identifier, designations.has(identifier) ? '' : collapse(row['gaia'] ?? ''));
|
||||
}
|
||||
for (const [gj, designation] of designations) {
|
||||
for (const [identifier, designation] of designations) {
|
||||
if (!designation) {
|
||||
designations.delete(gj);
|
||||
designations.delete(identifier);
|
||||
}
|
||||
}
|
||||
if (designations.size < MIN_GJ_DESIGNATIONS) {
|
||||
throw new Error(`SIMBAD gave ${designations.size} GJ stars a Gaia DR3 designation (of ${rows.length} rows), not the ~4 868 it holds; delete tools/etl/.cache/simbad-gj-gaia-*.csv once it answers properly`);
|
||||
}
|
||||
return designations;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user