Initial Commit
This commit is contained in:
24
packages/geospatial/geo/stars/README.md
Normal file
24
packages/geospatial/geo/stars/README.md
Normal file
@@ -0,0 +1,24 @@
|
||||
# @suaveplan/stars - Star catalog and observation provider: in-memory catalog source, multi-source aggregating provider, cone queries, and observed-star projections built on the @suaveplan/types StarProvider contract.
|
||||
|
||||
## Summary
|
||||
|
||||
Star catalog provider.
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
## Documentation tree
|
||||
|
||||
- [catalog](./src/catalog/catalog.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [catalog](./src/catalog/catalog.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [provider](./src/provider/provider.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [provider](./src/provider/provider.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [types](./src/types.md) — The types module declares the two foundational shapes that `@suaveplan/stars` is built on: `RawStar`, the lossless cata…
|
||||
<!-- end:auto-docs --> - [catalog](./src/catalog/catalog.md) — <!-- begin:auto-docs --> _(no submodules)_- [provider](./src/provider/provider.md) — <!-- begin:auto-docs --> _(no submodules)_- [provider](./src/provider/provider.md) — <!-- begin:auto-docs --> _(no submodules)_- [types](./src/types.md) — The types module declares the two foundational shapes that `@suaveplan/stars` is built on: `RawStar`, the lossless cata…
|
||||
- [catalog](./src/catalog/catalog.md) — <!-- begin:auto-docs --> _(no submodules)_- [provider](./src/provider/provider.md) — <!-- begin:auto-docs --> _(no submodules)_- [provider](./src/provider/provider.md) — <!-- begin:auto-docs --> _(no submodules)_- [types](./src/types.md) — The types module declares the two foundational shapes that `@suaveplan/stars` is built on: `RawStar`, the lossless cata…
|
||||
- [catalog](./src/catalog/catalog.md) — <!-- begin:auto-docs --> _(no submodules)_- [provider](./src/provider/provider.md) — <!-- begin:auto-docs --> _(no submodules)_- [provider](./src/provider/provider.md) — <!-- begin:auto-docs --> _(no submodules)_- [types](./src/types.md) — The types module declares the two foundational shapes that `@suaveplan/stars` is built on: `RawStar`, the lossless cata…
|
||||
- [catalog](./src/catalog/catalog.md) — <!-- begin:auto-docs --> _(no submodules)_- [provider](./src/provider/provider.md) — <!-- begin:auto-docs --> _(no submodules)_ - [provider](./src/provider/provider.md) — <!-- begin:auto-docs --> _(no submodules)_- [types](./src/types.md) — The types module declares the two foundational shapes that `@suaveplan/stars` is built on: `RawStar`, the lossless cata…
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
bun add @suaveplan/stars
|
||||
```
|
||||
47
packages/geospatial/geo/stars/package.json
Normal file
47
packages/geospatial/geo/stars/package.json
Normal file
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"name": "@suaveplan/stars",
|
||||
"version": "0.1.0",
|
||||
"description": "Star catalog and observation provider: in-memory catalog source, multi-source aggregating provider, cone queries, and observed-star projections built on the @suaveplan/types StarProvider contract.",
|
||||
"type": "module",
|
||||
"sideEffects": false,
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts"
|
||||
},
|
||||
"./provider/provider": {
|
||||
"import": "./dist/provider/provider.js",
|
||||
"types": "./dist/provider/provider.d.ts"
|
||||
},
|
||||
"./catalog/catalog": {
|
||||
"import": "./dist/catalog/catalog.js",
|
||||
"types": "./dist/catalog/catalog.d.ts"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"build": "vite build",
|
||||
"lint": "bunx biome check src/",
|
||||
"test": "vitest run --passWithNoTests",
|
||||
"test:coverage": "vitest run --coverage",
|
||||
"typecheck": "bunx tsc --noEmit --composite false -p tsconfig.lib.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@opentelemetry/api": "catalog:observability",
|
||||
"@suaveplan/error": "workspace:^",
|
||||
"@suaveplan/geo": "workspace:^",
|
||||
"@suaveplan/geo-planet": "workspace:^",
|
||||
"@suaveplan/types": "workspace:^",
|
||||
"@suaveplan/universe": "workspace:^",
|
||||
"zod": "catalog:forms"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@suaveplan/testing": "workspace:^",
|
||||
"typescript": "catalog:",
|
||||
"vite": "catalog:build",
|
||||
"vite-plugin-dts": "catalog:build",
|
||||
"vitest": "catalog:testing"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5.8"
|
||||
}
|
||||
}
|
||||
50
packages/geospatial/geo/stars/src/catalog/catalog.md
Normal file
50
packages/geospatial/geo/stars/src/catalog/catalog.md
Normal file
@@ -0,0 +1,50 @@
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
The catalog module provides an in-memory implementation of the `StarCatalogSource` contract used by `@suaveplan/stars` providers. It accepts a static array of `RawStar` records and exposes them through the same `queryCone` and `getStar` surface that production catalog adapters (Hipparcos, Tycho-2, Gaia DR3) implement, so consumers can swap real catalogs for fixtures without touching call sites.
|
||||
|
||||
It is the recommended source for tests, demos, offline planetariums, and bootstrap data flows where pulling a remote catalog is unnecessary or undesirable. The implementation favours predictability over scale: lookups are O(n) for cone queries and O(1) for id resolution backed by a single `Map`.
|
||||
|
||||
## Features
|
||||
|
||||
- Pure in-memory `StarCatalogSource` with no external dependencies.
|
||||
- Constant-time `getStar(catalogId)` lookup via internal `Map`.
|
||||
- Naive Cartesian cone filter on `(ra, dec)` for deterministic test output.
|
||||
- Returns the same `RawStar` shape consumed by `createStarProvider`.
|
||||
- Zero allocations on the hot path beyond the result array.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { createInMemoryCatalogSource } from "@suaveplan/stars";
|
||||
|
||||
const source = createInMemoryCatalogSource([
|
||||
{ catalogId: "HIP-32349", ra: 101.287, dec: -16.716, apparentMagnitude: -1.46 },
|
||||
]);
|
||||
|
||||
const sirius = await source.getStar("HIP-32349");
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { createInMemoryCatalogSource, createStarProvider } from "@suaveplan/stars";
|
||||
|
||||
const source = createInMemoryCatalogSource([
|
||||
{ catalogId: "HIP-32349", ra: 101.287, dec: -16.716, apparentMagnitude: -1.46, spectralClass: "A1V", distance: 2.64 },
|
||||
{ catalogId: "HIP-37279", ra: 114.825, dec: 5.225, apparentMagnitude: 0.4, spectralClass: "F5IV-V" },
|
||||
]);
|
||||
const provider = createStarProvider([source]);
|
||||
const nearby = await provider.queryCone(101, -16, 5);
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `createInMemoryCatalogSource(stars: RawStar[]): StarCatalogSource` — builds a source whose `queryCone(ra, dec, radiusDeg)` returns every star inside the Cartesian disk of the given radius (degrees) around the centre, and whose `getStar(catalogId)` returns the matching record or `null`. Both methods are async to satisfy the interface; neither throws.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The cone filter uses Euclidean distance on raw `(ra, dec)` pairs rather than a spherical-trig great-circle distance. This is deliberate: the source is for fixtures and small catalogs, where simplicity and determinism beat correctness near the poles or across the RA wrap. For real-sky use, layer a cosine-correction adapter or replace the source with a HEALPix-backed implementation. The internal `Map` is built once at construction; mutating the input array post-construction is not supported.
|
||||
27
packages/geospatial/geo/stars/src/catalog/catalog.test.ts
Normal file
27
packages/geospatial/geo/stars/src/catalog/catalog.test.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { describe, expect, it } from "@suaveplan/testing/runner";
|
||||
import { createInMemoryCatalogSource } from "./catalog.js";
|
||||
|
||||
describe("createInMemoryCatalogSource", () => {
|
||||
const stars = [
|
||||
{ catalogId: "S1", ra: 10, dec: 20, apparentMagnitude: 2 },
|
||||
{ catalogId: "S2", ra: 11, dec: 21, apparentMagnitude: 3 },
|
||||
];
|
||||
|
||||
it("queries cone", { timeout: 30000 }, async () => {
|
||||
const source = createInMemoryCatalogSource(stars);
|
||||
const results = await source.queryCone(10, 20, 2);
|
||||
expect(results.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("gets star by catalogId", { timeout: 30000 }, async () => {
|
||||
const source = createInMemoryCatalogSource(stars);
|
||||
const star = await source.getStar("S1");
|
||||
expect(star).not.toBeNull();
|
||||
expect(star!.catalogId).toBe("S1");
|
||||
});
|
||||
|
||||
it("returns null for missing catalogId", { timeout: 30000 }, async () => {
|
||||
const source = createInMemoryCatalogSource(stars);
|
||||
expect(await source.getStar("S3")).toBeNull();
|
||||
});
|
||||
});
|
||||
23
packages/geospatial/geo/stars/src/catalog/catalog.ts
Normal file
23
packages/geospatial/geo/stars/src/catalog/catalog.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import type { RawStar, StarCatalogSource } from "../types.js";
|
||||
|
||||
export function createInMemoryCatalogSource(
|
||||
stars: RawStar[]
|
||||
): StarCatalogSource {
|
||||
const map = new Map(stars.map((s) => [s.catalogId, s]));
|
||||
return {
|
||||
async queryCone(
|
||||
ra: number,
|
||||
dec: number,
|
||||
radiusDeg: number
|
||||
): Promise<RawStar[]> {
|
||||
return stars.filter((s) => {
|
||||
const dRa = s.ra - ra;
|
||||
const dDec = s.dec - dec;
|
||||
return Math.sqrt(dRa * dRa + dDec * dDec) <= radiusDeg;
|
||||
});
|
||||
},
|
||||
async getStar(catalogId: string): Promise<RawStar | null> {
|
||||
return map.get(catalogId) ?? null;
|
||||
},
|
||||
};
|
||||
}
|
||||
1
packages/geospatial/geo/stars/src/catalog/index.ts
Normal file
1
packages/geospatial/geo/stars/src/catalog/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { createInMemoryCatalogSource } from "./catalog.js";
|
||||
3
packages/geospatial/geo/stars/src/index.ts
Normal file
3
packages/geospatial/geo/stars/src/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export { createInMemoryCatalogSource } from "./catalog/index.js";
|
||||
export { createStarProvider, getVisibleStars } from "./provider/index.js";
|
||||
export type { RawStar, StarCatalogSource } from "./types.js";
|
||||
4
packages/geospatial/geo/stars/src/provider/index.ts
Normal file
4
packages/geospatial/geo/stars/src/provider/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export {
|
||||
createStarProvider,
|
||||
getVisibleStars,
|
||||
} from "./provider.js";
|
||||
57
packages/geospatial/geo/stars/src/provider/provider.md
Normal file
57
packages/geospatial/geo/stars/src/provider/provider.md
Normal file
@@ -0,0 +1,57 @@
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
The provider module composes one or more `StarCatalogSource` instances into a single `StarProvider` (from `@suaveplan/types`) that consumers — sky renderers, planetarium UIs, telescope planners — can program against without binding to a specific catalog backend. It implements the cross-source fallback semantics expected by the rest of the geo family.
|
||||
|
||||
It also exposes a low-level `getVisibleStars` helper that returns `RawStar` records filtered by apparent magnitude, intended for diagnostic tools and tests that operate below the `StarProvider` abstraction.
|
||||
|
||||
## Features
|
||||
|
||||
- Multi-source aggregation with first-non-empty fallback for `queryCone`.
|
||||
- First-hit short-circuit for `getStar` across sources.
|
||||
- `getVisibleStars` projects every queried star into an `ObservedStar` with placeholder altitude/azimuth/extincted-magnitude fields suitable for the contract surface.
|
||||
- Total support for the optional `RawStar` columns (`spectralClass`, `distance`, `properMotionRa`, `properMotionDec`, `parallax`) — present fields are forwarded, absent ones omitted under `exactOptionalPropertyTypes`.
|
||||
- Pure functions with no global state and no I/O beyond delegated source calls.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { createInMemoryCatalogSource, createStarProvider } from "@suaveplan/stars";
|
||||
|
||||
const source = createInMemoryCatalogSource([
|
||||
{ catalogId: "HIP-32349", ra: 101.287, dec: -16.716, apparentMagnitude: -1.46 },
|
||||
]);
|
||||
const provider = createStarProvider([source]);
|
||||
const stars = await provider.queryCone(101, -16, 5);
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { createInMemoryCatalogSource, createStarProvider, getVisibleStars } from "@suaveplan/stars";
|
||||
|
||||
const primary = createInMemoryCatalogSource([]);
|
||||
const fallback = createInMemoryCatalogSource([
|
||||
{ catalogId: "HIP-37279", ra: 114.825, dec: 5.225, apparentMagnitude: 0.4 },
|
||||
{ catalogId: "HIP-91262", ra: 279.234, dec: 38.784, apparentMagnitude: 0.03 },
|
||||
]);
|
||||
const provider = createStarProvider([primary, fallback]);
|
||||
const visible = await provider.getVisibleStars({
|
||||
kind: "surface",
|
||||
planet: { name: "Earth", radius: 6_371_000, rotationPeriod: 86_164 },
|
||||
coord: { latitude: 51.4779, longitude: -0.0015 },
|
||||
});
|
||||
const bright = await getVisibleStars([fallback], { latitude: 0, longitude: 0 }, 0, 1);
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `createStarProvider(sources: StarCatalogSource[]): StarProvider` — returns a `StarProvider` whose `queryCone(ra, dec, radiusDeg)` walks `sources` in order and returns the first non-empty result mapped to `CatalogStar`; whose `getStar(catalogId)` returns the first match across sources or `null`; and whose `getVisibleStars(observer)` returns every star across all sources projected to `ObservedStar`.
|
||||
- `getVisibleStars(sources, observer, minAltitudeDeg?, maxMagnitude?): Promise<RawStar[]>` — collects raw stars from every source via a 90-degree cone at the celestial origin and filters by `maxMagnitude` (default 6). `observer` and `minAltitudeDeg` are accepted for forward-compatibility but currently unused.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
`toCatalogStar` uses guarded `Object.assign` for optional fields so undefined values do not leak as own properties — this matters under `exactOptionalPropertyTypes: true`. `toObservedStar` returns hard-coded altitude/azimuth values; this is a contract-only shim and a real implementation should plug in horizontal-coordinate transforms from `@suaveplan/geo`. `queryCone` short-circuits on the first non-empty source, which is appropriate for layered hot/cold caches but wrong for true union semantics; if you need union behaviour, compose sources upstream.
|
||||
136
packages/geospatial/geo/stars/src/provider/provider.test.ts
Normal file
136
packages/geospatial/geo/stars/src/provider/provider.test.ts
Normal file
@@ -0,0 +1,136 @@
|
||||
import { describe, expect, it } from "@suaveplan/testing/runner";
|
||||
import { createInMemoryCatalogSource } from "../catalog/catalog.js";
|
||||
import { createStarProvider, getVisibleStars } from "./provider.js";
|
||||
|
||||
describe("createStarProvider", () => {
|
||||
it("returns stars from source via queryCone", {
|
||||
timeout: 30000,
|
||||
}, async () => {
|
||||
const source = createInMemoryCatalogSource([
|
||||
{ catalogId: "S1", ra: 10, dec: 20, apparentMagnitude: 2 },
|
||||
]);
|
||||
const provider = createStarProvider([source]);
|
||||
const stars = await provider.queryCone(10, 20, 2);
|
||||
expect(stars.length).toBe(1);
|
||||
expect(stars[0]!.catalogId).toBe("S1");
|
||||
});
|
||||
|
||||
it("returns empty when no sources match", { timeout: 30000 }, async () => {
|
||||
const provider = createStarProvider([]);
|
||||
const stars = await provider.queryCone(0, 0, 1);
|
||||
expect(stars).toEqual([]);
|
||||
});
|
||||
|
||||
it("falls back to next source when first is empty", {
|
||||
timeout: 30000,
|
||||
}, async () => {
|
||||
const empty = createInMemoryCatalogSource([]);
|
||||
const source = createInMemoryCatalogSource([
|
||||
{ catalogId: "S1", ra: 10, dec: 20, apparentMagnitude: 2 },
|
||||
]);
|
||||
const provider = createStarProvider([empty, source]);
|
||||
const stars = await provider.queryCone(10, 20, 2);
|
||||
expect(stars.length).toBe(1);
|
||||
});
|
||||
|
||||
it("getStar returns a catalog star", { timeout: 30000 }, async () => {
|
||||
const source = createInMemoryCatalogSource([
|
||||
{ catalogId: "S1", ra: 10, dec: 20, apparentMagnitude: 2 },
|
||||
]);
|
||||
const provider = createStarProvider([source]);
|
||||
const star = await provider.getStar("S1");
|
||||
expect(star).not.toBeNull();
|
||||
expect(star!.catalogId).toBe("S1");
|
||||
});
|
||||
|
||||
it("getStar returns null when not found", { timeout: 30000 }, async () => {
|
||||
const provider = createStarProvider([]);
|
||||
expect(await provider.getStar("X")).toBeNull();
|
||||
});
|
||||
|
||||
it("getStar falls through to next source when first returns null", {
|
||||
timeout: 30000,
|
||||
}, async () => {
|
||||
// Exercises the falsy branch of `if (star)` in getStar: first
|
||||
// source has no match, second source supplies the result.
|
||||
const empty = createInMemoryCatalogSource([]);
|
||||
const populated = createInMemoryCatalogSource([
|
||||
{ catalogId: "S2", ra: 5, dec: 5, apparentMagnitude: 3 },
|
||||
]);
|
||||
const provider = createStarProvider([empty, populated]);
|
||||
const star = await provider.getStar("S2");
|
||||
expect(star).not.toBeNull();
|
||||
expect(star!.catalogId).toBe("S2");
|
||||
});
|
||||
|
||||
it("toCatalogStar preserves every optional field when present", {
|
||||
timeout: 30000,
|
||||
}, async () => {
|
||||
// Exercises the five `if (s.X !== undefined)` branches in
|
||||
// toCatalogStar so each optional field copies through.
|
||||
const source = createInMemoryCatalogSource([
|
||||
{
|
||||
catalogId: "S1",
|
||||
ra: 10,
|
||||
dec: 20,
|
||||
apparentMagnitude: 2,
|
||||
spectralClass: "G2V",
|
||||
distance: 1.0,
|
||||
properMotionRa: 0.001,
|
||||
properMotionDec: -0.002,
|
||||
parallax: 0.05,
|
||||
},
|
||||
]);
|
||||
const provider = createStarProvider([source]);
|
||||
const star = await provider.getStar("S1");
|
||||
expect(star).not.toBeNull();
|
||||
expect(star).toMatchObject({
|
||||
catalogId: "S1",
|
||||
ra: 10,
|
||||
dec: 20,
|
||||
apparentMagnitude: 2,
|
||||
spectralClass: "G2V",
|
||||
distance: 1.0,
|
||||
properMotionRa: 0.001,
|
||||
properMotionDec: -0.002,
|
||||
parallax: 0.05,
|
||||
});
|
||||
});
|
||||
|
||||
it("getVisibleStars returns observed stars", {
|
||||
timeout: 30000,
|
||||
}, async () => {
|
||||
const source = createInMemoryCatalogSource([
|
||||
{ catalogId: "S1", ra: 10, dec: 20, apparentMagnitude: 2 },
|
||||
]);
|
||||
const provider = createStarProvider([source]);
|
||||
const stars = await provider.getVisibleStars({
|
||||
kind: "surface",
|
||||
planet: {
|
||||
name: "Earth",
|
||||
radius: 6_371_000,
|
||||
rotationPeriod: 86_164,
|
||||
},
|
||||
coord: { latitude: 0, longitude: 0 },
|
||||
});
|
||||
expect(stars.length).toBeGreaterThan(0);
|
||||
expect(stars[0]!).toHaveProperty("altitude");
|
||||
expect(stars[0]!).toHaveProperty("azimuth");
|
||||
expect(stars[0]!).toHaveProperty("extinctedMagnitude");
|
||||
});
|
||||
});
|
||||
|
||||
describe("getVisibleStars", () => {
|
||||
it("filters by magnitude", { timeout: 30000 }, async () => {
|
||||
const source = createInMemoryCatalogSource([
|
||||
{ catalogId: "S1", ra: 0, dec: 0, apparentMagnitude: 2 },
|
||||
{ catalogId: "S2", ra: 0, dec: 0, apparentMagnitude: 8 },
|
||||
]);
|
||||
const stars = await getVisibleStars([source], {
|
||||
latitude: 0,
|
||||
longitude: 0,
|
||||
});
|
||||
expect(stars.length).toBe(1);
|
||||
expect(stars[0]!.catalogId).toBe("S1");
|
||||
});
|
||||
});
|
||||
83
packages/geospatial/geo/stars/src/provider/provider.ts
Normal file
83
packages/geospatial/geo/stars/src/provider/provider.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import type { CatalogStar, ObservedStar, StarProvider } from "@suaveplan/types";
|
||||
import type { RawStar, StarCatalogSource } from "../types.js";
|
||||
|
||||
function toCatalogStar(s: RawStar): CatalogStar {
|
||||
const star: CatalogStar = {
|
||||
catalogId: s.catalogId,
|
||||
ra: s.ra,
|
||||
dec: s.dec,
|
||||
apparentMagnitude: s.apparentMagnitude,
|
||||
};
|
||||
if (s.spectralClass !== undefined) {
|
||||
Object.assign(star, { spectralClass: s.spectralClass });
|
||||
}
|
||||
if (s.distance !== undefined) {
|
||||
Object.assign(star, { distance: s.distance });
|
||||
}
|
||||
if (s.properMotionRa !== undefined) {
|
||||
Object.assign(star, { properMotionRa: s.properMotionRa });
|
||||
}
|
||||
if (s.properMotionDec !== undefined) {
|
||||
Object.assign(star, { properMotionDec: s.properMotionDec });
|
||||
}
|
||||
if (s.parallax !== undefined) {
|
||||
Object.assign(star, { parallax: s.parallax });
|
||||
}
|
||||
return star;
|
||||
}
|
||||
|
||||
function toObservedStar(s: RawStar): ObservedStar {
|
||||
return {
|
||||
...toCatalogStar(s),
|
||||
altitude: 45,
|
||||
azimuth: 180,
|
||||
extinctedMagnitude: s.apparentMagnitude,
|
||||
};
|
||||
}
|
||||
|
||||
export function createStarProvider(sources: StarCatalogSource[]): StarProvider {
|
||||
return {
|
||||
async getVisibleStars(): Promise<ObservedStar[]> {
|
||||
const all: ObservedStar[] = [];
|
||||
for (const source of sources) {
|
||||
const stars = await source.queryCone(0, 0, 180);
|
||||
all.push(...stars.map(toObservedStar));
|
||||
}
|
||||
return all;
|
||||
},
|
||||
async queryCone(
|
||||
ra: number,
|
||||
dec: number,
|
||||
radiusDeg: number
|
||||
): Promise<CatalogStar[]> {
|
||||
for (const source of sources) {
|
||||
const stars = await source.queryCone(ra, dec, radiusDeg);
|
||||
if (stars.length > 0) {
|
||||
return stars.map(toCatalogStar);
|
||||
}
|
||||
}
|
||||
return [];
|
||||
},
|
||||
async getStar(catalogId: string): Promise<CatalogStar | null> {
|
||||
for (const source of sources) {
|
||||
const star = await source.getStar(catalogId);
|
||||
if (star) return toCatalogStar(star);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export async function getVisibleStars(
|
||||
sources: StarCatalogSource[],
|
||||
_observer: { latitude: number; longitude: number },
|
||||
_minAltitudeDeg = 0,
|
||||
_maxMagnitude = 6
|
||||
): Promise<RawStar[]> {
|
||||
const all: RawStar[] = [];
|
||||
for (const source of sources) {
|
||||
const stars = await source.queryCone(0, 0, 90);
|
||||
all.push(...stars);
|
||||
}
|
||||
return all.filter((s) => s.apparentMagnitude <= _maxMagnitude);
|
||||
}
|
||||
48
packages/geospatial/geo/stars/src/types.md
Normal file
48
packages/geospatial/geo/stars/src/types.md
Normal file
@@ -0,0 +1,48 @@
|
||||
## Purpose
|
||||
|
||||
The types module declares the two foundational shapes that `@suaveplan/stars` is built on: `RawStar`, the lossless catalog record returned by every source, and `StarCatalogSource`, the source contract that providers compose. They sit deliberately one layer below `@suaveplan/types`'s `CatalogStar` / `ObservedStar` / `StarProvider` so that catalog-backend authors have a stable, narrowly-scoped surface to implement against.
|
||||
|
||||
Keeping these types local to the `stars` package — instead of in `@suaveplan/types` — avoids forcing every downstream universe-tier consumer to take on the source-adapter shape, while still letting the provider layer translate cleanly into the public `StarProvider` contract.
|
||||
|
||||
## Features
|
||||
|
||||
- `RawStar` carries the required astrometry (`catalogId`, `ra`, `dec`, `apparentMagnitude`) plus optional photometric and motion columns (`spectralClass`, `distance`, `properMotionRa`, `properMotionDec`, `parallax`).
|
||||
- `StarCatalogSource` is a structural interface — any object meeting the shape works, no class inheritance required.
|
||||
- Async-by-default methods so HTTP-, file-, and DB-backed sources fit without wrapping.
|
||||
- No runtime cost; the module is types-only and erased at build time.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import type { RawStar, StarCatalogSource } from "@suaveplan/stars";
|
||||
|
||||
const star: RawStar = { catalogId: "HIP-32349", ra: 101.287, dec: -16.716, apparentMagnitude: -1.46 };
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import type { RawStar, StarCatalogSource } from "@suaveplan/stars";
|
||||
|
||||
class HttpCatalogSource implements StarCatalogSource {
|
||||
constructor(private readonly endpoint: string) {}
|
||||
async queryCone(ra: number, dec: number, radiusDeg: number): Promise<RawStar[]> {
|
||||
const url = `${this.endpoint}/cone?ra=${ra}&dec=${dec}&r=${radiusDeg}`;
|
||||
const res = await fetch(url);
|
||||
return (await res.json()) as RawStar[];
|
||||
}
|
||||
async getStar(catalogId: string): Promise<RawStar | null> {
|
||||
const res = await fetch(`${this.endpoint}/star/${encodeURIComponent(catalogId)}`);
|
||||
return res.ok ? ((await res.json()) as RawStar) : null;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `RawStar` — `{ catalogId: string; ra: number; dec: number; apparentMagnitude: number; spectralClass?: string; distance?: number; properMotionRa?: number; properMotionDec?: number; parallax?: number }`. Right ascension and declination are degrees (J2000); apparent magnitude is the visual band; distance is parsecs; proper motions are mas/yr; parallax is mas.
|
||||
- `StarCatalogSource` — `{ queryCone(ra, dec, radiusDeg): Promise<RawStar[]>; getStar(catalogId): Promise<RawStar | null> }`. Implementations must be referentially safe (no shared mutable result arrays) and must not throw for missing ids — return `null` instead.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
`RawStar` intentionally omits derived horizontal coordinates and extincted magnitudes — those belong on `ObservedStar` and are computed by the provider layer against an observer pose. The optional fields use `T | undefined` rather than nullable so authors can `Object.assign` only when present (see `provider.ts`), which keeps records small and TypeScript happy under `exactOptionalPropertyTypes`.
|
||||
16
packages/geospatial/geo/stars/src/types.ts
Normal file
16
packages/geospatial/geo/stars/src/types.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
export interface RawStar {
|
||||
catalogId: string;
|
||||
ra: number;
|
||||
dec: number;
|
||||
apparentMagnitude: number;
|
||||
spectralClass?: string;
|
||||
distance?: number;
|
||||
properMotionRa?: number;
|
||||
properMotionDec?: number;
|
||||
parallax?: number;
|
||||
}
|
||||
|
||||
export interface StarCatalogSource {
|
||||
queryCone(ra: number, dec: number, radiusDeg: number): Promise<RawStar[]>;
|
||||
getStar(catalogId: string): Promise<RawStar | null>;
|
||||
}
|
||||
12
packages/geospatial/geo/stars/tsconfig.lib.json
Normal file
12
packages/geospatial/geo/stars/tsconfig.lib.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"extends": "../../../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./dist",
|
||||
"lib": ["ES2022"],
|
||||
"types": ["node"]
|
||||
},
|
||||
"include": ["src/**/*.ts"],
|
||||
"exclude": ["src/**/*.test.ts", "src/**/*.bench.ts", "dist", "node_modules"]
|
||||
}
|
||||
25
packages/geospatial/geo/stars/vite.config.ts
Normal file
25
packages/geospatial/geo/stars/vite.config.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { defineConfig } from "vite";
|
||||
import dts from "vite-plugin-dts";
|
||||
|
||||
export default defineConfig({
|
||||
build: {
|
||||
lib: {
|
||||
entry: {
|
||||
index: "./src/index.ts",
|
||||
"provider/provider": "./src/provider/provider.ts",
|
||||
"catalog/catalog": "./src/catalog/catalog.ts",
|
||||
},
|
||||
formats: ["es"],
|
||||
},
|
||||
outDir: "dist",
|
||||
sourcemap: true,
|
||||
rollupOptions: {
|
||||
external: [/^@suaveplan\//, "zod", /^@opentelemetry\//],
|
||||
output: {
|
||||
preserveModules: true,
|
||||
preserveModulesRoot: "src",
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [dts({ tsconfigPath: "./tsconfig.lib.json" })],
|
||||
});
|
||||
10
packages/geospatial/geo/stars/vitest.config.ts
Normal file
10
packages/geospatial/geo/stars/vitest.config.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { createTestConfig } from "@suaveplan/testing/config";
|
||||
|
||||
export default createTestConfig({
|
||||
coverageThresholds: {
|
||||
statements: 100,
|
||||
branches: 100,
|
||||
functions: 100,
|
||||
lines: 100,
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user