Say which band each star was measured in, whose catalogue it is, and how sure its distance is
The readout named Hipparcos, Yale and Gliese while 378 775 of the 455 608 stars are described by Gaia DR3, printed one "Magnitude" for G and V alike, and gave every distance to the parsec. The band cannot be read off the star's source, which records whose position it has: 62 002 stars Gaia places keep HYG's V and B-V, and the 575 hosts renamed after their planets are Gaia's, in G. stars-meta.bin gains two byte columns, 14 to 16 bytes a star (6 378 512 to 7 289 728 bytes; gzip -9 2 804 049 to 3 110 991). One holds the magnitude's band (V 76 555 stars, G 378 744, none 309, whose magnitude is a stand-in), which colour the colour index is (B-V 75 203, BP-RP 376 703), and whether the distance is Gaia's parallax. The other holds the distance's relative error as its square root in 255ths: a step is 0.08 % of distance at 1 %, 0.35 % at 20 %, and 100 % is the top. encode, decode, BYTES_PER_STAR_META and the build.ts round trip cover both; no workflow reads the format. Where the errors come from: - Gaia rows keep the parallax_error their query already fetched: median 0.3 %, 90th percentile 1.2 %, at most 20 %, the query's own cut. - A HYG star at Gaia's distance takes the cross-match's parallax_over_error, and a star merged into a Gaia entry keeps that entry's error with its position. - The 3 067 Hipparcos stars that keep their Hipparcos distance, Rigel, Deneb and Alnilam among them, take e_plx from van Leeuwen's 2007 reduction: a new cached query of public.hipparcos_newreduction on the ESA archive, whose 117 955 rows HYG's distances invert. - The archive's stars take sy_disterr1/2 from pscomppars, in a query and cache file of their own so the composite rows already cached were not refetched. - 439 distances have no published error: 357 Gliese rows and 82 archive hosts. Of the errors, 392 786 are 1 % or less and are not printed; 61 156 print as "117 ± 12 pc" to the distance's own digits; 1 196 between 20 and 100 %, and 30 past it, print as the range the parallax gives, since a symmetric error in parallax is a lopsided one in distance. The star card (measured on the dev server) now reads, for example: - Rigel: 265 ± 23 pc, V 0.18, B-V -0.03, source HYG. - Deneb: 433 ± 60 pc. - Alnilam: "476 pc to 833 pc" (Hipparcos 1.65 ± 0.45 mas). - Gaia DR3 5612323414549657984: 111 ± 2 pc, G 4.63, BP-RP -0.15, source Gaia DR3. - Proxima Centauri: 1.30 pc, V 11.01, source "HYG, Gaia DR3 distance". - TRAPPIST-1: G 15.62, BP-RP 4.90, source Gaia DR3. - Kepler-186: V 15.14, source NASA Exoplanet Archive. The neighbourhood's subtitle reads "Gaia DR3 378,775 · HYG 73,556 · NASA Exoplanet Archive 3,277", counted by the catalogue describing each star. build.ts validateStars now fails a catalogue with more than 1 000 stars without a band (309 today) or without a distance error (439). Dropping G from the Gaia rows gave 379 040 without a band, and dropping their parallax_error gave 441 216 without an error; both runs failed. Decoding the catalogue in Node took a median 29 ms before and 24 ms after (nine runs each, within noise). In the app, five cold boots gave a 654-786 ms long task after the data landed and the HUD at 1.83-2.07 s. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -46,7 +46,7 @@ import {
|
||||
systemFramingDistanceAu,
|
||||
systemViewDirection,
|
||||
} from './system-framing';
|
||||
import { formatAu, formatLuminosity, formatParsecs } from '../../shared/format/quantity';
|
||||
import { formatAu, formatParsecs } from '../../shared/format/quantity';
|
||||
import {
|
||||
distanceRings,
|
||||
formatRoundLength,
|
||||
@@ -83,6 +83,7 @@ import { JumpLinkRenderer } from './jump-link-renderer';
|
||||
import { ReservedBox, ringPlacement } from './label-ring';
|
||||
import { LabeledPoint, LabelSide, StarLabelOverlay } from './star-label-overlay';
|
||||
import { SystemOrbitsRenderer } from './system-orbits-renderer';
|
||||
import { catalogueCensus, starReadouts } from './star-readouts';
|
||||
|
||||
/** HYG catalog id for the Sun itself — the only star we have a real close-up photo of. */
|
||||
const SOL_STAR_ID = 0;
|
||||
@@ -532,6 +533,8 @@ export class GalaxySystemSceneComponent implements AfterViewInit, OnDestroy {
|
||||
private galacticStrength = 0;
|
||||
private labelOverlay?: StarLabelOverlay;
|
||||
private stars: readonly StarRecord[] = [];
|
||||
/** The neighbourhood's subtitle: what the catalogue holds, by the catalogue describing it. */
|
||||
private catalogueCensus = '';
|
||||
private starsById = new Map<number, StarRecord>();
|
||||
private bodies: readonly BodyRecord[] = [];
|
||||
private exoplanets: readonly ExoplanetRecord[] = [];
|
||||
@@ -679,6 +682,7 @@ export class GalaxySystemSceneComponent implements AfterViewInit, OnDestroy {
|
||||
}),
|
||||
]);
|
||||
this.stars = stars;
|
||||
this.catalogueCensus = catalogueCensus(stars);
|
||||
this.starsById = new Map(stars.map((star) => [star.id, star]));
|
||||
this.neighbourhood = new StarNeighbourhood(stars);
|
||||
this.routing = new RoutingClient(stars, positions, this.neighbourhood);
|
||||
@@ -1742,8 +1746,6 @@ export class GalaxySystemSceneComponent implements AfterViewInit, OnDestroy {
|
||||
const moonCount = this.bodies.filter(
|
||||
(body) => body.systemStarId === star.id && body.parentBodyId,
|
||||
).length;
|
||||
const distancePc = Math.hypot(star.x, star.y, star.z);
|
||||
const luminosity = luminosityOf(star);
|
||||
this.hudEyebrow.set('System');
|
||||
this.hudTitle.set(star.name);
|
||||
this.hudSubtitle.set(star.spectralType ? `Spectral type ${star.spectralType}` : '');
|
||||
@@ -1752,13 +1754,7 @@ export class GalaxySystemSceneComponent implements AfterViewInit, OnDestroy {
|
||||
label: 'Bodies',
|
||||
value: moonCount > 0 ? `${planetCount} + ${moonCount} moons` : `${planetCount}`,
|
||||
},
|
||||
// Suppressed for the Sun rather than printed as `0.00 pc`, which is arithmetically right
|
||||
// and reads as a bug: the distance from here to here is not a measurement.
|
||||
...(distancePc > 0 ? [{ label: 'Distance', value: formatParsecs(distancePc) }] : []),
|
||||
{ label: 'Magnitude', value: star.magnitude.toFixed(2) },
|
||||
...(luminosity !== null
|
||||
? [{ label: 'Luminosity', value: formatLuminosity(luminosity), derived: true }]
|
||||
: []),
|
||||
...starReadouts(star, luminosityOf(star)),
|
||||
]);
|
||||
this.hudNote.set(this.time.atNow() ? 'Orbits propagated from published elements to the current date.' : 'Orbits propagated from published elements to the date on the clock.');
|
||||
this.hudRange.set(
|
||||
@@ -1796,7 +1792,7 @@ export class GalaxySystemSceneComponent implements AfterViewInit, OnDestroy {
|
||||
|
||||
this.hudEyebrow.set('Solar Neighbourhood');
|
||||
this.hudTitle.set('Local Stars');
|
||||
this.hudSubtitle.set('Hipparcos · Yale Bright Star · Gliese');
|
||||
this.hudSubtitle.set(this.catalogueCensus);
|
||||
this.hudReadouts.set([
|
||||
// Both numbers, because they differ: the catalogue is what the map knows and the first is
|
||||
// what it draws. See `STAR_RENDER_BUDGET`.
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { StarRecord } from '../../shared/models/star.model';
|
||||
import { catalogueCensus, describingCatalogue, starReadouts } from './star-readouts';
|
||||
|
||||
/** Three kinds of star the catalogue holds, each as the decoder gives it back. */
|
||||
const HYG_STAR: StarRecord = {
|
||||
id: 32263,
|
||||
name: 'Sirius',
|
||||
x: -0.49,
|
||||
y: 2.47,
|
||||
z: -0.75,
|
||||
magnitude: -1.44,
|
||||
magnitudeBand: 'V',
|
||||
spectralType: 'A0m',
|
||||
colorIndex: 0.009,
|
||||
colorSystem: 'B-V',
|
||||
distanceError: 0.004,
|
||||
distanceFromGaia: false,
|
||||
source: 'hyg'
|
||||
};
|
||||
// A star Gaia alone knows, at 117 pc and good to a tenth.
|
||||
const GAIA_STAR: StarRecord = {
|
||||
id: 1000000001,
|
||||
name: 'Gaia DR3 5612323414549657984',
|
||||
x: 117,
|
||||
y: 0,
|
||||
z: 0,
|
||||
magnitude: 11.2,
|
||||
magnitudeBand: 'G',
|
||||
spectralType: 'Unknown',
|
||||
colorIndex: 1.43,
|
||||
colorSystem: 'BP-RP',
|
||||
distanceError: 0.1,
|
||||
distanceFromGaia: true,
|
||||
source: 'gaia'
|
||||
};
|
||||
// HYG's description of Proxima, at the place and distance Gaia measures.
|
||||
const PLACED_BY_GAIA: StarRecord = { ...HYG_STAR, name: 'Proxima Centauri', magnitude: 11.01, colorIndex: 1.807, spectralType: 'M5Ve', distanceFromGaia: true, source: 'gaia' };
|
||||
|
||||
function value(readouts: { label: string; value: string }[], label: string): string | undefined {
|
||||
return readouts.find((readout) => readout.label === label)?.value;
|
||||
}
|
||||
|
||||
describe('starReadouts', () => {
|
||||
it('names the band of the magnitude, and which colour the colour index is', () => {
|
||||
expect(value(starReadouts(HYG_STAR, null), 'Magnitude')).toBe('V -1.44');
|
||||
expect(value(starReadouts(GAIA_STAR, null), 'Magnitude')).toBe('G 11.20');
|
||||
expect(value(starReadouts(HYG_STAR, null), 'Colour')).toBe('B−V 0.01');
|
||||
expect(value(starReadouts(GAIA_STAR, null), 'Colour')).toBe('BP−RP 1.43');
|
||||
});
|
||||
|
||||
it('says a stand-in magnitude was not measured, and leaves out a colour there is none of', () => {
|
||||
const unmeasured = starReadouts({ ...GAIA_STAR, magnitude: 12, magnitudeBand: undefined, colorIndex: null, colorSystem: undefined }, null);
|
||||
expect(value(unmeasured, 'Magnitude')).toBe('Not measured');
|
||||
expect(value(unmeasured, 'Colour')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('gives the distance with its uncertainty', () => {
|
||||
expect(value(starReadouts(GAIA_STAR, null), 'Distance')).toBe('117 ± 12 pc');
|
||||
});
|
||||
|
||||
it('names the catalogue a star comes from, and whose distance it has', () => {
|
||||
expect(value(starReadouts(GAIA_STAR, null), 'Source')).toBe('Gaia DR3');
|
||||
expect(value(starReadouts(HYG_STAR, null), 'Source')).toBe('HYG');
|
||||
expect(value(starReadouts(PLACED_BY_GAIA, null), 'Source')).toBe('HYG, Gaia DR3 distance');
|
||||
expect(value(starReadouts({ ...HYG_STAR, source: 'exoplanet-archive' }, null), 'Source')).toBe('NASA Exoplanet Archive');
|
||||
});
|
||||
|
||||
it('marks the luminosity as derived', () => {
|
||||
expect(starReadouts(HYG_STAR, 25.4).find((readout) => readout.label === 'Luminosity')).toEqual({ label: 'Luminosity', value: '25.40 L☉', derived: true });
|
||||
});
|
||||
});
|
||||
|
||||
describe('catalogueCensus', () => {
|
||||
it('counts the stars by the catalogue describing them, not by whose position they have', () => {
|
||||
expect(describingCatalogue(PLACED_BY_GAIA)).toBe('HYG');
|
||||
expect(catalogueCensus([GAIA_STAR, GAIA_STAR, GAIA_STAR, HYG_STAR, PLACED_BY_GAIA])).toBe('Gaia DR3 3 · HYG 2');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,52 @@
|
||||
import { formatDistance, formatLuminosity } from '../../shared/format/quantity';
|
||||
import { StarRecord } from '../../shared/models/star.model';
|
||||
import { HudReadout } from '../hud/hud-dock.component';
|
||||
|
||||
/**
|
||||
* The catalogue that describes a star — its name, type and photometry — as the readout names it.
|
||||
*
|
||||
* Not the same as `source`, which records whose *position* the star has: 62 002 stars HYG
|
||||
* describes sit where Gaia places them, and carry `gaia`. What gives them away is their V
|
||||
* magnitude, which only HYG and the archive measure and the archive's stars have their own source.
|
||||
*/
|
||||
export function describingCatalogue(star: StarRecord): string {
|
||||
if (star.source === 'exoplanet-archive') {
|
||||
return 'NASA Exoplanet Archive';
|
||||
}
|
||||
return star.source === 'gaia' && star.magnitudeBand !== 'V' ? 'Gaia DR3' : 'HYG';
|
||||
}
|
||||
|
||||
/**
|
||||
* A star's measured readouts, each with what it was measured in: the band of its magnitude, which
|
||||
* colour its colour index is, the distance's uncertainty, and the catalogues they come from.
|
||||
* `luminosity` is derived, and marked so.
|
||||
*/
|
||||
export function starReadouts(star: StarRecord, luminosity: number | null): HudReadout[] {
|
||||
const distancePc = Math.hypot(star.x, star.y, star.z);
|
||||
const catalogue = describingCatalogue(star);
|
||||
return [
|
||||
// Suppressed for the Sun rather than printed as `0.00 pc`, which is arithmetically right
|
||||
// and reads as a bug: the distance from here to here is not a measurement.
|
||||
...(distancePc > 0 ? [{ label: 'Distance', value: formatDistance(distancePc, star.distanceError) }] : []),
|
||||
// A G magnitude and a V one are not comparable: a red dwarf is up to three brighter in G.
|
||||
{ label: 'Magnitude', value: star.magnitudeBand ? `${star.magnitudeBand} ${star.magnitude.toFixed(2)}` : 'Not measured' },
|
||||
...(star.colorIndex !== null
|
||||
? [{ label: 'Colour', value: `${star.colorSystem === 'BP-RP' ? 'BP−RP' : 'B−V'} ${star.colorIndex.toFixed(2)}` }]
|
||||
: []),
|
||||
...(luminosity !== null ? [{ label: 'Luminosity', value: formatLuminosity(luminosity), derived: true }] : []),
|
||||
{ label: 'Source', value: catalogue === 'HYG' && star.distanceFromGaia ? 'HYG, Gaia DR3 distance' : catalogue }
|
||||
];
|
||||
}
|
||||
|
||||
/** What the catalogue holds, counted by the catalogue describing each star, largest first. */
|
||||
export function catalogueCensus(stars: readonly StarRecord[]): string {
|
||||
const counts = new Map<string, number>();
|
||||
for (const star of stars) {
|
||||
const catalogue = describingCatalogue(star);
|
||||
counts.set(catalogue, (counts.get(catalogue) ?? 0) + 1);
|
||||
}
|
||||
return [...counts]
|
||||
.sort((a, b) => b[1] - a[1])
|
||||
.map(([catalogue, count]) => `${catalogue} ${count.toLocaleString('en-GB')}`)
|
||||
.join(' · ');
|
||||
}
|
||||
Reference in New Issue
Block a user