diff --git a/.junie/plans/nasa-star-map.md b/.junie/plans/nasa-star-map.md index d09ce69..beab061 100644 --- a/.junie/plans/nasa-star-map.md +++ b/.junie/plans/nasa-star-map.md @@ -108,12 +108,23 @@ interface ExoplanetRecord { orbit: Partial; } +// Revised during implementation: OpenNGC publishes no distance column, and the redshift/ +// parallax fallbacks both fail for the best-known objects (M31/M33/M42 are Local Group +// members with negative or absent redshift; a galaxy's catalog parallax comes from a +// cross-matched foreground star). The line of sight is always known, so these are stored as +// unit directions and drawn on a fixed backdrop shell, with distance as optional metadata. +// See `shared/models/deepsky.model.ts`. interface DeepSkyRecord { id: string; name: string; kind: 'nebula' | 'galaxy' | 'cluster'; - x: number; y: number; z: number; // parsecs + x: number; y: number; z: number; // unit vector on the celestial sphere, not a position angularSizeDeg: number; + magnitude: number | null; + distancePc: number | null; + distanceMethod: 'parallax' | 'redshift' | null; + constellation: string; + messier: string | null; } ``` diff --git a/README.md b/README.md index 2a47299..cd30541 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,124 @@ # star-map -Personal marketplace of `cs:*` Claude Code commands, agents, and skills. +An interactive 3D star map in the spirit of Star Citizen's in-game starmap, but populated with +real astronomical data instead of fictional systems. Browse the solar neighbourhood, fly into a +star's system to see its planets on their real orbits, and drill into a single body for the +NASA figures behind it. -## Install (global — works in every project) +This repo also hosts a small Claude Code plugin marketplace — see [Plugins](#plugins) below. -This repo is a Claude Code plugin marketplace. Adding it and installing a -plugin defaults to **user scope**, meaning the plugin becomes available in -*every* project on your machine, not just the one you happen to be in: +## Running it + +```bash +npm install +npm start # dev server on http://localhost:4200 +npm run build # production bundle into dist/ +``` + +Requires the Node version in `package.json`'s Angular toolchain range (Node 22.22.3+ or 24.15+). + +```bash +npm test # unit/component tests (Vitest, jsdom) +npm run e2e # end-to-end tests (Playwright + Chromium) — see e2e/README.md +npm run etl # refresh the astronomical datasets — see below +npm run etl:typecheck # type-check the ETL scripts (they build separately from the app) +npm run e2e:typecheck +``` + +## What's in it + +**Galaxy view** — every HYG-catalogue star within 50 parsecs as a point field, positioned from +real RA/Dec/parallax, coloured by spectral index and sized by magnitude. Names label the stars +nearest the camera. Behind them sits a backdrop of notable deep-sky objects and a Milky Way +panorama. + +**System view** — selecting a star flies the camera continuously into its system rather than +cutting to a new scene. The Sun gets the real solar-system bodies from JPL Horizons; other +stars get their confirmed exoplanets. Orbits are drawn as ellipses and bodies are propagated +along them by a Kepler solver against the current epoch. + +**Body detail** — a dedicated close-up scene and info panel for one planet, moon or exoplanet, +with real photography where NASA/ESA/USGS imagery exists. + +**Search** — name search across stars, solar-system bodies and exoplanets, navigating to the +same place an in-scene click would. + +### Architecture notes + +- **Rendering** runs on Three.js `WebGPURenderer`, which falls back to a WebGL2 backend + automatically. The render loop runs outside Angular's change detection. +- **Two coordinate scales.** The galaxy view works in parsecs and the system view in AU — + about eight orders of magnitude apart, which wrecks float precision if rendered in one unit + space. The camera rig recentres the active star to the origin ("floating origin") and swaps + the unit scale and near/far planes at the transition point. +- **No backend.** Every dataset is baked at build time into `src/assets/data/` and served as a + static asset. Nothing queries an astronomy API at runtime. + +## Data pipeline + +`npm run etl` runs `tools/etl/build.ts`, which fetches each source, writes the static assets, +then validates the combined output. Raw responses are cached under `tools/etl/.cache/`, so +re-runs are cheap and offline-friendly; set `ETL_FORCE_REFRESH=1` to bypass the cache. + +| Script | Source | Output | +| --- | --- | --- | +| `fetchStars.ts` | HYG database (Hipparcos/Yale/Gliese) | `stars.bin`, `stars-index.json` | +| `fetchSolarSystem.ts` | JPL Horizons / SSD | `bodies.json` | +| `fetchExoplanets.ts` | NASA Exoplanet Archive (TAP) | `exoplanets.json` | +| `fetchDeepSky.ts` | OpenNGC | `deepsky.json` | + +Star positions ship as a packed `Float32Array` (`stars.bin`) rather than JSON to keep the +initial payload and parse cost down; `stars-index.json` carries everything else in the same +order. + +`ETL_STAR_DISTANCE_PC` (default `50`) sets the star-field distance cutoff. + +### On deep-sky distances + +OpenNGC publishes no distance column, so distance has to be inferred — and the inference fails +for precisely the best-known objects. M31, M33 and M42 are Local Group members whose redshift +is negative or absent, and the catalogue's parallax for a galaxy comes from a cross-matched +foreground star (it lists 6 mas for M31, implying 167 pc for something 780,000 pc away). + +So deep-sky records store a **unit direction** on the celestial sphere rather than a position: +the line of sight is always known precisely, and the objects are drawn as a fixed-radius +backdrop shell where true distance would be unusable anyway. `distancePc` is optional metadata, +derived from parallax for galactic objects or the Hubble law for genuinely distant galaxies, +and left `null` — with its `distanceMethod` — whenever neither is trustworthy. Roughly 330 of +the 463 cataloged objects get a distance; the rest honestly report none. + +## Layout + +``` +src/app/ + core/engine/ Three.js renderer, render loop, resize + core/data/ static-asset loading and caching + features/galaxy-system/ shared galaxy+system scene, camera rig, star field, + deep-sky backdrop, orbits, labels + features/body-detail/ close-up scene and info panel + features/search/ name search across every dataset + shared/astro/ coordinates, Kepler propagator, deep-sky classification + shared/models/ record contracts shared by the app and the ETL + shared/rendering/ skybox, glow sprites, texture catalog + shared/state/ navigation store (Angular signals) +tools/etl/ build-time data pipeline +e2e/ Playwright end-to-end tests +``` + +The design document behind all of this is `.junie/plans/nasa-star-map.md`. + +## Plugins + +This repo doubles as a Claude Code plugin marketplace. Adding it and installing a plugin +defaults to **user scope**, meaning the plugin becomes available in *every* project on your +machine, not just the one you happen to be in: ```bash /plugin marketplace add avalon-vanguard/star-map /plugin install caveman@star-map ``` -Scope can be overridden at install time if you want it tied to a single -repo instead: +Scope can be overridden at install time if you want it tied to a single repo instead: ```bash # Shared with collaborators via that repo's .claude/settings.json @@ -27,9 +131,15 @@ repo instead: See [Claude Code plugin installation scopes](https://code.claude.com/docs/en/plugins-reference) for details on `user` / `project` / `local` scope. -## Plugins - - **caveman** — `/cs:caveman` ultra-compressed communication mode. - Command: [`commands/cs/caveman.md`](commands/cs/caveman.md) - Agent: [`agents/cs-caveman-mode.md`](agents/cs-caveman-mode.md) - - Skill: [`skills/caveman/SKILL.md`](skills/caveman/SKILL.md) \ No newline at end of file + - Skill: [`skills/caveman/SKILL.md`](skills/caveman/SKILL.md) + +## Data credits + +Star catalogue: [HYG database](https://github.com/astronexus/HYG-Database) (Hipparcos, Yale +Bright Star, Gliese). Solar-system ephemerides: NASA/JPL Horizons. Exoplanets: NASA Exoplanet +Archive. Deep-sky objects: [OpenNGC](https://github.com/mattiaverga/OpenNGC). Body and skybox +imagery: NASA/JPL/USGS public domain and Solar System Scope (CC BY 4.0) — per-file provenance +is recorded in `src/app/shared/rendering/texture-catalog.ts`. diff --git a/e2e/README.md b/e2e/README.md index 7cc1157..1261ed8 100644 --- a/e2e/README.md +++ b/e2e/README.md @@ -25,6 +25,8 @@ server a developer might already have running there. exactly where the default galaxy-view camera looks. `camera-flight.spec.ts` relies on this to reliably click-select it by clicking the center of the canvas, without needing pixel-perfect knowledge of the star field's on-screen layout. -- Data (bootstrap fetch of `stars.bin`/`stars-index.json`/`bodies.json`/`exoplanets.json`) loads - asynchronously after the page loads, so tests poll (re-click/re-check) rather than assume the - scene is interactive immediately after `page.goto()`. +- Data (bootstrap fetch of `stars.bin`/`stars-index.json`/`bodies.json`/`exoplanets.json`/ + `deepsky.json`) loads asynchronously after the page loads, so tests poll (re-click/re-check) + rather than assume the scene is interactive immediately after `page.goto()`. +- `deepsky.json` is the one dataset the scene treats as optional: it only feeds the decorative + backdrop, so a failure to load it is logged and the star field comes up regardless. diff --git a/src/app/core/data/data-loader.service.ts b/src/app/core/data/data-loader.service.ts index 838823b..1abf2a1 100644 --- a/src/app/core/data/data-loader.service.ts +++ b/src/app/core/data/data-loader.service.ts @@ -1,6 +1,7 @@ import { Injectable } from '@angular/core'; import { BodyRecord } from '../../shared/models/body.model'; +import { DeepSkyRecord } from '../../shared/models/deepsky.model'; import { ExoplanetRecord } from '../../shared/models/exoplanet.model'; import { StarRecord } from '../../shared/models/star.model'; @@ -19,6 +20,7 @@ export class DataLoaderService { private starFieldPromise?: Promise; private bodiesPromise?: Promise; private exoplanetsPromise?: Promise; + private deepSkyPromise?: Promise; loadStars(): Promise { this.starFieldPromise ??= this.fetchStars(); @@ -35,6 +37,11 @@ export class DataLoaderService { return this.exoplanetsPromise; } + loadDeepSky(): Promise { + this.deepSkyPromise ??= this.fetchJson('assets/data/deepsky.json'); + return this.deepSkyPromise; + } + private async fetchStars(): Promise { const [stars, buffer] = await Promise.all([ fetch('assets/data/stars-index.json').then((response) => response.json() as Promise), diff --git a/src/app/features/galaxy-system/deep-sky-renderer.spec.ts b/src/app/features/galaxy-system/deep-sky-renderer.spec.ts new file mode 100644 index 0000000..5a953b3 --- /dev/null +++ b/src/app/features/galaxy-system/deep-sky-renderer.spec.ts @@ -0,0 +1,214 @@ +import * as THREE from 'three/webgpu'; +import { describe, expect, it } from 'vitest'; + +import { DeepSkyRecord } from '../../shared/models/deepsky.model'; +import { backdropPosition, backdropSpriteSizePc, BACKDROP_RADIUS_PC, brightnessBandIndex, deepSkyLabelPoints, DeepSkyRenderer } from './deep-sky-renderer'; + +function record(overrides: Partial = {}): DeepSkyRecord { + return { + id: 'NGC0224', + name: 'Andromeda Galaxy', + kind: 'galaxy', + x: 0, + y: 0, + z: 1, + angularSizeDeg: 2.96, + magnitude: 3.44, + distancePc: null, + distanceMethod: null, + constellation: 'And', + messier: 'M31', + ...overrides + }; +} + +describe('backdropPosition', () => { + it('pushes the direction out to the shell radius', () => { + const position = backdropPosition(record({ x: 0, y: 0, z: 1 })); + expect(position.z).toBeCloseTo(BACKDROP_RADIUS_PC, 9); + expect(position.length()).toBeCloseTo(BACKDROP_RADIUS_PC, 9); + }); + + it('preserves direction for an off-axis object', () => { + const direction = new THREE.Vector3(0.3, -0.5, 0.81).normalize(); + const position = backdropPosition(record({ x: direction.x, y: direction.y, z: direction.z })); + + expect(position.length()).toBeCloseTo(BACKDROP_RADIUS_PC, 6); + expect(position.clone().normalize().dot(direction)).toBeCloseTo(1, 9); + }); + + it('honours an explicit radius', () => { + expect(backdropPosition(record(), 100).length()).toBeCloseTo(100, 9); + }); + + it('lands inside the galaxy camera frustum from anywhere on its orbit', () => { + // The camera orbits at most 2000 pc out and its far plane is 5000 pc, so the far side of + // the shell has to stay within reach or the backdrop would be clipped away. + expect(BACKDROP_RADIUS_PC).toBeGreaterThan(2000); + expect(BACKDROP_RADIUS_PC + 2000).toBeLessThan(5000); + }); +}); + +describe('backdropSpriteSizePc', () => { + it('scales with true angular size', () => { + const small = backdropSpriteSizePc(record({ angularSizeDeg: 1 })); + const large = backdropSpriteSizePc(record({ angularSizeDeg: 2 })); + expect(large).toBeGreaterThan(small); + }); + + it('reproduces the real angular size in the unclamped range', () => { + // 2 degrees at the shell radius: r * theta. + const expected = BACKDROP_RADIUS_PC * 2 * (Math.PI / 180); + expect(backdropSpriteSizePc(record({ angularSizeDeg: 2 }))).toBeCloseTo(expected, 6); + }); + + it('floors sub-arcminute objects so they stay visible', () => { + const tiny = backdropSpriteSizePc(record({ angularSizeDeg: 0 })); + expect(tiny).toBeGreaterThan(0); + expect(tiny).toBe(backdropSpriteSizePc(record({ angularSizeDeg: 0.001 }))); + }); + + it('caps very extended objects so they cannot blanket the view', () => { + const huge = backdropSpriteSizePc(record({ angularSizeDeg: 90 })); + const larger = backdropSpriteSizePc(record({ angularSizeDeg: 180 })); + expect(huge).toBe(larger); + }); +}); + +describe('brightnessBandIndex', () => { + it('puts the brightest objects in the most opaque band', () => { + expect(brightnessBandIndex(3.44)).toBe(0); + }); + + it('separates mid and faint objects into later bands', () => { + expect(brightnessBandIndex(6)).toBe(1); + expect(brightnessBandIndex(9)).toBe(2); + }); + + it('is monotonic in magnitude', () => { + const bands = [0, 3, 5, 6, 7.5, 9, 14].map(brightnessBandIndex); + expect([...bands].sort((a, b) => a - b)).toEqual(bands); + }); + + it('treats an unphotometered object as faintest rather than brightest', () => { + expect(brightnessBandIndex(null)).toBe(brightnessBandIndex(99)); + }); +}); + +describe('deepSkyLabelPoints', () => { + const records = [record({ id: 'a', name: 'A' }), record({ id: 'b', name: 'B' }), record({ id: 'c', name: 'C' })]; + + it('takes a prefix of the (magnitude-sorted) records', () => { + expect(deepSkyLabelPoints(records, 2).map((point) => point.id)).toEqual(['a', 'b']); + }); + + it('anchors each label on the backdrop shell', () => { + const [point] = deepSkyLabelPoints(records, 1); + expect(Math.hypot(point.x, point.y, point.z)).toBeCloseTo(BACKDROP_RADIUS_PC, 6); + }); + + it('carries the display name and the catalog id', () => { + const [point] = deepSkyLabelPoints(records, 1); + expect(point).toMatchObject({ id: 'a', name: 'A' }); + }); + + it('never returns more labels than there are records', () => { + expect(deepSkyLabelPoints(records, 99)).toHaveLength(3); + expect(deepSkyLabelPoints([], 5)).toEqual([]); + }); +}); + +describe('DeepSkyRenderer', () => { + it('adds one sprite per record', () => { + const renderer = new DeepSkyRenderer([record({ id: 'a' }), record({ id: 'b' })]); + expect(renderer.object.children).toHaveLength(2); + expect(renderer.object.children.every((child) => child instanceof THREE.Sprite)).toBe(true); + renderer.dispose(); + }); + + it('positions and scales each sprite from its record', () => { + const only = record({ angularSizeDeg: 2 }); + const renderer = new DeepSkyRenderer([only]); + const sprite = renderer.object.children[0] as THREE.Sprite; + + expect(sprite.position.length()).toBeCloseTo(BACKDROP_RADIUS_PC, 6); + expect(sprite.scale.x).toBeCloseTo(backdropSpriteSizePc(only), 6); + renderer.dispose(); + }); + + it('shares one material across objects of the same kind and brightness', () => { + const renderer = new DeepSkyRenderer([ + record({ id: 'a', kind: 'galaxy', magnitude: 3 }), + record({ id: 'b', kind: 'galaxy', magnitude: 4 }) + ]); + const [first, second] = renderer.object.children as THREE.Sprite[]; + + expect(first.material).toBe(second.material); + renderer.dispose(); + }); + + it('gives different kinds different materials', () => { + const renderer = new DeepSkyRenderer([ + record({ id: 'a', kind: 'galaxy', magnitude: 3 }), + record({ id: 'b', kind: 'nebula', magnitude: 3 }), + record({ id: 'c', kind: 'cluster', magnitude: 3 }) + ]); + const materials = new Set((renderer.object.children as THREE.Sprite[]).map((sprite) => sprite.material)); + + expect(materials.size).toBe(3); + renderer.dispose(); + }); + + it('gives different brightness bands different materials', () => { + const renderer = new DeepSkyRenderer([ + record({ id: 'a', kind: 'galaxy', magnitude: 3 }), + record({ id: 'b', kind: 'galaxy', magnitude: 9 }) + ]); + const [bright, faint] = renderer.object.children as THREE.Sprite[]; + + expect(bright.material).not.toBe(faint.material); + expect(bright.material.opacity).toBeGreaterThan(faint.material.opacity); + renderer.dispose(); + }); + + it('keeps the material count bounded no matter how many objects there are', () => { + const many = Array.from({ length: 200 }, (_, index) => + record({ id: `obj-${index}`, kind: (['galaxy', 'nebula', 'cluster'] as const)[index % 3], magnitude: index % 12 }) + ); + const renderer = new DeepSkyRenderer(many); + const materials = new Set((renderer.object.children as THREE.Sprite[]).map((sprite) => sprite.material)); + + expect(renderer.object.children).toHaveLength(200); + // Three kinds x three brightness bands. + expect(materials.size).toBeLessThanOrEqual(9); + renderer.dispose(); + }); + + it('renders behind the star field', () => { + const renderer = new DeepSkyRenderer([record()]); + expect(renderer.object.renderOrder).toBeLessThan(0); + renderer.dispose(); + }); + + it('disposes its materials and empties the group', () => { + const renderer = new DeepSkyRenderer([record({ id: 'a' }), record({ id: 'b', kind: 'nebula' })]); + const materials = (renderer.object.children as THREE.Sprite[]).map((sprite) => sprite.material); + const disposed = materials.map((material) => { + let seen = false; + material.addEventListener('dispose', () => (seen = true)); + return () => seen; + }); + + renderer.dispose(); + + expect(renderer.object.children).toHaveLength(0); + expect(disposed.every((wasDisposed) => wasDisposed())).toBe(true); + }); + + it('handles an empty catalog', () => { + const renderer = new DeepSkyRenderer([]); + expect(renderer.object.children).toHaveLength(0); + expect(renderer.labelPoints(5)).toEqual([]); + renderer.dispose(); + }); +}); diff --git a/src/app/features/galaxy-system/deep-sky-renderer.ts b/src/app/features/galaxy-system/deep-sky-renderer.ts new file mode 100644 index 0000000..104d8c5 --- /dev/null +++ b/src/app/features/galaxy-system/deep-sky-renderer.ts @@ -0,0 +1,151 @@ +import * as THREE from 'three/webgpu'; + +import { DeepSkyKind, DeepSkyRecord } from '../../shared/models/deepsky.model'; +import { createGlowTexture } from '../../shared/rendering/skybox'; +import { LabeledPoint } from './star-label-overlay'; + +/** + * Radius (parsecs) of the shell the backdrop is painted on. + * + * Chosen to sit clear of everything else the galaxy camera deals with: well outside the 50 pc + * star field, beyond the camera's 2000 pc orbit limit so it can never be flown through, and + * close enough that even the far side of the shell (2000 + 2500 = 4500 pc) stays inside the + * 5000 pc far plane rather than being clipped away. + */ +export const BACKDROP_RADIUS_PC = 2500; + +/** + * Apparent-size clamps (parsecs at {@link BACKDROP_RADIUS_PC}) for a backdrop sprite. The floor + * is generous — most catalog objects are a few arcminutes across, and at this shell radius that + * is a pixel or two — so they read as haze rather than as another star. + */ +const MIN_SPRITE_SIZE_PC = 70; +const MAX_SPRITE_SIZE_PC = 340; + +const DEGREES_TO_RADIANS = Math.PI / 180; + +/** Loosely evocative of each class's real appearance in long-exposure photography. */ +const KIND_COLORS: Readonly> = { + galaxy: 0xffd9a0, + nebula: 0xff86b0, + cluster: 0xa8c8ff +}; + +/** + * Opacity bands by apparent magnitude. Sprites share a material per (kind, band), so + * brightness is quantised rather than continuous — nine materials instead of one per object, + * which keeps 400-odd backdrop sprites cheap to build and dispose. + */ +const BRIGHTNESS_BANDS: readonly { maxMagnitude: number; opacity: number }[] = [ + { maxMagnitude: 5, opacity: 0.5 }, + { maxMagnitude: 7.5, opacity: 0.3 }, + { maxMagnitude: Infinity, opacity: 0.16 } +]; + +/** + * Where a deep-sky object lands on the backdrop shell. The record stores a unit direction, + * so this is just that direction pushed out to the shell radius. + */ +export function backdropPosition(record: DeepSkyRecord, radiusPc = BACKDROP_RADIUS_PC): THREE.Vector3 { + return new THREE.Vector3(record.x, record.y, record.z).multiplyScalar(radiusPc); +} + +/** + * On-shell size for an object, from its true angular size — so the backdrop reproduces the + * real sky, where the Andromeda Galaxy is six times wider than the full Moon. + * + * Clamped at both ends: without a floor, the many sub-arcminute objects would be invisible + * specks, and without a ceiling a handful of very extended objects would blanket the view. + */ +export function backdropSpriteSizePc(record: DeepSkyRecord, radiusPc = BACKDROP_RADIUS_PC): number { + const trueSize = radiusPc * record.angularSizeDeg * DEGREES_TO_RADIANS; + return THREE.MathUtils.clamp(trueSize, MIN_SPRITE_SIZE_PC, MAX_SPRITE_SIZE_PC); +} + +/** Index into {@link BRIGHTNESS_BANDS}; unphotometered objects fall into the faintest band. */ +export function brightnessBandIndex(magnitude: number | null): number { + if (magnitude === null) { + return BRIGHTNESS_BANDS.length - 1; + } + const index = BRIGHTNESS_BANDS.findIndex((band) => magnitude <= band.maxMagnitude); + return index === -1 ? BRIGHTNESS_BANDS.length - 1 : index; +} + +/** + * The `limit` most prominent objects, as label anchors on the backdrop shell. Prominence is + * apparent magnitude, which is the order the ETL already writes, so this is a prefix of the + * records that actually have a name worth showing. + */ +export function deepSkyLabelPoints( + records: readonly DeepSkyRecord[], + limit: number, + radiusPc = BACKDROP_RADIUS_PC +): LabeledPoint[] { + return records.slice(0, limit).map((record) => { + const position = backdropPosition(record, radiusPc); + return { id: record.id, name: record.name, x: position.x, y: position.y, z: position.z }; + }); +} + +/** + * Paints the notable deep-sky objects from `deepsky.json` onto a fixed shell around the star + * field, as soft additive billboards coloured by kind and sized by real angular extent. + * + * Billboards rather than a single `THREE.Points` cloud: the WebGPU backend caps point + * primitives at one pixel (see `StarFieldRenderer`), which would reduce the Orion Nebula to a + * dot. Sprites cost one draw call each, so materials are shared across all of them and the + * catalog is pre-filtered by the ETL to the few hundred objects actually worth drawing. + */ +export class DeepSkyRenderer { + readonly object = new THREE.Group(); + + private readonly materials = new Map(); + + constructor( + private readonly records: readonly DeepSkyRecord[], + private readonly radiusPc = BACKDROP_RADIUS_PC + ) { + // Drawn before the star field so the stars composite on top of the glow. + this.object.renderOrder = -1; + + for (const record of records) { + const sprite = new THREE.Sprite(this.materialFor(record)); + sprite.position.copy(backdropPosition(record, this.radiusPc)); + sprite.scale.setScalar(backdropSpriteSizePc(record, this.radiusPc)); + this.object.add(sprite); + } + } + + /** Label anchors for the brightest `limit` objects on this backdrop. */ + labelPoints(limit: number): LabeledPoint[] { + return deepSkyLabelPoints(this.records, limit, this.radiusPc); + } + + dispose(): void { + for (const material of this.materials.values()) { + material.dispose(); + } + this.materials.clear(); + this.object.clear(); + } + + private materialFor(record: DeepSkyRecord): THREE.SpriteMaterial { + const band = brightnessBandIndex(record.magnitude); + const key = `${record.kind}:${band}`; + + let material = this.materials.get(key); + if (!material) { + const color = KIND_COLORS[record.kind]; + material = new THREE.SpriteMaterial({ + map: createGlowTexture(color, 'diffuse'), + color, + transparent: true, + opacity: BRIGHTNESS_BANDS[band].opacity, + depthWrite: false, + blending: THREE.AdditiveBlending + }); + this.materials.set(key, material); + } + return material; + } +} diff --git a/src/app/features/galaxy-system/galaxy-system-scene.component.spec.ts b/src/app/features/galaxy-system/galaxy-system-scene.component.spec.ts index 17f1263..469d27d 100644 --- a/src/app/features/galaxy-system/galaxy-system-scene.component.spec.ts +++ b/src/app/features/galaxy-system/galaxy-system-scene.component.spec.ts @@ -6,6 +6,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; import { DataLoaderService, StarField } from '../../core/data/data-loader.service'; import { EngineService, EngineTickCallback } from '../../core/engine/engine.service'; import { BodyRecord } from '../../shared/models/body.model'; +import { DeepSkyRecord } from '../../shared/models/deepsky.model'; import { ExoplanetRecord } from '../../shared/models/exoplanet.model'; import { StarRecord } from '../../shared/models/star.model'; import { NavigationStore } from '../../shared/state/navigation.store'; @@ -26,6 +27,21 @@ const PROXIMA: StarRecord = { id: 2, name: 'Proxima Centauri', x: 0, y: 1.3, z: const STARS: StarRecord[] = [SUN, ALPHA_CENTAURI, PROXIMA]; const STAR_POSITIONS = new Float32Array(STARS.flatMap((star) => [star.x, star.y, star.z])); +const DEEP_SKY_OBJECT: DeepSkyRecord = { + id: 'NGC0224', + name: 'Andromeda Galaxy', + kind: 'galaxy', + x: 0, + y: 0, + z: 1, + angularSizeDeg: 2.96, + magnitude: 3.44, + distancePc: null, + distanceMethod: null, + constellation: 'And', + messier: 'M31' +}; + const EARTH: BodyRecord = { id: 'earth', systemStarId: SUN.id, @@ -99,6 +115,10 @@ class FakeDataLoaderService { loadExoplanets(): Promise { return Promise.resolve([]); } + + loadDeepSky(): Promise { + return Promise.resolve([DEEP_SKY_OBJECT]); + } } /** Waits out several macrotask turns so chained promises (bootstrap's awaits) settle. */ diff --git a/src/app/features/galaxy-system/galaxy-system-scene.component.ts b/src/app/features/galaxy-system/galaxy-system-scene.component.ts index b084b75..be30b24 100644 --- a/src/app/features/galaxy-system/galaxy-system-scene.component.ts +++ b/src/app/features/galaxy-system/galaxy-system-scene.component.ts @@ -7,14 +7,16 @@ import { dateToJulianDate } from '../../shared/astro/constants'; import { DataLoaderService } from '../../core/data/data-loader.service'; import { EngineService } from '../../core/engine/engine.service'; import { BodyRecord } from '../../shared/models/body.model'; +import { DeepSkyRecord } from '../../shared/models/deepsky.model'; import { ExoplanetRecord } from '../../shared/models/exoplanet.model'; import { applyMilkyWaySkybox, createGlowSprite } from '../../shared/rendering/skybox'; import { loadCachedTexture, MILKY_WAY_SKYBOX_PATH, SUN_TEXTURE_PATH } from '../../shared/rendering/texture-catalog'; import { StarRecord } from '../../shared/models/star.model'; import { NavigationStore } from '../../shared/state/navigation.store'; import { CameraRigController } from './camera-rig-controller'; +import { DeepSkyRenderer } from './deep-sky-renderer'; import { colorIndexToRgb, StarFieldRenderer } from './star-field-renderer'; -import { StarLabelOverlay } from './star-label-overlay'; +import { LabeledPoint, StarLabelOverlay } from './star-label-overlay'; import { SystemOrbitsRenderer } from './system-orbits-renderer'; /** HYG catalog id for the Sun itself — the only star we have a real close-up photo of. */ @@ -25,6 +27,12 @@ const SUN_GLOW_SCALE = 3.2; const LABEL_MAX_DISTANCE_PC = 20; /** Caps how many labels are shown at once, to keep the DOM light. */ const LABEL_MAX_COUNT = 15; +/** + * How many deep-sky objects get a permanent label. These sit on a fixed backdrop shell rather + * than near the camera, so proximity is meaningless for them — the brightest handful are simply + * always named. + */ +const DEEP_SKY_LABEL_COUNT = 12; /** How often (seconds) the visible label set is recomputed; doesn't need to be per-frame. */ const LABEL_UPDATE_INTERVAL_SECONDS = 0.2; /** Raycast pick tolerance around each star point, in parsecs. */ @@ -99,6 +107,8 @@ export class GalaxySystemSceneComponent implements AfterViewInit, OnDestroy { private controls?: OrbitControls; private rig?: CameraRigController; private starField?: StarFieldRenderer; + private deepSky?: DeepSkyRenderer; + private deepSkyLabels: readonly LabeledPoint[] = []; private labelOverlay?: StarLabelOverlay; private stars: readonly StarRecord[] = []; private starsById = new Map(); @@ -140,6 +150,7 @@ export class GalaxySystemSceneComponent implements AfterViewInit, OnDestroy { this.canvasRef().nativeElement.removeEventListener('click', this.handleClick); this.controls?.dispose(); this.starField?.dispose(); + this.deepSky?.dispose(); this.labelOverlay?.dispose(); this.systemRenderer?.dispose(); (this.starMarker?.material as THREE.Material | undefined)?.dispose(); @@ -182,10 +193,16 @@ export class GalaxySystemSceneComponent implements AfterViewInit, OnDestroy { this.systemGroup.visible = false; applyMilkyWaySkybox(scene, MILKY_WAY_SKYBOX_PATH); - const [{ stars, positions }, bodies, exoplanets] = await Promise.all([ + const [{ stars, positions }, bodies, exoplanets, deepSky] = await Promise.all([ this.dataLoader.loadStars(), this.dataLoader.loadBodies(), - this.dataLoader.loadExoplanets() + this.dataLoader.loadExoplanets(), + // The backdrop is decorative — if its dataset is missing or malformed the star field + // should still come up, so this one failure is swallowed rather than aborting bootstrap. + this.dataLoader.loadDeepSky().catch((error) => { + console.error('Failed to load the deep-sky backdrop; continuing without it.', error); + return [] as DeepSkyRecord[]; + }) ]); this.stars = stars; this.starsById = new Map(stars.map((star) => [star.id, star])); @@ -195,6 +212,12 @@ export class GalaxySystemSceneComponent implements AfterViewInit, OnDestroy { this.starField = new StarFieldRenderer(stars, positions); this.galaxyGroup.add(this.starField.object); + if (deepSky.length > 0) { + this.deepSky = new DeepSkyRenderer(deepSky); + this.galaxyGroup.add(this.deepSky.object); + this.deepSkyLabels = this.deepSky.labelPoints(DEEP_SKY_LABEL_COUNT); + } + this.labelOverlay = new StarLabelOverlay(scene); this.labelHostRef().nativeElement.appendChild(this.labelOverlay.domElement); const { width, height } = canvas.getBoundingClientRect(); @@ -244,7 +267,8 @@ export class GalaxySystemSceneComponent implements AfterViewInit, OnDestroy { } candidates.sort((a, b) => a.distanceSq - b.distanceSq); - this.labelOverlay?.update(candidates.slice(0, LABEL_MAX_COUNT).map((candidate) => candidate.star)); + const starLabels = candidates.slice(0, LABEL_MAX_COUNT).map((candidate) => candidate.star); + this.labelOverlay?.update([...starLabels, ...this.deepSkyLabels]); } private readonly handleClick = (event: MouseEvent): void => { diff --git a/src/app/features/galaxy-system/star-label-overlay.ts b/src/app/features/galaxy-system/star-label-overlay.ts index e479423..af408de 100644 --- a/src/app/features/galaxy-system/star-label-overlay.ts +++ b/src/app/features/galaxy-system/star-label-overlay.ts @@ -2,7 +2,8 @@ import * as THREE from 'three/webgpu'; import { CSS2DObject, CSS2DRenderer } from 'three/addons/renderers/CSS2DRenderer.js'; export interface LabeledPoint { - id: number; + /** Numeric for HYG stars, string for catalog designations such as deep-sky objects. */ + id: number | string; name: string; x: number; y: number; @@ -19,7 +20,7 @@ export class StarLabelOverlay { readonly domElement: HTMLElement; private readonly cssRenderer = new CSS2DRenderer(); - private readonly labelObjects = new Map(); + private readonly labelObjects = new Map(); constructor(private readonly scene: THREE.Scene) { this.cssRenderer.domElement.classList.add('star-label-layer'); @@ -70,7 +71,7 @@ export class StarLabelOverlay { this.labelObjects.set(point.id, object); } - private removeLabel(id: number, object: CSS2DObject): void { + private removeLabel(id: number | string, object: CSS2DObject): void { this.scene.remove(object); object.element.remove(); this.labelObjects.delete(id); diff --git a/src/app/shared/astro/coordinates.spec.ts b/src/app/shared/astro/coordinates.spec.ts index d8ae8bf..2b75a28 100644 --- a/src/app/shared/astro/coordinates.spec.ts +++ b/src/app/shared/astro/coordinates.spec.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest'; -import { distanceBetween, parallaxMasToParsecs, raDecDistanceToXyz, raDegDecDistanceToXyz } from './coordinates'; +import { distanceBetween, parallaxMasToParsecs, parseSexagesimal, raDecDistanceToXyz, raDecToUnitVector, raDegDecDistanceToXyz } from './coordinates'; // Reference values taken directly from the HYG v4.1 database (RA/Dec/dist and its own // precomputed x/y/z, which uses the same equatorial-Cartesian convention we implement). @@ -65,3 +65,74 @@ describe('distanceBetween', () => { expect(distanceBetween({ x: 0, y: 0, z: 0 }, { x: 3, y: 4, z: 0 })).toBeCloseTo(5, 9); }); }); + +describe('raDecToUnitVector', () => { + it('always returns a unit-length vector', () => { + for (const [ra, dec] of [ + [0, 0], + [6, 45], + [13.7, -62.7], + [23.99, 89.9] + ]) { + const { x, y, z } = raDecToUnitVector(ra, dec); + expect(Math.hypot(x, y, z)).toBeCloseTo(1, 12); + } + }); + + it('points along +Z at the north celestial pole', () => { + const { x, y, z } = raDecToUnitVector(0, 90); + expect(x).toBeCloseTo(0, 12); + expect(y).toBeCloseTo(0, 12); + expect(z).toBeCloseTo(1, 12); + }); + + it('agrees with the distance-carrying conversion, scaled', () => { + const unit = raDecToUnitVector(6.752481, -16.716116); + const scaled = raDecDistanceToXyz(6.752481, -16.716116, 2.6371); + + expect(unit.x * 2.6371).toBeCloseTo(scaled.x, 12); + expect(unit.y * 2.6371).toBeCloseTo(scaled.y, 12); + expect(unit.z * 2.6371).toBeCloseTo(scaled.z, 12); + }); +}); + +describe('parseSexagesimal', () => { + it('parses a right ascension into decimal hours', () => { + // 00:08:27.05 = 8/60 + 27.05/3600 hours + expect(parseSexagesimal('00:08:27.05')).toBeCloseTo(0.140847, 6); + }); + + it('parses a positive declination into decimal degrees', () => { + expect(parseSexagesimal('+27:43:03.6')).toBeCloseTo(27.7176667, 6); + }); + + it('parses a negative declination', () => { + expect(parseSexagesimal('-12:49:22.3')).toBeCloseTo(-12.8228611, 6); + }); + + it('keeps the sign for a negative angle inside the first degree', () => { + // The trap: `Number('-00')` is `-0`, which is `=== 0`, so a naive implementation flips + // this object into the northern hemisphere. + const parsed = parseSexagesimal('-00:24:54.8'); + expect(parsed).toBeLessThan(0); + expect(parsed).toBeCloseTo(-0.4152222, 6); + }); + + it('treats an unsigned angle as positive', () => { + expect(parseSexagesimal('00:24:54.8')).toBeCloseTo(0.4152222, 6); + }); + + it('tolerates surrounding whitespace', () => { + expect(parseSexagesimal(' +27:43:03.6 ')).toBeCloseTo(27.7176667, 6); + }); + + it('returns null for missing or malformed values', () => { + for (const input of ['', ' ', 'not-an-angle', '12:34', '12:34:56:78', '12;34;56', undefined, null]) { + expect(parseSexagesimal(input)).toBeNull(); + } + }); + + it('returns null rather than a partial value for empty sub-fields', () => { + expect(parseSexagesimal('12::56')).toBeNull(); + }); +}); diff --git a/src/app/shared/astro/coordinates.ts b/src/app/shared/astro/coordinates.ts index c39e819..7b4b7e3 100644 --- a/src/app/shared/astro/coordinates.ts +++ b/src/app/shared/astro/coordinates.ts @@ -33,6 +33,40 @@ export function raDegDecDistanceToXyz(raDeg: number, decDeg: number, distancePc: return raDecDistanceToXyz(raDeg / HOURS_TO_DEG, decDeg, distancePc); } +/** + * Direction to a point on the celestial sphere as a unit vector, in the same equatorial frame + * as {@link raDecDistanceToXyz}. Used for sources whose distance is unknown or irrelevant — + * e.g. deep-sky objects rendered as a backdrop, where only the line of sight matters. + */ +export function raDecToUnitVector(raHours: number, decDeg: number): CartesianCoordinates { + return raDecDistanceToXyz(raHours, decDeg, 1); +} + +const SEXAGESIMAL_PATTERN = /^([+-])?(\d+):(\d+):(\d+(?:\.\d+)?)$/; + +/** + * Parses a sexagesimal angle ("HH:MM:SS.ss" or "+DD:MM:SS.s", as published by OpenNGC and + * most catalogs) into a decimal value carrying the unit of its first field — hours for right + * ascension, degrees for declination. + * + * The sign is read from the string rather than from the parsed degrees field: `Number('-00')` + * is `-0`, which compares equal to `0`, so a declination like "-00:24:54.8" would otherwise + * come out positive and place the object in the wrong hemisphere. + * + * Returns `null` for anything that isn't a well-formed sexagesimal triple, including the empty + * strings that catalogs use for missing values. + */ +export function parseSexagesimal(text: string | undefined | null): number | null { + const match = SEXAGESIMAL_PATTERN.exec((text ?? '').trim()); + if (!match) { + return null; + } + + const [, sign, degreesOrHours, minutes, seconds] = match; + const magnitude = Number(degreesOrHours) + Number(minutes) / 60 + Number(seconds) / 3600; + return sign === '-' ? -magnitude : magnitude; +} + /** * Converts a parallax (milliarcseconds) into a distance in parsecs. * Returns `Infinity` for non-positive parallax (unmeasured/negative parallax). diff --git a/src/app/shared/astro/deep-sky.spec.ts b/src/app/shared/astro/deep-sky.spec.ts new file mode 100644 index 0000000..c333190 --- /dev/null +++ b/src/app/shared/astro/deep-sky.spec.ts @@ -0,0 +1,141 @@ +import { describe, expect, it } from 'vitest'; + +import { + classifyOpenNgcType, + estimateDeepSkyDistancePc, + HUBBLE_CONSTANT_KM_S_PER_MPC, + isNotableDeepSkyObject, + NOTABLE_MAGNITUDE_LIMIT, + redshiftToDistancePc, + SPEED_OF_LIGHT_KM_S +} from './deep-sky'; + +describe('classifyOpenNgcType', () => { + it('groups every galaxy-ish type as a galaxy', () => { + for (const type of ['G', 'GPair', 'GTrpl', 'GGroup']) { + expect(classifyOpenNgcType(type)).toBe('galaxy'); + } + }); + + it('groups nebulae, remnants and cluster-with-nebulosity as nebulae', () => { + for (const type of ['PN', 'HII', 'EmN', 'RfN', 'Neb', 'DrkN', 'SNR', 'Cl+N']) { + expect(classifyOpenNgcType(type)).toBe('nebula'); + } + }); + + it('groups open, globular and stellar-association types as clusters', () => { + for (const type of ['OCl', 'GCl', '*Ass']) { + expect(classifyOpenNgcType(type)).toBe('cluster'); + } + }); + + it('rejects catalog rows that are not deep-sky objects', () => { + // Duplicates, non-existent entries, plain and double stars, novae, and the catch-all. + for (const type of ['Dup', 'NonEx', '*', '**', 'Nova', 'Other']) { + expect(classifyOpenNgcType(type)).toBeNull(); + } + }); + + it('rejects missing or unknown types', () => { + for (const type of ['', ' ', 'wat', undefined, null]) { + expect(classifyOpenNgcType(type)).toBeNull(); + } + }); + + it('ignores surrounding whitespace', () => { + expect(classifyOpenNgcType(' G ')).toBe('galaxy'); + }); +}); + +describe('redshiftToDistancePc', () => { + it('applies the Hubble law', () => { + const redshift = 0.02286; + const expectedMpc = (SPEED_OF_LIGHT_KM_S * redshift) / HUBBLE_CONSTANT_KM_S_PER_MPC; + expect(redshiftToDistancePc(redshift)).toBeCloseTo(expectedMpc * 1e6, 0); + }); + + it('scales linearly with redshift', () => { + const near = redshiftToDistancePc(0.01)!; + const far = redshiftToDistancePc(0.02)!; + expect(far / near).toBeCloseTo(2, 9); + }); + + it('rejects a blueshift, which carries no distance information', () => { + // M31 approaches us at ~300 km/s. + expect(redshiftToDistancePc(-0.001)).toBeNull(); + }); + + it('rejects redshifts too small to be dominated by cosmological expansion', () => { + // The Small Magellanic Cloud: a real positive redshift that yields a 33x-wrong distance. + expect(redshiftToDistancePc(0.000527)).toBeNull(); + }); + + it('rejects null and non-finite input', () => { + expect(redshiftToDistancePc(null)).toBeNull(); + expect(redshiftToDistancePc(Number.NaN)).toBeNull(); + expect(redshiftToDistancePc(Number.POSITIVE_INFINITY)).toBeNull(); + }); +}); + +describe('estimateDeepSkyDistancePc', () => { + it('prefers parallax for a galactic object', () => { + // The Helix Nebula, ~200 pc away. + const estimate = estimateDeepSkyDistancePc({ kind: 'nebula', redshift: 0.05, parallaxMas: 4.98 }); + expect(estimate?.method).toBe('parallax'); + expect(estimate?.distancePc).toBeCloseTo(200.8, 1); + }); + + it('refuses parallax for a galaxy, because the catalog value is a foreground star', () => { + // OpenNGC lists 6 mas for M31 — that would put a 780 kpc galaxy at 167 pc. + const estimate = estimateDeepSkyDistancePc({ kind: 'galaxy', redshift: -0.001, parallaxMas: 6 }); + expect(estimate).toBeNull(); + }); + + it('falls back to redshift for a distant galaxy', () => { + const estimate = estimateDeepSkyDistancePc({ kind: 'galaxy', redshift: 0.00365, parallaxMas: null }); + expect(estimate?.method).toBe('redshift'); + expect(estimate!.distancePc).toBeGreaterThan(1e7); + }); + + it('falls back to redshift when a galactic object has no usable parallax', () => { + for (const parallaxMas of [null, 0, -3]) { + const estimate = estimateDeepSkyDistancePc({ kind: 'cluster', redshift: 0.01, parallaxMas }); + expect(estimate?.method).toBe('redshift'); + } + }); + + it('rejects a parallax implying a distance beyond the Milky Way', () => { + // 0.000001 mas would imply a billion parsecs — noise, not a measurement. + const estimate = estimateDeepSkyDistancePc({ kind: 'cluster', redshift: null, parallaxMas: 1e-6 }); + expect(estimate).toBeNull(); + }); + + it('returns null when neither source is usable', () => { + expect(estimateDeepSkyDistancePc({ kind: 'nebula', redshift: null, parallaxMas: null })).toBeNull(); + expect(estimateDeepSkyDistancePc({ kind: 'galaxy', redshift: null, parallaxMas: null })).toBeNull(); + }); +}); + +describe('isNotableDeepSkyObject', () => { + it('keeps anything in the Messier catalog, however faint', () => { + expect(isNotableDeepSkyObject({ messier: 'M76', commonName: null, magnitude: 12 })).toBe(true); + }); + + it('keeps anything with a common name', () => { + expect(isNotableDeepSkyObject({ messier: null, commonName: 'Helix Nebula', magnitude: 20 })).toBe(true); + }); + + it('keeps an anonymous object that is bright enough', () => { + expect(isNotableDeepSkyObject({ messier: null, commonName: null, magnitude: NOTABLE_MAGNITUDE_LIMIT })).toBe(true); + }); + + it('drops an anonymous object fainter than the limit', () => { + expect(isNotableDeepSkyObject({ messier: null, commonName: null, magnitude: NOTABLE_MAGNITUDE_LIMIT + 0.1 })).toBe(false); + }); + + it('drops an anonymous object with no measured magnitude', () => { + // Guards the `Number('') === 0` trap: an unphotometered object must not be treated as + // magnitude 0, which would make it brighter than every star in the sky. + expect(isNotableDeepSkyObject({ messier: null, commonName: null, magnitude: null })).toBe(false); + }); +}); diff --git a/src/app/shared/astro/deep-sky.ts b/src/app/shared/astro/deep-sky.ts new file mode 100644 index 0000000..33e1867 --- /dev/null +++ b/src/app/shared/astro/deep-sky.ts @@ -0,0 +1,133 @@ +import { DeepSkyDistanceMethod, DeepSkyKind } from '../models/deepsky.model'; +import { parallaxMasToParsecs } from './coordinates'; + +/** Speed of light in km/s (exact, by definition of the metre). */ +export const SPEED_OF_LIGHT_KM_S = 299792.458; + +/** + * Hubble constant in km/s/Mpc. The measured value is contested — "Hubble tension" — with + * ~67 from the cosmic microwave background and ~73 from the local distance ladder; 70 is the + * conventional round middle. Distances derived from it are good to roughly 10%, which is far + * inside the tolerance needed to place a smudge of light on a backdrop. + */ +export const HUBBLE_CONSTANT_KM_S_PER_MPC = 70; + +const PARSECS_PER_MEGAPARSEC = 1e6; + +/** + * Minimum redshift trusted for a Hubble-law distance, ≈900 km/s of recession or ~13 Mpc. + * + * Below this a galaxy's measured velocity is mostly its own motion through its group rather + * than cosmological expansion, so `cz / H0` stops being a distance at all. Galaxies have + * peculiar velocities of a few hundred km/s in any direction: the Local Group's members are + * blueshifted outright (M31 approaches at ~300 km/s), and the Small Magellanic Cloud's small + * positive redshift yields 2 Mpc against a true distance of 62 kpc — a 33-fold error. + * + * Cutting here trades coverage for honesty. Nearby galaxies come back with a `null` distance + * instead of a confident wrong one, which is the right answer to show a user. + */ +const MIN_USABLE_REDSHIFT = 0.003; + +/** + * Upper bound on a parallax-derived distance, in parsecs. The Milky Way's disc is ~30 kpc + * across, so a parallax implying more than this is measurement noise rather than a real + * distance to a galactic object. + */ +const MAX_PARALLAX_DISTANCE_PC = 100000; + +/** + * OpenNGC object-type codes grouped into the three kinds the backdrop distinguishes. + * Codes not listed here (`Dup` duplicates, `NonEx` non-existent entries, plain stars `*`, + * doubles `**`, `Nova`, `Other`) are not deep-sky objects and are dropped. + */ +const KIND_BY_OPENNGC_TYPE: Readonly> = { + // Galaxies, and multi-galaxy systems. + G: 'galaxy', + GPair: 'galaxy', + GTrpl: 'galaxy', + GGroup: 'galaxy', + // Nebulae of every flavour, including supernova remnants and cluster-with-nebulosity. + PN: 'nebula', + HII: 'nebula', + EmN: 'nebula', + RfN: 'nebula', + Neb: 'nebula', + DrkN: 'nebula', + SNR: 'nebula', + 'Cl+N': 'nebula', + // Star clusters and associations. + OCl: 'cluster', + GCl: 'cluster', + '*Ass': 'cluster' +}; + +/** Maps an OpenNGC `Type` code to a backdrop kind, or `null` if it isn't a deep-sky object. */ +export function classifyOpenNgcType(type: string | undefined | null): DeepSkyKind | null { + return KIND_BY_OPENNGC_TYPE[(type ?? '').trim()] ?? null; +} + +/** + * Hubble-law distance for a cosmological redshift, in parsecs: `d = cz / H0`. + * Returns `null` for a redshift too small (or negative) to be dominated by expansion — + * see {@link MIN_USABLE_REDSHIFT}. + */ +export function redshiftToDistancePc(redshift: number | null): number | null { + if (redshift === null || !Number.isFinite(redshift) || redshift < MIN_USABLE_REDSHIFT) { + return null; + } + return ((SPEED_OF_LIGHT_KM_S * redshift) / HUBBLE_CONSTANT_KM_S_PER_MPC) * PARSECS_PER_MEGAPARSEC; +} + +export interface DeepSkyDistanceEstimate { + distancePc: number; + method: DeepSkyDistanceMethod; +} + +/** + * Best-effort distance for a deep-sky object, with its provenance. + * + * Parallax is preferred for galactic objects (clusters, nebulae) where it is a direct + * geometric measurement, but is *rejected outright for galaxies*: OpenNGC's parallax column + * for a galaxy comes from a cross-matched foreground star, not the galaxy itself, and taking + * it at face value is badly wrong — M31 lists 6 mas, implying 167 pc for something actually + * ~780,000 pc away. Redshift is the fallback, and the only usable option for distant galaxies. + * + * Returns `null` when neither source is trustworthy, which is the honest answer for Local + * Group members and for anything OpenNGC leaves unmeasured. + */ +export function estimateDeepSkyDistancePc(input: { + kind: DeepSkyKind; + redshift: number | null; + parallaxMas: number | null; +}): DeepSkyDistanceEstimate | null { + const { kind, redshift, parallaxMas } = input; + + if (kind !== 'galaxy' && parallaxMas !== null && Number.isFinite(parallaxMas) && parallaxMas > 0) { + const distancePc = parallaxMasToParsecs(parallaxMas); + if (Number.isFinite(distancePc) && distancePc <= MAX_PARALLAX_DISTANCE_PC) { + return { distancePc, method: 'parallax' }; + } + } + + const fromRedshift = redshiftToDistancePc(redshift); + return fromRedshift === null ? null : { distancePc: fromRedshift, method: 'redshift' }; +} + +/** + * Whether an object is notable enough for the backdrop. The full catalog is ~12,000 objects, + * almost all of them faint anonymous galaxies that would render as visual noise; this keeps + * the ones a person could actually pick out — everything in the Messier catalog, everything + * with a common name, and anything else brighter than {@link NOTABLE_MAGNITUDE_LIMIT}. + */ +export const NOTABLE_MAGNITUDE_LIMIT = 9; + +export function isNotableDeepSkyObject(input: { + messier: string | null; + commonName: string | null; + magnitude: number | null; +}): boolean { + if (input.messier || input.commonName) { + return true; + } + return input.magnitude !== null && input.magnitude <= NOTABLE_MAGNITUDE_LIMIT; +} diff --git a/src/app/shared/models/deepsky.model.ts b/src/app/shared/models/deepsky.model.ts new file mode 100644 index 0000000..7919c5b --- /dev/null +++ b/src/app/shared/models/deepsky.model.ts @@ -0,0 +1,50 @@ +/** Broad visual category a deep-sky object is grouped under on the galaxy-view backdrop. */ +export type DeepSkyKind = 'galaxy' | 'nebula' | 'cluster'; + +/** How a record's distance estimate was derived, when one could be derived at all. */ +export type DeepSkyDistanceMethod = 'parallax' | 'redshift'; + +/** + * A notable deep-sky object (nebula, star cluster or galaxy) from the OpenNGC catalog, + * rendered as the galaxy view's backdrop. + * + * **Why a direction and not a position.** Every other record in this app carries a Cartesian + * position in parsecs, but deep-sky objects deliberately do not. The star field spans 50 pc; + * the nearest object here is several hundred parsecs away and the galaxies are millions. At + * true scale they would all sit far outside the galaxy camera's far plane, so a position in + * parsecs would be unusable for the backdrop it exists to draw. + * + * More importantly the distances mostly are not knowable from this catalog: OpenNGC publishes + * no distance column, so it has to be inferred from redshift or parallax, and that inference + * fails for exactly the best-known objects — M31, M33 and M42 are all Local Group members whose + * redshift is negative (they are approaching us) or absent. What *is* always known, and known + * precisely, is the line of sight. So the position here is a unit vector on the celestial + * sphere and {@link distancePc} is optional metadata. + */ +export interface DeepSkyRecord { + /** OpenNGC designation, e.g. `NGC0224`. Stable, and unique within the catalog. */ + id: string; + /** Best available display name: common name, else Messier number, else the designation. */ + name: string; + kind: DeepSkyKind; + /** + * Unit vector toward the object, in the same equatorial J2000 frame as `StarRecord` + * (+X toward the vernal equinox, +Z toward the north celestial pole). Not a position — + * see the note on this interface. + */ + x: number; + y: number; + z: number; + /** Apparent major-axis size on the sky, in degrees. */ + angularSizeDeg: number; + /** Apparent visual magnitude (falling back to blue), or `null` when unphotometered. */ + magnitude: number | null; + /** Estimated distance in parsecs, or `null` when it could not be derived. */ + distancePc: number | null; + /** Provenance for {@link distancePc}; `null` whenever the distance is `null`. */ + distanceMethod: DeepSkyDistanceMethod | null; + /** IAU constellation abbreviation, e.g. `And`. */ + constellation: string; + /** Messier designation, e.g. `M31`, when the object has one. */ + messier: string | null; +} diff --git a/src/app/shared/rendering/skybox.ts b/src/app/shared/rendering/skybox.ts index 1d94d2e..6600279 100644 --- a/src/app/shared/rendering/skybox.ts +++ b/src/app/shared/rendering/skybox.ts @@ -22,12 +22,41 @@ export function applyMilkyWaySkybox(scene: THREE.Scene, path: string): void { const glowSpriteCache = new Map(); /** - * A soft radial-gradient canvas texture, cached per color, used to fake atmosphere/corona glow. - * Returns `undefined` if 2D canvas rendering isn't available (e.g. under a test/jsdom - * environment with no canvas backend) so callers can fall back to a flat-color sprite instead. + * Falloff shapes for {@link createGlowTexture}. + * + * `corona` is a tight, bright core for a star's or planet's halo, where the light really does + * come from a small hot source. `diffuse` is a much softer, dimmer profile for deep-sky + * objects, which are extended clouds — the same tight curve turns them into hard-edged + * billiard balls that read as solid geometry rather than as haze. */ -function glowSpriteTexture(color: THREE.ColorRepresentation): THREE.Texture | undefined { - const key = new THREE.Color(color).getHexString(); +export type GlowProfile = 'corona' | 'diffuse'; + +const GLOW_PROFILES: Readonly> = { + corona: [ + { offset: 0, alpha: 0.85 }, + { offset: 0.4, alpha: 0.35 }, + { offset: 1, alpha: 0 } + ], + diffuse: [ + { offset: 0, alpha: 0.5 }, + { offset: 0.18, alpha: 0.34 }, + { offset: 0.45, alpha: 0.13 }, + { offset: 0.75, alpha: 0.03 }, + { offset: 1, alpha: 0 } + ] +}; + +/** + * A soft radial-gradient canvas texture, cached per color and profile, used to fake + * atmosphere/corona glow and to paint deep-sky objects on the galaxy backdrop. Returns + * `undefined` if 2D canvas rendering isn't available (e.g. under a test/jsdom environment with + * no canvas backend) so callers can fall back to a flat-color sprite instead. + * + * The cache is process-wide and intentionally not disposed: there is one texture per distinct + * color/profile pair, they are tiny, and they outlive any individual scene. + */ +export function createGlowTexture(color: THREE.ColorRepresentation, profile: GlowProfile = 'corona'): THREE.Texture | undefined { + const key = `${new THREE.Color(color).getHexString()}:${profile}`; const cached = glowSpriteCache.get(key); if (cached) { return cached; @@ -46,9 +75,9 @@ function glowSpriteTexture(color: THREE.ColorRepresentation): THREE.Texture | un const [r, g, b] = [Math.round(rgb.r * 255), Math.round(rgb.g * 255), Math.round(rgb.b * 255)]; const gradient = context.createRadialGradient(size / 2, size / 2, 0, size / 2, size / 2, size / 2); - gradient.addColorStop(0, `rgba(${r}, ${g}, ${b}, 0.85)`); - gradient.addColorStop(0.4, `rgba(${r}, ${g}, ${b}, 0.35)`); - gradient.addColorStop(1, `rgba(${r}, ${g}, ${b}, 0)`); + for (const stop of GLOW_PROFILES[profile]) { + gradient.addColorStop(stop.offset, `rgba(${r}, ${g}, ${b}, ${stop.alpha})`); + } context.fillStyle = gradient; context.fillRect(0, 0, size, size); @@ -66,7 +95,7 @@ function glowSpriteTexture(color: THREE.ColorRepresentation): THREE.Texture | un */ export function createGlowSprite(color: THREE.ColorRepresentation, radius: number, scale: number): THREE.Sprite { const material = new THREE.SpriteMaterial({ - map: glowSpriteTexture(color), + map: createGlowTexture(color), color: color, transparent: true, depthWrite: false, diff --git a/src/assets/data/deepsky.json b/src/assets/data/deepsky.json new file mode 100644 index 0000000..94f4a30 --- /dev/null +++ b/src/assets/data/deepsky.json @@ -0,0 +1 @@ +[{"id":"NGC0292","name":"Small Magellanic Cloud","kind":"galaxy","x":0.2874464392741965,"y":0.06734894476361801,"z":-0.9554259072203416,"angularSizeDeg":4.998666666666667,"magnitude":2.3,"distancePc":null,"distanceMethod":null,"constellation":"Tuc","messier":null},{"id":"IC2391","name":"omi Vel Cluster","kind":"cluster","x":-0.3875882398464801,"y":0.45974086213155807,"z":-0.7990079449037032,"angularSizeDeg":0.48500000000000004,"magnitude":2.5,"distancePc":151.58405335758678,"distanceMethod":"parallax","constellation":"Vel","messier":null},{"id":"NGC1980","name":"Lower Sword","kind":"nebula","x":0.10641923517598882,"y":0.9889758929735606,"z":-0.10296421466561094,"angularSizeDeg":0.155,"magnitude":2.5,"distancePc":null,"distanceMethod":null,"constellation":"Ori","messier":null},{"id":"NGC6231","name":"NGC6231","kind":"cluster","x":-0.21107900792194648,"y":-0.7146743971467615,"z":-0.6668479275499023,"angularSizeDeg":0.23,"magnitude":2.6,"distancePc":1697.792869269949,"distanceMethod":"parallax","constellation":"Sco","messier":null},{"id":"NGC3372","name":"Carina Nebula","kind":"nebula","x":-0.4754717707081708,"y":0.16107205633398092,"z":-0.8648597504381734,"angularSizeDeg":2,"magnitude":3,"distancePc":2631.578947368421,"distanceMethod":"parallax","constellation":"Car","messier":null},{"id":"NGC3532","name":"Wishing Well Cluster","kind":"cluster","x":-0.5040346459318406,"y":0.12148029634607257,"z":-0.8550974291272513,"angularSizeDeg":0.2,"magnitude":3,"distancePc":484.05053487584104,"distanceMethod":"parallax","constellation":"Car","messier":null},{"id":"NGC2632","name":"Beehive","kind":"cluster","x":-0.6064349630178212,"y":0.7203560306607595,"z":0.3366360419210172,"angularSizeDeg":1.8099999999999998,"magnitude":3.1,"distancePc":186.1850679575498,"distanceMethod":"parallax","constellation":"Cnc","messier":"M44"},{"id":"NGC6475","name":"Ptolemy's Cluster","kind":"cluster","x":-0.022023601806847627,"y":-0.820925219620826,"z":-0.5706108522924814,"angularSizeDeg":0.37,"magnitude":3.3,"distancePc":280.08066323101053,"distanceMethod":"parallax","constellation":"Sco","messier":"M7"},{"id":"NGC0224","name":"Andromeda Galaxy","kind":"galaxy","x":0.7385889167607737,"y":0.13935478763331233,"z":0.6595958271561618,"angularSizeDeg":2.9638333333333335,"magnitude":3.44,"distancePc":null,"distanceMethod":null,"constellation":"And","messier":"M31"},{"id":"NGC0869","name":"h Persei Cluster","kind":"cluster","x":0.4461223088472046,"y":0.30941686191927265,"z":0.8397833596284634,"angularSizeDeg":0.24000000000000002,"magnitude":3.7,"distancePc":2536.783358701167,"distanceMethod":"parallax","constellation":"Per","messier":null},{"id":"NGC0884","name":"chi Persei Cluster","kind":"cluster","x":0.4409437226883132,"y":0.31607773244972576,"z":0.8400377970491634,"angularSizeDeg":0.175,"magnitude":3.8,"distancePc":2515.0905432595573,"distanceMethod":"parallax","constellation":"Per","messier":null},{"id":"NGC2516","name":"NGC2516","kind":"cluster","x":-0.24080086205778853,"y":0.42510468468507406,"z":-0.8725256167534736,"angularSizeDeg":0.405,"magnitude":3.8,"distancePc":414.6280786134837,"distanceMethod":"parallax","constellation":"Car","messier":null},{"id":"IC4592","name":"IC4592","kind":"nebula","x":-0.42815130869162565,"y":-0.8400933752818194,"z":-0.3330609218645269,"angularSizeDeg":1,"magnitude":3.9,"distancePc":null,"distanceMethod":null,"constellation":"Sco","messier":null},{"id":"NGC2232","name":"NGC2232","kind":"cluster","x":-0.12151471570003614,"y":0.9889860573131279,"z":-0.0845029721878092,"angularSizeDeg":0.165,"magnitude":3.9,"distancePc":325.626831650928,"distanceMethod":"parallax","constellation":"Mon","messier":null},{"id":"NGC2264","name":"Christmas Tree Cluster","kind":"nebula","x":-0.1751728924053458,"y":0.9694233363224082,"z":0.1718512518426217,"angularSizeDeg":0.19,"magnitude":3.9,"distancePc":738.5524372230428,"distanceMethod":"parallax","constellation":"Mon","messier":null},{"id":"NGC1976","name":"Great Orion Nebula","kind":"nebula","x":0.10719941904388641,"y":0.9897907214756494,"z":-0.09392876150235727,"angularSizeDeg":1.5,"magnitude":4,"distancePc":null,"distanceMethod":null,"constellation":"Ori","messier":"M42"},{"id":"NGC7000","name":"North America Nebula","kind":"nebula","x":0.5025215893390409,"y":-0.5056639800030981,"z":0.7012674180193963,"angularSizeDeg":2,"magnitude":4,"distancePc":null,"distanceMethod":null,"constellation":"Cyg","messier":null},{"id":"NGC0104","name":"47 Tuc Cluster","kind":"cluster","x":0.30596680214344907,"y":0.032278992048101876,"z":-0.9514948148353043,"angularSizeDeg":0.53,"magnitude":4.09,"distancePc":5125.576627370579,"distanceMethod":"parallax","constellation":"Tuc","messier":null},{"id":"NGC2362","name":"NGC2362","kind":"cluster","x":-0.3052204746769348,"y":0.8537249415171625,"z":-0.4218935719698797,"angularSizeDeg":0.12000000000000001,"magnitude":4.1,"distancePc":1347.7088948787061,"distanceMethod":"parallax","constellation":"CMa","messier":null},{"id":"IC4665","name":"IC4665","kind":"cluster","x":-0.0587886556779346,"y":-0.9934060562671552,"z":0.09842916912845905,"angularSizeDeg":0.41000000000000003,"magnitude":4.2,"distancePc":345.8053807317242,"distanceMethod":"parallax","constellation":"Oph","messier":null},{"id":"NGC1981","name":"Upper Sword","kind":"nebula","x":0.10785113525032462,"y":0.9911686200501475,"z":-0.07715503388050853,"angularSizeDeg":0.15,"magnitude":4.2,"distancePc":null,"distanceMethod":null,"constellation":"Ori","messier":null},{"id":"NGC3114","name":"NGC3114","kind":"cluster","x":-0.4339859307239476,"y":0.24430695251176718,"z":-0.8671622252428216,"angularSizeDeg":0.20500000000000002,"magnitude":4.2,"distancePc":1048.2180293501049,"distanceMethod":"parallax","constellation":"Car","messier":null},{"id":"NGC6405","name":"Butterfly Cluster","kind":"cluster","x":-0.07243529677627633,"y":-0.8425811747567244,"z":-0.5336760175673166,"angularSizeDeg":0.26,"magnitude":4.2,"distancePc":462.40636271155097,"distanceMethod":"parallax","constellation":"Sco","messier":"M6"},{"id":"IC2581","name":"IC2581","kind":"cluster","x":-0.4925256757030606,"y":0.21037005219703814,"z":-0.8444897275348345,"angularSizeDeg":0.1,"magnitude":4.3,"distancePc":2840.909090909091,"distanceMethod":"parallax","constellation":"Car","messier":null},{"id":"NGC2422","name":"M47","kind":"cluster","x":-0.39606335174633467,"y":0.8835104588555747,"z":-0.25008616614333395,"angularSizeDeg":0.33,"magnitude":4.4,"distancePc":483.32527791203484,"distanceMethod":"parallax","constellation":"Pup","messier":"M47"},{"id":"IC2944","name":"lam Cen Nebula","kind":"nebula","x":-0.4511514487359661,"y":0.04785151764202504,"z":-0.8911636227779913,"angularSizeDeg":0.12000000000000001,"magnitude":4.5,"distancePc":null,"distanceMethod":null,"constellation":"Cen","messier":null},{"id":"IC4715","name":"Small Sgr Star Cloud","kind":"cluster","x":0.07000603344724389,"y":-0.945655313660917,"z":-0.31754556055132604,"angularSizeDeg":2,"magnitude":4.5,"distancePc":null,"distanceMethod":null,"constellation":"Sgr","messier":"M24"},{"id":"NGC2287","name":"M41","kind":"cluster","x":-0.1864267712174606,"y":0.9163373209231911,"z":-0.3543599487198593,"angularSizeDeg":0.2,"magnitude":4.5,"distancePc":735.2941176470588,"distanceMethod":"parallax","constellation":"CMa","messier":"M41"},{"id":"IC2395","name":"IC2395","kind":"cluster","x":-0.43440568357871007,"y":0.5063742774094224,"z":-0.7449005257432693,"angularSizeDeg":0.1,"magnitude":4.6,"distancePc":725.1631617113851,"distanceMethod":"parallax","constellation":"Vel","messier":null},{"id":"IC4725","name":"M25","kind":"cluster","x":0.13059930869773279,"y":-0.935794269263033,"z":-0.3274643586440217,"angularSizeDeg":0.235,"magnitude":4.6,"distancePc":664.7610184138803,"distanceMethod":"parallax","constellation":"Sgr","messier":"M25"},{"id":"IC4756","name":"IC4756","kind":"cluster","x":0.16797477197439006,"y":-0.9811848113061786,"z":0.09518845540405824,"angularSizeDeg":0.4,"magnitude":4.6,"distancePc":477.48651100606406,"distanceMethod":"parallax","constellation":"Se2","messier":null},{"id":"NGC6530","name":"NGC6530","kind":"nebula","x":0.017954238789533526,"y":-0.9108088940810977,"z":-0.4124376362218367,"angularSizeDeg":0.1,"magnitude":4.6,"distancePc":1333.3333333333333,"distanceMethod":"parallax","constellation":"Sgr","messier":null},{"id":"NGC6633","name":"NGC6633","kind":"cluster","x":0.11787265934354445,"y":-0.9865387812647259,"z":0.11334579498150814,"angularSizeDeg":0.2,"magnitude":4.6,"distancePc":396.32213062777424,"distanceMethod":"parallax","constellation":"Oph","messier":null},{"id":"NGC7092","name":"M39","kind":"cluster","x":0.5294977838486854,"y":-0.39971079219609973,"z":0.7482401883762307,"angularSizeDeg":0.325,"magnitude":4.6,"distancePc":299.6434243250532,"distanceMethod":"parallax","constellation":"Cyg","messier":"M39"},{"id":"IC4605","name":"IC4605","kind":"nebula","x":-0.3457438373278256,"y":-0.8368468395391778,"z":-0.4244391194307136,"angularSizeDeg":0.5,"magnitude":4.7,"distancePc":null,"distanceMethod":null,"constellation":"Sco","messier":null},{"id":"NGC2547","name":"NGC2547","kind":"cluster","x":-0.3514226035130876,"y":0.5507838050316326,"z":-0.7570596765479982,"angularSizeDeg":0.13,"magnitude":4.7,"distancePc":393.1126660901014,"distanceMethod":"parallax","constellation":"Vel","messier":null},{"id":"NGC3293","name":"NGC3293","kind":"cluster","x":-0.4914623946130251,"y":0.1891153856246212,"z":-0.8501176892650111,"angularSizeDeg":0.08499999999999999,"magnitude":4.7,"distancePc":2659.574468085106,"distanceMethod":"parallax","constellation":"Car","messier":null},{"id":"NGC2239","name":"NGC2239","kind":"nebula","x":-0.13833695372626123,"y":0.9866299748040883,"z":0.0861636817448187,"angularSizeDeg":0.155,"magnitude":4.8,"distancePc":1620.745542949757,"distanceMethod":"parallax","constellation":"Mon","messier":null},{"id":"NGC6254","name":"M10","kind":"cluster","x":-0.2701183270517635,"y":-0.9601697059757981,"z":-0.07148583857734243,"angularSizeDeg":0.155,"magnitude":4.98,"distancePc":6635.700066357001,"distanceMethod":"parallax","constellation":"Oph","messier":"M10"},{"id":"NGC1499","name":"California Nebula","kind":"nebula","x":0.39271579904270193,"y":0.7029726583408904,"z":0.592961839250549,"angularSizeDeg":2.6666666666666665,"magnitude":5,"distancePc":null,"distanceMethod":null,"constellation":"Per","messier":null},{"id":"IC4604","name":"rho Oph Nebula","kind":"nebula","x":-0.3676158750654221,"y":-0.8406345200045263,"z":-0.39773379561792793,"angularSizeDeg":1,"magnitude":5.1,"distancePc":null,"distanceMethod":null,"constellation":"Oph","messier":null},{"id":"NGC2168","name":"M35","kind":"cluster","x":-0.036105630611456725,"y":0.9104098850104072,"z":0.4121288933252388,"angularSizeDeg":0.4,"magnitude":5.1,"distancePc":884.8774444739403,"distanceMethod":"parallax","constellation":"Gem","messier":"M35"},{"id":"NGC6025","name":"NGC6025","kind":"cluster","x":-0.240560695113735,"y":-0.43085858160337603,"z":-0.8697651606181542,"angularSizeDeg":0.19,"magnitude":5.1,"distancePc":790.7638779060572,"distanceMethod":"parallax","constellation":"TrA","messier":null},{"id":"NGC6397","name":"NGC6397","kind":"cluster","x":-0.04985438066715133,"y":-0.5902815574072766,"z":-0.8056563930815263,"angularSizeDeg":0.255,"magnitude":5.17,"distancePc":2615.062761506276,"distanceMethod":"parallax","constellation":"Ara","messier":null},{"id":"NGC1039","name":"M34","kind":"cluster","x":0.5581612597756229,"y":0.4772344674860218,"z":0.6787512586574134,"angularSizeDeg":0.375,"magnitude":5.2,"distancePc":511.87551187551185,"distanceMethod":"parallax","constellation":"Per","messier":"M34"},{"id":"NGC6871","name":"NGC6871","kind":"cluster","x":0.42387283472022885,"y":-0.6917608341374124,"z":0.5846355859334503,"angularSizeDeg":0.155,"magnitude":5.2,"distancePc":1945.5252918287938,"distanceMethod":"parallax","constellation":"Cyg","messier":null},{"id":"NGC3766","name":"Pearl Cluster","kind":"cluster","x":-0.4729915588432107,"y":0.04921303139796569,"z":-0.8796914588670804,"angularSizeDeg":0.115,"magnitude":5.3,"distancePc":2178.649237472767,"distanceMethod":"parallax","constellation":"Cen","messier":null},{"id":"NGC5138","name":"NGC5138","kind":"cluster","x":-0.47759208230922023,"y":-0.19115254005015678,"z":-0.8575351359261708,"angularSizeDeg":0.07,"magnitude":5.33,"distancePc":1960.7843137254902,"distanceMethod":"parallax","constellation":"Cen","messier":null},{"id":"NGC5139","name":"Omega Centauri","kind":"cluster","x":-0.6280278035586645,"y":-0.2498109961156749,"z":-0.7370044397267724,"angularSizeDeg":0.45,"magnitude":5.33,"distancePc":7309.941520467836,"distanceMethod":"parallax","constellation":"Cen","messier":null},{"id":"NGC2281","name":"NGC2281","kind":"cluster","x":-0.15768150979599718,"y":0.7371294586901496,"z":0.6570971789618503,"angularSizeDeg":0.18000000000000002,"magnitude":5.4,"distancePc":527.4261603375528,"distanceMethod":"parallax","constellation":"Aur","messier":null},{"id":"NGC6087","name":"S Nor Cluster","kind":"cluster","x":-0.2267887483312572,"y":-0.48000838170144616,"z":-0.8474425155293428,"angularSizeDeg":0.16999999999999998,"magnitude":5.4,"distancePc":975.6097560975611,"distanceMethod":"parallax","constellation":"Nor","messier":null},{"id":"NGC6121","name":"M4","kind":"cluster","x":-0.3653833841332712,"y":-0.8167291764582917,"z":-0.44659650124142164,"angularSizeDeg":0.47,"magnitude":5.4,"distancePc":2002.002002002002,"distanceMethod":"parallax","constellation":"Sco","messier":"M4"},{"id":"NGC6281","name":"NGC6281","kind":"cluster","x":-0.18837771903340633,"y":-0.7653267447261158,"z":-0.6154582104243128,"angularSizeDeg":0.16999999999999998,"magnitude":5.4,"distancePc":534.3022013250695,"distanceMethod":"parallax","constellation":"Sco","messier":null},{"id":"NGC5662","name":"NGC5662","kind":"cluster","x":-0.42816308127411395,"y":-0.34556528845918894,"z":-0.8350215609467662,"angularSizeDeg":0.13499999999999998,"magnitude":5.5,"distancePc":776.3975155279503,"distanceMethod":"parallax","constellation":"Cen","messier":null},{"id":"NGC6374","name":"NGC6374","kind":"cluster","x":-0.09279770931404681,"y":-0.8375021737479545,"z":-0.5384966983311195,"angularSizeDeg":0.115,"magnitude":5.5,"distancePc":null,"distanceMethod":null,"constellation":"Sco","messier":null},{"id":"NGC6494","name":"M23","kind":"cluster","x":-0.012049559255326978,"y":-0.945525108765827,"z":-0.325326108412964,"angularSizeDeg":0.28,"magnitude":5.5,"distancePc":738.5524372230428,"distanceMethod":"parallax","constellation":"Sgr","messier":"M23"},{"id":"NGC6273","name":"M19","kind":"cluster","x":-0.2221445333339682,"y":-0.8687830517873585,"z":-0.4425695597721081,"angularSizeDeg":0.125,"magnitude":5.57,"distancePc":10695.187165775402,"distanceMethod":"parallax","constellation":"Oph","messier":"M19"},{"id":"NGC2099","name":"M37","kind":"cluster","x":0.028292438444592147,"y":0.8424191063496056,"z":0.5380795361133807,"angularSizeDeg":0.19,"magnitude":5.6,"distancePc":1501.5015015015015,"distanceMethod":"parallax","constellation":"Aur","messier":"M37"},{"id":"NGC5460","name":"NGC5460","kind":"cluster","x":-0.5645001843641979,"y":-0.35090411727035625,"z":-0.7471317436272525,"angularSizeDeg":0.22,"magnitude":5.6,"distancePc":746.2686567164179,"distanceMethod":"parallax","constellation":"Cen","messier":null},{"id":"NGC6067","name":"NGC6067","kind":"cluster","x":-0.2627481734484313,"y":-0.5223266930397976,"z":-0.8112571867710153,"angularSizeDeg":0.13499999999999998,"magnitude":5.6,"distancePc":2257.3363431151242,"distanceMethod":"parallax","constellation":"Nor","messier":null},{"id":"NGC7686","name":"NGC7686","kind":"cluster","x":0.6487388927104301,"y":-0.08505391500664448,"z":0.7562431359205104,"angularSizeDeg":0.060000000000000005,"magnitude":5.6,"distancePc":null,"distanceMethod":null,"constellation":"And","messier":null},{"id":"NGC2808","name":"NGC2808","kind":"cluster","x":-0.31573057880571365,"y":0.28417950624962945,"z":-0.9052934385240654,"angularSizeDeg":0.15,"magnitude":5.69,"distancePc":16750.418760469012,"distanceMethod":"parallax","constellation":"Car","messier":null},{"id":"NGC0752","name":"NGC0752","kind":"cluster","x":0.6881159401773786,"y":0.38765561278612704,"z":0.6133674092656167,"angularSizeDeg":0.65,"magnitude":5.7,"distancePc":448.3500717360115,"distanceMethod":"parallax","constellation":"And","messier":null},{"id":"NGC6402","name":"M14","kind":"cluster","x":-0.09741478690542303,"y":-0.9936318985452464,"z":-0.05662163443013853,"angularSizeDeg":0.165,"magnitude":5.73,"distancePc":16977.92869269949,"distanceMethod":"parallax","constellation":"Oph","messier":"M14"},{"id":"NGC0598","name":"Triangulum Galaxy","kind":"galaxy","x":0.7890881143941509,"y":0.34248385108719315,"z":0.5099458397382695,"angularSizeDeg":1.0348333333333335,"magnitude":5.79,"distancePc":null,"distanceMethod":null,"constellation":"Tri","messier":"M33"},{"id":"NGC1788","name":"NGC1788","kind":"nebula","x":0.2292899278099273,"y":0.9716119658983978,"z":-0.058277926592913414,"angularSizeDeg":0.03333333333333333,"magnitude":5.8,"distancePc":null,"distanceMethod":null,"constellation":"Ori","messier":null},{"id":"NGC2477","name":"NGC2477","kind":"cluster","x":-0.36773374751833837,"y":0.6904215336414445,"z":-0.6229686965011281,"angularSizeDeg":0.31,"magnitude":5.8,"distancePc":1503.7593984962405,"distanceMethod":"parallax","constellation":"Pup","messier":null},{"id":"NGC2548","name":"M48","kind":"cluster","x":-0.5481442212419815,"y":0.8303605954461221,"z":-0.10019577959866073,"angularSizeDeg":0.47,"magnitude":5.8,"distancePc":775.3741180119407,"distanceMethod":"parallax","constellation":"Hya","messier":"M48"},{"id":"NGC6124","name":"NGC6124","kind":"cluster","x":-0.3045349136081267,"y":-0.6948562136051587,"z":-0.651485478585665,"angularSizeDeg":0.225,"magnitude":5.8,"distancePc":642.2607578676943,"distanceMethod":"parallax","constellation":"Sco","messier":null},{"id":"NGC6205","name":"Hercules Globular Cluster","kind":"cluster","x":-0.2694795120991914,"y":-0.7577680037113929,"z":0.5942797700662814,"angularSizeDeg":0.275,"magnitude":5.8,"distancePc":12300.123001230013,"distanceMethod":"parallax","constellation":"Her","messier":"M13"},{"id":"NGC6523","name":"Lagoon Nebula","kind":"nebula","x":0.014655668319202338,"y":-0.910708688967663,"z":-0.41278916558567524,"angularSizeDeg":0.75,"magnitude":5.8,"distancePc":null,"distanceMethod":null,"constellation":"Sgr","messier":"M8"},{"id":"NGC6705","name":"Amas de l'Ecu de Sobieski","kind":"cluster","x":0.21979957399438912,"y":-0.9694123864165629,"z":-0.10921434124706128,"angularSizeDeg":0.15,"magnitude":5.8,"distancePc":2341.92037470726,"distanceMethod":"parallax","constellation":"Sct","messier":"M11"},{"id":"NGC2169","name":"NGC2169","kind":"cluster","x":-0.03558534785879954,"y":0.969791250304264,"z":0.2413267781475162,"angularSizeDeg":0.12000000000000001,"magnitude":5.9,"distancePc":1018.3299389002037,"distanceMethod":"parallax","constellation":"Ori","messier":null},{"id":"NGC2323","name":"M50","kind":"cluster","x":-0.2672006998300256,"y":0.9525988774319325,"z":-0.14546190128609854,"angularSizeDeg":0.235,"magnitude":5.9,"distancePc":998.8014382740711,"distanceMethod":"parallax","constellation":"Mon","messier":"M50"},{"id":"NGC5281","name":"NGC5281","kind":"cluster","x":-0.40693234469459183,"y":-0.20418941012876196,"z":-0.8903441759413211,"angularSizeDeg":0.115,"magnitude":5.9,"distancePc":1658.374792703151,"distanceMethod":"parallax","constellation":"Cen","messier":null},{"id":"NGC6250","name":"NGC6250","kind":"nebula","x":-0.18604336911666255,"y":-0.6701069650298385,"z":-0.7185711657353224,"angularSizeDeg":0.16,"magnitude":5.9,"distancePc":1012.1457489878543,"distanceMethod":"parallax","constellation":"Ara","messier":null},{"id":"NGC6531","name":"M21","kind":"cluster","x":0.0170286567857565,"y":-0.9237890029236703,"z":-0.38252307502340827,"angularSizeDeg":0.1,"magnitude":5.9,"distancePc":1253.1328320802004,"distanceMethod":"parallax","constellation":"Sgr","messier":"M21"},{"id":"NGC5904","name":"M5","kind":"cluster","x":-0.647151996237669,"y":-0.7614942955664055,"z":0.03634187096757717,"angularSizeDeg":0.25,"magnitude":5.95,"distancePc":8779.63125548727,"distanceMethod":"parallax","constellation":"Se1","messier":"M5"},{"id":"IC4703","name":"Eagle Nebula","kind":"nebula","x":0.08013622726481798,"y":-0.967632369383254,"z":-0.2393027011997799,"angularSizeDeg":0.08416666666666667,"magnitude":6,"distancePc":1766.78445229682,"distanceMethod":"parallax","constellation":"Se2","messier":null},{"id":"NGC1960","name":"M36","kind":"cluster","x":0.08545219341417411,"y":0.8232382969387942,"z":0.561227787172036,"angularSizeDeg":0.12000000000000001,"magnitude":6,"distancePc":1197.6047904191616,"distanceMethod":"parallax","constellation":"Aur","messier":"M36"},{"id":"NGC2238","name":"Rosette Nebula","kind":"nebula","x":-0.13292587987419,"y":0.9872664118356865,"z":0.08738273582785602,"angularSizeDeg":1.3333333333333333,"magnitude":6,"distancePc":1562.5,"distanceMethod":"parallax","constellation":"Mon","messier":null},{"id":"NGC2301","name":"Great Bird Cluster","kind":"cluster","x":-0.223902119627563,"y":0.9745786836952448,"z":0.008014369165968864,"angularSizeDeg":0.16999999999999998,"magnitude":6,"distancePc":881.0572687224669,"distanceMethod":"parallax","constellation":"Mon","messier":null},{"id":"NGC3228","name":"NGC3228","kind":"cluster","x":-0.5629858602131779,"y":0.258436805538769,"z":-0.7850205976552109,"angularSizeDeg":0.11,"magnitude":6,"distancePc":492.0533385819022,"distanceMethod":"parallax","constellation":"Vel","messier":null},{"id":"NGC5316","name":"NGC5316","kind":"cluster","x":-0.4143966507439037,"y":-0.22489056021451456,"z":-0.881872809297711,"angularSizeDeg":0.165,"magnitude":6,"distancePc":1503.7593984962405,"distanceMethod":"parallax","constellation":"Cen","messier":null},{"id":"NGC6322","name":"NGC6322","kind":"cluster","x":-0.1320712750387813,"y":-0.7201277312703271,"z":-0.681155803737355,"angularSizeDeg":0.105,"magnitude":6,"distancePc":1373.6263736263736,"distanceMethod":"parallax","constellation":"Sco","messier":null},{"id":"NGC6605","name":"NGC6605","kind":"cluster","x":0.0688937795036676,"y":-0.963396954322082,"z":-0.25907519284685726,"angularSizeDeg":0.105,"magnitude":6,"distancePc":null,"distanceMethod":null,"constellation":"Se2","messier":null},{"id":"NGC6611","name":"Eagle Nebula","kind":"nebula","x":0.07958279539732169,"y":-0.9678377730929238,"z":-0.23865586867135286,"angularSizeDeg":2,"magnitude":6,"distancePc":1766.78445229682,"distanceMethod":"parallax","constellation":"Se2","messier":"M16"},{"id":"NGC6218","name":"M12","kind":"cluster","x":-0.311980451552753,"y":-0.9494803356942316,"z":-0.03398955690958037,"angularSizeDeg":0.185,"magnitude":6.07,"distancePc":6313.131313131313,"distanceMethod":"parallax","constellation":"Oph","messier":"M12"},{"id":"IC1287","name":"IC1287","kind":"nebula","x":0.1342823678949792,"y":-0.9730793558998105,"z":-0.1873094039126898,"angularSizeDeg":0.3333333333333333,"magnitude":6.1,"distancePc":null,"distanceMethod":null,"constellation":"Sct","messier":null},{"id":"NGC1746","name":"NGC1746","kind":"cluster","x":0.22203732964972628,"y":0.8878442928103709,"z":0.40302845552897515,"angularSizeDeg":0.3,"magnitude":6.1,"distancePc":null,"distanceMethod":null,"constellation":"Tau","messier":null},{"id":"NGC2437","name":"M46","kind":"cluster","x":-0.4153724392611078,"y":0.8729988349656912,"z":-0.2556144965584368,"angularSizeDeg":0.35,"magnitude":6.1,"distancePc":1665.278934221482,"distanceMethod":"parallax","constellation":"Pup","messier":"M46"},{"id":"NGC2669","name":"NGC2669","kind":"cluster","x":-0.3999993381682471,"y":0.4506244758603507,"z":-0.7980840251631081,"angularSizeDeg":0.14,"magnitude":6.1,"distancePc":1183.4319526627219,"distanceMethod":"parallax","constellation":"Vel","messier":null},{"id":"NGC6838","name":"M71","kind":"cluster","x":0.4509192756954104,"y":-0.8324942362410857,"z":0.3219086103721023,"angularSizeDeg":0.115,"magnitude":6.1,"distancePc":4372.540445999126,"distanceMethod":"parallax","constellation":"Sge","messier":"M71"},{"id":"NGC7160","name":"NGC7160","kind":"cluster","x":0.391995776953124,"y":-0.24098964974588585,"z":0.8878419338859096,"angularSizeDeg":0.07,"magnitude":6.1,"distancePc":952.3809523809523,"distanceMethod":"parallax","constellation":"Cep","messier":null},{"id":"NGC6656","name":"M22","kind":"cluster","x":0.14460594683301448,"y":-0.9027210181958165,"z":-0.4051961049270287,"angularSizeDeg":0.21,"magnitude":6.17,"distancePc":3789.31413414172,"distanceMethod":"parallax","constellation":"Sgr","messier":"M22"},{"id":"NGC1545","name":"NGC1545","kind":"cluster","x":0.26783531340999206,"y":0.580564344583136,"z":0.7689013504275598,"angularSizeDeg":0.07,"magnitude":6.2,"distancePc":731.528895391368,"distanceMethod":"parallax","constellation":"Per","messier":null},{"id":"NGC2447","name":"M93","kind":"cluster","x":-0.4026742910317021,"y":0.8211698164232867,"z":-0.4043928139043297,"angularSizeDeg":0.25,"magnitude":6.2,"distancePc":1041.3412475268144,"distanceMethod":"parallax","constellation":"Pup","messier":"M93"},{"id":"NGC7089","name":"M2","kind":"cluster","x":0.8023446680003661,"y":-0.596687999289276,"z":-0.0143688982021359,"angularSizeDeg":0.14,"magnitude":6.25,"distancePc":16339.869281045752,"distanceMethod":"parallax","constellation":"Aqr","messier":"M2"},{"id":"NGC6752","name":"NGC6752","kind":"cluster","x":0.15223088364209736,"y":-0.4765500785533289,"z":-0.8658670687214998,"angularSizeDeg":0.22,"magnitude":6.28,"distancePc":4189.359028068706,"distanceMethod":"parallax","constellation":"Pav","messier":null},{"id":"NGC2546","name":"NGC2546","kind":"cluster","x":-0.4322999376232725,"y":0.6640299163909587,"z":-0.6100664177519781,"angularSizeDeg":0.275,"magnitude":6.3,"distancePc":967.1179883945841,"distanceMethod":"parallax","constellation":"Pup","messier":null},{"id":"NGC5617","name":"NGC5617","kind":"cluster","x":-0.38846712995833255,"y":-0.2973669526715043,"z":-0.872161787973309,"angularSizeDeg":0.08499999999999999,"magnitude":6.3,"distancePc":2500,"distanceMethod":"parallax","constellation":"Cen","messier":null},{"id":"NGC6940","name":"NGC6940","kind":"cluster","x":0.5495356443182167,"y":-0.6881152864144198,"z":0.47382267593115845,"angularSizeDeg":0.18000000000000002,"magnitude":6.3,"distancePc":1055.9662090813094,"distanceMethod":"parallax","constellation":"Vul","messier":null},{"id":"NGC7078","name":"M15","kind":"cluster","x":0.7754629723601224,"y":-0.5951788272715809,"z":0.2107589667511917,"angularSizeDeg":0.185,"magnitude":6.3,"distancePc":19157.088122605364,"distanceMethod":"parallax","constellation":"Peg","messier":"M15"},{"id":"NGC5272","name":"M3","kind":"cluster","x":-0.7938321604421924,"y":-0.37943461714389226,"z":0.47524716975543285,"angularSizeDeg":0.26999999999999996,"magnitude":6.39,"distancePc":40816.32653061224,"distanceMethod":"parallax","constellation":"CVn","messier":"M3"},{"id":"NGC0457","name":"Owl Cluster","kind":"cluster","x":0.4942682497805973,"y":0.17878642858776012,"z":0.8507257549949094,"angularSizeDeg":0.13,"magnitude":6.4,"distancePc":3144.6540880503144,"distanceMethod":"parallax","constellation":"Cas","messier":null},{"id":"NGC1528","name":"NGC1528","kind":"cluster","x":0.2762994969346828,"y":0.5622235995031492,"z":0.7794634129677697,"angularSizeDeg":0.16,"magnitude":6.4,"distancePc":1054.8523206751056,"distanceMethod":"parallax","constellation":"Per","messier":null},{"id":"NGC1647","name":"NGC1647","kind":"cluster","x":0.3001342331088808,"y":0.89604723650245,"z":0.32713726793574954,"angularSizeDeg":0.45,"magnitude":6.4,"distancePc":597.0149253731344,"distanceMethod":"parallax","constellation":"Tau","messier":null},{"id":"NGC1662","name":"NGC1662","kind":"cluster","x":0.301444285894798,"y":0.9344394182738818,"z":0.1896162336862132,"angularSizeDeg":0.23,"magnitude":6.4,"distancePc":416.6666666666667,"distanceMethod":"parallax","constellation":"Ori","messier":null},{"id":"NGC1912","name":"M38","kind":"cluster","x":0.11031960212579359,"y":0.802959736810762,"z":0.5857347919046625,"angularSizeDeg":0.16,"magnitude":6.4,"distancePc":1144.1647597254005,"distanceMethod":"parallax","constellation":"Aur","messier":"M38"},{"id":"NGC6242","name":"NGC6242","kind":"cluster","x":-0.21424108440093426,"y":-0.741737346247843,"z":-0.6355520961620055,"angularSizeDeg":0.11,"magnitude":6.4,"distancePc":1324.5033112582782,"distanceMethod":"parallax","constellation":"Sco","messier":null},{"id":"NGC7243","name":"NGC7243","kind":"cluster","x":0.5779045136400356,"y":-0.28454282698078537,"z":0.7648932949949664,"angularSizeDeg":0.25,"magnitude":6.4,"distancePc":896.0573476702508,"distanceMethod":"parallax","constellation":"Lac","messier":null},{"id":"NGC6809","name":"M55","kind":"cluster","x":0.36236460867598236,"y":-0.7771819444796504,"z":-0.514470713990536,"angularSizeDeg":0.2,"magnitude":6.49,"distancePc":5760.36866359447,"distanceMethod":"parallax","constellation":"Sgr","messier":"M55"},{"id":"IC1805","name":"IC1805","kind":"nebula","x":0.37563746026341943,"y":0.2953105664275916,"z":0.8784578349602574,"angularSizeDeg":1,"magnitude":6.5,"distancePc":2227.1714922048996,"distanceMethod":"parallax","constellation":"Cas","messier":null},{"id":"IC1848","name":"IC1848","kind":"nebula","x":0.36242677864533573,"y":0.33554185413818155,"z":0.8695162415057445,"angularSizeDeg":0.6666666666666666,"magnitude":6.5,"distancePc":2409.6385542168678,"distanceMethod":"parallax","constellation":"Cas","messier":null},{"id":"NGC0129","name":"NGC0129","kind":"cluster","x":0.49256312539624003,"y":0.0647812085187922,"z":0.867862294677406,"angularSizeDeg":0.09000000000000001,"magnitude":6.5,"distancePc":1956.9471624266143,"distanceMethod":"parallax","constellation":"Cas","messier":null},{"id":"NGC0654","name":"NGC0654","kind":"cluster","x":0.4235903059206222,"y":0.20657706148836857,"z":0.8819847903432941,"angularSizeDeg":0.105,"magnitude":6.5,"distancePc":3194.888178913738,"distanceMethod":"parallax","constellation":"Cas","messier":null},{"id":"NGC2354","name":"NGC2354","kind":"cluster","x":-0.2862700444868024,"y":0.8544825419342029,"z":-0.4334847715424237,"angularSizeDeg":0.08,"magnitude":6.5,"distancePc":1328.0212483399735,"distanceMethod":"parallax","constellation":"CMa","messier":null},{"id":"NGC2520","name":"NGC2520","kind":"cluster","x":-0.4573248838516129,"y":0.7538730420866019,"z":-0.47173020575875924,"angularSizeDeg":0.155,"magnitude":6.5,"distancePc":null,"distanceMethod":null,"constellation":"Pup","messier":null},{"id":"NGC2539","name":"NGC2539","kind":"cluster","x":-0.5261140527746977,"y":0.8209532845603188,"z":-0.22190022091608375,"angularSizeDeg":0.20500000000000002,"magnitude":6.5,"distancePc":1326.2599469496022,"distanceMethod":"parallax","constellation":"Pup","messier":null},{"id":"NGC5822","name":"NGC5822","kind":"cluster","x":-0.4037642750457691,"y":-0.41940525533184536,"z":-0.8130643529246588,"angularSizeDeg":0.3,"magnitude":6.5,"distancePc":842.4599831508003,"distanceMethod":"parallax","constellation":"Lup","messier":null},{"id":"NGC6604","name":"NGC6604","kind":"cluster","x":0.07688438128193328,"y":-0.9742275269872003,"z":-0.21206017442060693,"angularSizeDeg":0.16,"magnitude":6.5,"distancePc":null,"distanceMethod":null,"constellation":"Se2","messier":null},{"id":"NGC6341","name":"M92","kind":"cluster","x":-0.13573248882153413,"y":-0.7169919710521602,"z":0.6837391351422342,"angularSizeDeg":0.24000000000000002,"magnitude":6.52,"distancePc":17793.59430604982,"distanceMethod":"parallax","constellation":"Her","messier":"M92"},{"id":"NGC0362","name":"NGC0362","kind":"cluster","x":0.3156620108662022,"y":0.08937863802686209,"z":-0.944652821919444,"angularSizeDeg":0.145,"magnitude":6.58,"distancePc":12674.27122940431,"distanceMethod":"parallax","constellation":"Tuc","messier":null},{"id":"NGC1444","name":"NGC1444","kind":"cluster","x":0.32708742007184344,"y":0.5108693686780174,"z":0.7950008225010018,"angularSizeDeg":0.060000000000000005,"magnitude":6.6,"distancePc":1201.923076923077,"distanceMethod":"parallax","constellation":"Per","messier":null},{"id":"NGC3572","name":"NGC3572","kind":"cluster","x":-0.48462793146419025,"y":0.10672971950836145,"z":-0.868184620353532,"angularSizeDeg":0.068,"magnitude":6.6,"distancePc":2659.574468085106,"distanceMethod":"parallax","constellation":"Car","messier":null},{"id":"NGC6169","name":"NGC6169","kind":"cluster","x":-0.2632108474395207,"y":-0.6688600097873696,"z":-0.6952311393323873,"angularSizeDeg":0.07,"magnitude":6.6,"distancePc":null,"distanceMethod":null,"constellation":"Nor","messier":null},{"id":"NGC6913","name":"M29","kind":"cluster","x":0.45985389550824907,"y":-0.6331504867413714,"z":0.6226193507473509,"angularSizeDeg":0.060000000000000005,"magnitude":6.6,"distancePc":1808.3182640144664,"distanceMethod":"parallax","constellation":"Cyg","messier":"M29"},{"id":"NGC1027","name":"NGC1027","kind":"cluster","x":0.36094443123795306,"y":0.30987071997356497,"z":0.8796017590144845,"angularSizeDeg":0.13,"magnitude":6.7,"distancePc":1133.7868480725624,"distanceMethod":"parallax","constellation":"Cas","messier":null},{"id":"NGC1342","name":"NGC1342","kind":"cluster","x":0.47913909664604404,"y":0.6339301448111382,"z":0.6070900242673587,"angularSizeDeg":0.105,"magnitude":6.7,"distancePc":665.7789613848203,"distanceMethod":"parallax","constellation":"Per","messier":null},{"id":"NGC2129","name":"NGC2129","kind":"cluster","x":-0.004443535307973005,"y":0.9182825324577092,"z":0.3959008026981291,"angularSizeDeg":0.065,"magnitude":6.7,"distancePc":2036.6598778004075,"distanceMethod":"parallax","constellation":"Gem","messier":null},{"id":"NGC2343","name":"NGC2343","kind":"cluster","x":-0.2878314679500001,"y":0.9397918905458418,"z":-0.18423964970119944,"angularSizeDeg":0.125,"magnitude":6.7,"distancePc":1113.5857461024498,"distanceMethod":"parallax","constellation":"Mon","messier":null},{"id":"NGC2423","name":"NGC2423","kind":"cluster","x":-0.3991737608565526,"y":0.8849760261414912,"z":-0.2397451601147573,"angularSizeDeg":0.19499999999999998,"magnitude":6.7,"distancePc":958.0379383023567,"distanceMethod":"parallax","constellation":"Pup","messier":null},{"id":"NGC3324","name":"NGC3324","kind":"nebula","x":-0.4871591332318817,"y":0.1839116717014581,"z":-0.8537285727447201,"angularSizeDeg":0.08,"magnitude":6.7,"distancePc":2808.9887640449438,"distanceMethod":"parallax","constellation":"Car","messier":null},{"id":"NGC6167","name":"NGC6167","kind":"cluster","x":-0.23516902120304284,"y":-0.6014939391513997,"z":-0.7634792548789621,"angularSizeDeg":0.12000000000000001,"magnitude":6.7,"distancePc":1531.3935681470136,"distanceMethod":"parallax","constellation":"Nor","messier":null},{"id":"NGC6709","name":"NGC6709","kind":"cluster","x":0.21844943844649872,"y":-0.9592676219728159,"z":0.17912418116219858,"angularSizeDeg":0.145,"magnitude":6.7,"distancePc":1097.694840834248,"distanceMethod":"parallax","constellation":"Aql","messier":null},{"id":"NGC7789","name":"NGC7789","kind":"cluster","x":0.5488667648976696,"y":-0.006224567881062811,"z":0.8358866724296184,"angularSizeDeg":0.24000000000000002,"magnitude":6.7,"distancePc":2207.5055187637968,"distanceMethod":"parallax","constellation":"Cas","messier":null},{"id":"NGC6164","name":"NGC6164","kind":"nebula","x":-0.24567709042984412,"y":-0.6212802609443134,"z":-0.744079031151189,"angularSizeDeg":0.0235,"magnitude":6.71,"distancePc":1134.6873936230568,"distanceMethod":"parallax","constellation":"Nor","messier":null},{"id":"NGC6165","name":"NGC6165","kind":"nebula","x":-0.24436517113192638,"y":-0.6208138478278689,"z":-0.744899878831256,"angularSizeDeg":0.041666666666666664,"magnitude":6.71,"distancePc":1134.6873936230568,"distanceMethod":"parallax","constellation":"Nor","messier":null},{"id":"NGC2175","name":"NGC2175","kind":"nebula","x":-0.039468557963842854,"y":0.9359162159470508,"z":0.3500043852005404,"angularSizeDeg":0.09000000000000001,"magnitude":6.8,"distancePc":null,"distanceMethod":null,"constellation":"Ori","messier":null},{"id":"NGC6811","name":"NGC6811","kind":"cluster","x":0.2841165785962234,"y":-0.6285280814262626,"z":0.7240374442150057,"angularSizeDeg":0.12000000000000001,"magnitude":6.8,"distancePc":1149.4252873563219,"distanceMethod":"parallax","constellation":"Cyg","messier":null},{"id":"IC4651","name":"IC4651","kind":"cluster","x":-0.09841091783010028,"y":-0.6360446295674965,"z":-0.7653512399220266,"angularSizeDeg":0.16,"magnitude":6.9,"distancePc":948.5866059571239,"distanceMethod":"parallax","constellation":"Ara","messier":null},{"id":"NGC1502","name":"NGC1502","kind":"cluster","x":0.21832032850414035,"y":0.40983117468277447,"z":0.8856492773214357,"angularSizeDeg":0.16999999999999998,"magnitude":6.9,"distancePc":1091.703056768559,"distanceMethod":"parallax","constellation":"Cam","messier":null},{"id":"NGC2439","name":"NGC2439","kind":"cluster","x":-0.36214234335153017,"y":0.7699680540364746,"z":-0.5253590381017033,"angularSizeDeg":0.145,"magnitude":6.9,"distancePc":4310.3448275862065,"distanceMethod":"parallax","constellation":"Pup","messier":null},{"id":"NGC2682","name":"M67","kind":"cluster","x":-0.6654784565790319,"y":0.717799615541802,"z":0.2047001117665319,"angularSizeDeg":0.55,"magnitude":6.9,"distancePc":883.0022075055188,"distanceMethod":"parallax","constellation":"Cnc","messier":"M67"},{"id":"NGC4609","name":"Coalsack Cluster","kind":"cluster","x":-0.4463517633905793,"y":-0.08329159891211932,"z":-0.8909728463139508,"angularSizeDeg":0.09000000000000001,"magnitude":6.9,"distancePc":1515.151515151515,"distanceMethod":"parallax","constellation":"Cru","messier":null},{"id":"NGC6613","name":"M18","kind":"cluster","x":0.08319741200892534,"y":-0.9521549920505711,"z":-0.29407322514672113,"angularSizeDeg":0.1,"magnitude":6.9,"distancePc":1562.5,"distanceMethod":"parallax","constellation":"Sgr","messier":"M18"},{"id":"NGC6626","name":"M28","kind":"cluster","x":0.09699293802265645,"y":-0.9020660614898797,"z":-0.42055818941244,"angularSizeDeg":0.08499999999999999,"magnitude":6.9,"distancePc":6410.25641025641,"distanceMethod":"parallax","constellation":"Sgr","messier":"M28"},{"id":"NGC7654","name":"M52","kind":"cluster","x":0.4701311314294211,"y":-0.07276614116322959,"z":0.8795918416863047,"angularSizeDeg":0.165,"magnitude":6.9,"distancePc":1677.8523489932886,"distanceMethod":"parallax","constellation":"Cas","messier":"M52"},{"id":"NGC3031","name":"Bode's Galaxy","kind":"galaxy","x":-0.3059093437742947,"y":0.18462219185731024,"z":0.9339882866853219,"angularSizeDeg":0.3605,"magnitude":6.92,"distancePc":null,"distanceMethod":null,"constellation":"UMa","messier":"M81"},{"id":"NGC5986","name":"NGC5986","kind":"cluster","x":-0.43603272424263684,"y":-0.6591317880435406,"z":-0.6127158798171218,"angularSizeDeg":0.09000000000000001,"magnitude":6.92,"distancePc":13054.830287206267,"distanceMethod":"parallax","constellation":"Lup","messier":null},{"id":"NGC0225","name":"NGC0225","kind":"cluster","x":0.4645220671505197,"y":0.08946616352227596,"z":0.8810306775106151,"angularSizeDeg":0.07,"magnitude":7,"distancePc":698.3240223463688,"distanceMethod":"parallax","constellation":"Cas","messier":null},{"id":"NGC1582","name":"NGC1582","kind":"cluster","x":0.2710876798423652,"y":0.6691141680097955,"z":0.6919520937219877,"angularSizeDeg":0.13,"magnitude":7,"distancePc":1006.0362173038229,"distanceMethod":"parallax","constellation":"Per","messier":null},{"id":"NGC1807","name":"NGC1807","kind":"cluster","x":0.20444658798062915,"y":0.9367046689689912,"z":0.2842287033266898,"angularSizeDeg":0.09000000000000001,"magnitude":7,"distancePc":null,"distanceMethod":null,"constellation":"Tau","messier":null},{"id":"NGC1857","name":"NGC1857","kind":"cluster","x":0.13398423128844963,"y":0.7616630757707601,"z":0.6339697033561372,"angularSizeDeg":0.075,"magnitude":7,"distancePc":3125,"distanceMethod":"parallax","constellation":"Aur","messier":null},{"id":"NGC1973","name":"NGC1973","kind":"nebula","x":0.10815145930717339,"y":0.9907060382511412,"z":-0.08249125785473943,"angularSizeDeg":0.08333333333333333,"magnitude":7,"distancePc":null,"distanceMethod":null,"constellation":"Ori","messier":null},{"id":"NGC1975","name":"NGC1975","kind":"nebula","x":0.10721478056834084,"y":0.9908749321559317,"z":-0.08168145231728105,"angularSizeDeg":0.16666666666666666,"magnitude":7,"distancePc":null,"distanceMethod":null,"constellation":"Ori","messier":null},{"id":"NGC2571","name":"NGC2571","kind":"cluster","x":-0.494684710802874,"y":0.713489368713589,"z":-0.4962055598545437,"angularSizeDeg":0.12000000000000001,"magnitude":7,"distancePc":1366.120218579235,"distanceMethod":"parallax","constellation":"Pup","messier":null},{"id":"NGC6618","name":"Checkmark Nebula","kind":"nebula","x":0.0869845430058914,"y":-0.9564850830247957,"z":-0.27851386900674413,"angularSizeDeg":0.21,"magnitude":7,"distancePc":1666.6666666666667,"distanceMethod":"parallax","constellation":"Sgr","messier":"M17"},{"id":"NGC6960","name":"Veil Nebula","kind":"nebula","x":0.5702882064263252,"y":-0.6447654946959821,"z":0.5089683865040052,"angularSizeDeg":3.5,"magnitude":7,"distancePc":null,"distanceMethod":null,"constellation":"Cyg","messier":null},{"id":"NGC6992","name":"Eastern Veil","kind":"nebula","x":0.5915983388122461,"y":-0.6109202457524266,"z":0.5261063189550051,"angularSizeDeg":1,"magnitude":7,"distancePc":null,"distanceMethod":null,"constellation":"Cyg","messier":null},{"id":"NGC6995","name":"Eastern Veil","kind":"nebula","x":0.5971221138992852,"y":-0.6120041629225287,"z":0.5185519122112502,"angularSizeDeg":0.2,"magnitude":7,"distancePc":null,"distanceMethod":null,"constellation":"Cyg","messier":null},{"id":"NGC7063","name":"NGC7063","kind":"cluster","x":0.6256126177296458,"y":-0.5049785384641582,"z":0.5946473982353432,"angularSizeDeg":0.105,"magnitude":7,"distancePc":677.0480704129993,"distanceMethod":"parallax","constellation":"Cyg","messier":null},{"id":"IC0444","name":"IC0444","kind":"nebula","x":-0.0743167030753604,"y":0.9153423754202822,"z":0.3957592240289087,"angularSizeDeg":0.13333333333333333,"magnitude":7.03,"distancePc":null,"distanceMethod":null,"constellation":"Gem","messier":null},{"id":"NGC0663","name":"NGC0663","kind":"cluster","x":0.4306378138255401,"y":0.21533604211067434,"z":0.876459618163706,"angularSizeDeg":0.1,"magnitude":7.1,"distancePc":3125,"distanceMethod":"parallax","constellation":"Cas","messier":null},{"id":"NGC2353","name":"NGC2353","kind":"cluster","x":-0.3142811303100915,"y":0.9324518467759702,"z":-0.17821594927248835,"angularSizeDeg":0.11,"magnitude":7.1,"distancePc":1225.4901960784314,"distanceMethod":"parallax","constellation":"Mon","messier":null},{"id":"NGC6823","name":"NGC6823","kind":"nebula","x":0.39960970881908836,"y":-0.8269561902094756,"z":0.3955446120120369,"angularSizeDeg":0.1,"magnitude":7.1,"distancePc":2242.152466367713,"distanceMethod":"parallax","constellation":"Vul","messier":null},{"id":"NGC7099","name":"M30","kind":"cluster","x":0.7538727311240706,"y":-0.52607029453244,"z":-0.39360633947903645,"angularSizeDeg":0.15,"magnitude":7.1,"distancePc":14792.899408284025,"distanceMethod":"parallax","constellation":"Cap","messier":"M30"},{"id":"IC5146","name":"Cocoon Nebula","kind":"nebula","x":0.5777809003168345,"y":-0.3558726581966425,"z":0.7345228943859571,"angularSizeDeg":0.16666666666666666,"magnitude":7.2,"distancePc":824.4023083264633,"distanceMethod":"parallax","constellation":"Cyg","messier":null},{"id":"NGC2335","name":"NGC2335","kind":"cluster","x":-0.2830693452541233,"y":0.9431579216023199,"z":-0.17414040512240125,"angularSizeDeg":0.145,"magnitude":7.2,"distancePc":1709.4017094017095,"distanceMethod":"parallax","constellation":"Mon","messier":null},{"id":"NGC2360","name":"Caroline's Cluster","kind":"cluster","x":-0.3203309437684844,"y":0.9081279189384475,"z":-0.2696141118504681,"angularSizeDeg":0.15,"magnitude":7.2,"distancePc":1108.8933244621867,"distanceMethod":"parallax","constellation":"CMa","messier":null},{"id":"NGC2910","name":"NGC2910","kind":"cluster","x":-0.47917608474394807,"y":0.3660806719092136,"z":-0.7977312965303288,"angularSizeDeg":0.08,"magnitude":7.2,"distancePc":1340.4825737265417,"distanceMethod":"parallax","constellation":"Vel","messier":null},{"id":"NGC4463","name":"NGC4463","kind":"cluster","x":-0.42231777445279056,"y":-0.055449849697527955,"z":-0.9047502481623111,"angularSizeDeg":0.08,"magnitude":7.2,"distancePc":1912.0458891013384,"distanceMethod":"parallax","constellation":"Mus","messier":null},{"id":"NGC6134","name":"NGC6134","kind":"cluster","x":-0.25615473063228417,"y":-0.6018193288996115,"z":-0.7564378687886555,"angularSizeDeg":0.115,"magnitude":7.2,"distancePc":1182.033096926714,"distanceMethod":"parallax","constellation":"Nor","messier":null},{"id":"NGC6178","name":"NGC6178","kind":"cluster","x":-0.2511463965192171,"y":-0.6524499030572369,"z":-0.7150067213082785,"angularSizeDeg":0.115,"magnitude":7.2,"distancePc":909.9181073703367,"distanceMethod":"parallax","constellation":"Sco","messier":null},{"id":"NGC6208","name":"NGC6208","kind":"cluster","x":-0.17920668749215635,"y":-0.5638197929239224,"z":-0.8062209401059451,"angularSizeDeg":0.14,"magnitude":7.2,"distancePc":1204.8192771084339,"distanceMethod":"parallax","constellation":"Ara","messier":null},{"id":"NGC6235","name":"NGC6235","kind":"cluster","x":-0.26523950692839177,"y":-0.8872201917173268,"z":-0.37747627127205435,"angularSizeDeg":0.07,"magnitude":7.2,"distancePc":15923.566878980893,"distanceMethod":"parallax","constellation":"Oph","messier":null},{"id":"NGC6425","name":"NGC6425","kind":"cluster","x":-0.048219377536590204,"y":-0.8510070479127756,"z":-0.5229358431325649,"angularSizeDeg":0.08,"magnitude":7.2,"distancePc":1013.1712259371834,"distanceMethod":"parallax","constellation":"Sco","messier":null},{"id":"NGC7023","name":"Iris Nebula","kind":"nebula","x":0.2647676012302222,"y":-0.26111075112598375,"z":0.9282883673650223,"angularSizeDeg":0.16666666666666666,"magnitude":7.2,"distancePc":null,"distanceMethod":null,"constellation":"Cep","messier":null},{"id":"NGC7082","name":"NGC7082","kind":"cluster","x":0.5385101202645189,"y":-0.4158489692272073,"z":0.7328550232927127,"angularSizeDeg":0.15,"magnitude":7.2,"distancePc":1371.7421124828534,"distanceMethod":"parallax","constellation":"Cyg","messier":null},{"id":"NGC7380","name":"NGC7380","kind":"nebula","x":0.5016532082110134,"y":-0.16457103352536914,"z":0.8492705302881935,"angularSizeDeg":0.4166666666666667,"magnitude":7.2,"distancePc":3003.003003003003,"distanceMethod":"parallax","constellation":"Cep","messier":null},{"id":"NGC5236","name":"Southern Pinwheel Galaxy","kind":"galaxy","x":-0.7906531332128867,"y":-0.35622897568222234,"z":-0.4979643961419731,"angularSizeDeg":0.22683333333333333,"magnitude":7.21,"distancePc":null,"distanceMethod":null,"constellation":"Hya","messier":"M83"},{"id":"NGC5128","name":"Centaurus A","kind":"galaxy","x":-0.6808817279227569,"y":-0.26635608200232336,"z":-0.6822422664723231,"angularSizeDeg":0.4313333333333333,"magnitude":7.22,"distancePc":null,"distanceMethod":null,"constellation":"Cen","messier":null},{"id":"NGC1851","name":"NGC1851","kind":"cluster","x":0.15225323671014238,"y":0.7502278107187043,"z":-0.643410587366656,"angularSizeDeg":0.15,"magnitude":7.23,"distancePc":34722.222222222226,"distanceMethod":"parallax","constellation":"Col","messier":null},{"id":"NGC2070","name":"30 Dor Cluster","kind":"nebula","x":0.03309644249820051,"y":0.3551848606317894,"z":-0.9342100086554092,"angularSizeDeg":0.26666666666666666,"magnitude":7.25,"distancePc":null,"distanceMethod":null,"constellation":"Dor","messier":null},{"id":"IC4996","name":"IC4996","kind":"cluster","x":0.4448977764867137,"y":-0.6561580187179042,"z":0.6095265563938724,"angularSizeDeg":0.1,"magnitude":7.3,"distancePc":2053.388090349076,"distanceMethod":"parallax","constellation":"Cyg","messier":null},{"id":"NGC2251","name":"NGC2251","kind":"cluster","x":-0.1489739802399624,"y":0.9780775662731742,"z":0.14550267201879005,"angularSizeDeg":0.095,"magnitude":7.3,"distancePc":1519.756838905775,"distanceMethod":"parallax","constellation":"Mon","messier":null},{"id":"NGC2409","name":"NGC2409","kind":"cluster","x":-0.3717870087208958,"y":0.8800145697589419,"z":-0.29554826536177303,"angularSizeDeg":0.0018333333333333333,"magnitude":7.3,"distancePc":1314.060446780552,"distanceMethod":"parallax","constellation":"Pup","messier":null},{"id":"NGC2482","name":"NGC2482","kind":"cluster","x":-0.4391339843603334,"y":0.799006085821454,"z":-0.41079267106424594,"angularSizeDeg":0.11,"magnitude":7.3,"distancePc":1388.888888888889,"distanceMethod":"parallax","constellation":"Pup","messier":null},{"id":"NGC6093","name":"M80","kind":"cluster","x":-0.399831297451063,"y":-0.8293228948391729,"z":-0.39033123071844217,"angularSizeDeg":0.095,"magnitude":7.3,"distancePc":19960.079840319362,"distanceMethod":"parallax","constellation":"Sco","messier":"M80"},{"id":"NGC6819","name":"Foxhead Cluster","kind":"cluster","x":0.32678387000436854,"y":-0.690524933916128,"z":0.6452810379556297,"angularSizeDeg":0.115,"magnitude":7.3,"distancePc":2808.9887640449438,"distanceMethod":"parallax","constellation":"Cyg","messier":null},{"id":"NGC7293","name":"Helix Nebula","kind":"nebula","x":0.8628939268078679,"y":-0.35899887402470043,"z":-0.35571600965811934,"angularSizeDeg":0.2721666666666666,"magnitude":7.3,"distancePc":200.96463022508038,"distanceMethod":"parallax","constellation":"Aqr","messier":null},{"id":"NGC2645","name":"NGC2645","kind":"cluster","x":-0.44248401114280345,"y":0.5317837236953106,"z":-0.7220900020743432,"angularSizeDeg":0.11,"magnitude":7.32,"distancePc":1912.0458891013384,"distanceMethod":"parallax","constellation":"Vel","messier":null},{"id":"NGC6541","name":"NGC6541","kind":"cluster","x":0.025346895965503134,"y":-0.7223309473533782,"z":-0.6910828730047396,"angularSizeDeg":0.125,"magnitude":7.32,"distancePc":8257.638315441784,"distanceMethod":"parallax","constellation":"CrA","messier":null},{"id":"NGC6266","name":"M62","kind":"cluster","x":-0.21947507591521928,"y":-0.8367379726707951,"z":-0.50169737506068,"angularSizeDeg":0.13,"magnitude":7.39,"distancePc":6891.798759476223,"distanceMethod":"parallax","constellation":"Oph","messier":"M62"},{"id":"IC2488","name":"IC2488","kind":"cluster","x":-0.42857001948942713,"y":0.3359296027429585,"z":-0.8387365738989746,"angularSizeDeg":0.12000000000000001,"magnitude":7.4,"distancePc":1392.757660167131,"distanceMethod":"parallax","constellation":"Vel","messier":null},{"id":"NGC0581","name":"M103","kind":"cluster","x":0.44992015676303726,"y":0.19414685619176028,"z":0.871710302089622,"angularSizeDeg":0.075,"magnitude":7.4,"distancePc":2680.9651474530833,"distanceMethod":"parallax","constellation":"Cas","messier":"M103"},{"id":"NGC2384","name":"NGC2384","kind":"cluster","x":-0.3389418455805133,"y":0.8697463999059208,"z":-0.358691545990654,"angularSizeDeg":0.08,"magnitude":7.4,"distancePc":null,"distanceMethod":null,"constellation":"CMa","messier":null},{"id":"NGC2396","name":"NGC2396","kind":"cluster","x":-0.36698999795129683,"y":0.9077770793944073,"z":-0.20312339975952337,"angularSizeDeg":0.11,"magnitude":7.4,"distancePc":1531.3935681470136,"distanceMethod":"parallax","constellation":"Pup","messier":null},{"id":"NGC2567","name":"NGC2567","kind":"cluster","x":-0.48916289117001543,"y":0.7078500300927546,"z":-0.5095762953669236,"angularSizeDeg":0.1,"magnitude":7.4,"distancePc":1845.0184501845017,"distanceMethod":"parallax","constellation":"Pup","messier":null},{"id":"NGC3330","name":"NGC3330","kind":"cluster","x":-0.5495066939140246,"y":0.20338545974920788,"z":-0.8103559391442021,"angularSizeDeg":0.08499999999999999,"magnitude":7.4,"distancePc":1811.5942028985505,"distanceMethod":"parallax","constellation":"Vel","messier":null},{"id":"NGC4103","name":"NGC4103","kind":"cluster","x":-0.48078444927158154,"y":-0.0139743516204585,"z":-0.8767274552763874,"angularSizeDeg":0.13,"magnitude":7.4,"distancePc":2114.1649048625795,"distanceMethod":"parallax","constellation":"Cru","messier":null},{"id":"NGC4349","name":"NGC4349","kind":"cluster","x":-0.46886241984893345,"y":-0.04948765443854691,"z":-0.881883781068443,"angularSizeDeg":0.105,"magnitude":7.4,"distancePc":2040.8163265306123,"distanceMethod":"parallax","constellation":"Cru","messier":null},{"id":"NGC6200","name":"NGC6200","kind":"cluster","x":-0.2197653058000277,"y":-0.6393547418335102,"z":-0.7368369727840928,"angularSizeDeg":0.13499999999999998,"magnitude":7.4,"distancePc":null,"distanceMethod":null,"constellation":"Ara","messier":null},{"id":"NGC6388","name":"NGC6388","kind":"cluster","x":-0.07335752131929665,"y":-0.706564268257724,"z":-0.7038363509277679,"angularSizeDeg":0.14,"magnitude":7.4,"distancePc":12406.947890818858,"distanceMethod":"parallax","constellation":"Sco","messier":null},{"id":"NGC6853","name":"Dumbbell Nebula","kind":"nebula","x":0.4598253926331911,"y":-0.799609732219643,"z":0.3862445914564531,"angularSizeDeg":0.11166666666666666,"magnitude":7.4,"distancePc":376.2935089369708,"distanceMethod":"parallax","constellation":"Vul","messier":"M27"},{"id":"NGC6910","name":"NGC6910","kind":"cluster","x":0.44295495654380695,"y":-0.6141674870819979,"z":0.6531379672662264,"angularSizeDeg":0.105,"magnitude":7.4,"distancePc":1834.8623853211009,"distanceMethod":"parallax","constellation":"Cyg","messier":null},{"id":"NGC6356","name":"NGC6356","kind":"cluster","x":-0.15064499491545363,"y":-0.9400659882496488,"z":-0.30591178997079244,"angularSizeDeg":0.09000000000000001,"magnitude":7.42,"distancePc":11389.521640091116,"distanceMethod":"parallax","constellation":"Oph","messier":null},{"id":"NGC6284","name":"NGC6284","kind":"cluster","x":-0.2178317988611357,"y":-0.8815231504862003,"z":-0.41888690903608533,"angularSizeDeg":0.11,"magnitude":7.43,"distancePc":18450.18450184502,"distanceMethod":"parallax","constellation":"Oph","messier":null},{"id":"NGC6888","name":"Crescent Nebula","kind":"nebula","x":0.4274091647750824,"y":-0.6574665698654809,"z":0.6205313169982519,"angularSizeDeg":0.3333333333333333,"magnitude":7.44,"distancePc":null,"distanceMethod":null,"constellation":"Cyg","messier":null},{"id":"NGC1893","name":"NGC1893","kind":"cluster","x":0.13512715701934425,"y":0.8237224289844305,"z":0.5506559828276266,"angularSizeDeg":0.1,"magnitude":7.5,"distancePc":3745.318352059925,"distanceMethod":"parallax","constellation":"Aur","messier":null},{"id":"NGC2286","name":"NGC2286","kind":"cluster","x":-0.2061893688853416,"y":0.9769702600878892,"z":-0.05490951704820016,"angularSizeDeg":0.09000000000000001,"magnitude":7.5,"distancePc":2457.002457002457,"distanceMethod":"parallax","constellation":"Mon","messier":null},{"id":"NGC6716","name":"NGC6716","kind":"cluster","x":0.22178897101428238,"y":-0.913750135887748,"z":-0.340397328869584,"angularSizeDeg":0.12000000000000001,"magnitude":7.5,"distancePc":711.7437722419928,"distanceMethod":"parallax","constellation":"Sgr","messier":null},{"id":"NGC6755","name":"NGC6755","kind":"cluster","x":0.29080202267664784,"y":-0.9538866195838439,"z":0.0743942241446015,"angularSizeDeg":0.1,"magnitude":7.5,"distancePc":2659.574468085106,"distanceMethod":"parallax","constellation":"Aql","messier":null},{"id":"NGC0957","name":"NGC0957","kind":"cluster","x":0.42068464827652474,"y":0.33258606658588236,"z":0.8440443916153864,"angularSizeDeg":0.16999999999999998,"magnitude":7.6,"distancePc":2469.135802469136,"distanceMethod":"parallax","constellation":"Per","messier":null},{"id":"NGC1664","name":"NGC1664","kind":"cluster","x":0.21420224987779435,"y":0.6908070817825617,"z":0.6905816185697039,"angularSizeDeg":0.19,"magnitude":7.6,"distancePc":1362.3978201634877,"distanceMethod":"parallax","constellation":"Aur","messier":null},{"id":"NGC2483","name":"NGC2483","kind":"cluster","x":-0.42731730744192364,"y":0.7737128941063822,"z":-0.46772671107614827,"angularSizeDeg":0.055,"magnitude":7.6,"distancePc":null,"distanceMethod":null,"constellation":"Pup","messier":null},{"id":"NGC2506","name":"NGC2506","kind":"cluster","x":-0.4913033274399913,"y":0.8507079945408599,"z":-0.18686077295853476,"angularSizeDeg":0.18000000000000002,"magnitude":7.6,"distancePc":3424.657534246576,"distanceMethod":"parallax","constellation":"Mon","messier":null},{"id":"NGC2533","name":"NGC2533","kind":"cluster","x":-0.4564668180224975,"y":0.7371508824876853,"z":-0.4982435353238843,"angularSizeDeg":0.083,"magnitude":7.6,"distancePc":3039.51367781155,"distanceMethod":"parallax","constellation":"Pup","messier":null},{"id":"NGC3247","name":"NGC3247","kind":"nebula","x":-0.4875219460570511,"y":0.2164642831899578,"z":-0.8458519765395145,"angularSizeDeg":0.08333333333333333,"magnitude":7.6,"distancePc":null,"distanceMethod":null,"constellation":"Car","messier":null},{"id":"NGC3680","name":"NGC3680","kind":"cluster","x":-0.7201886825351094,"y":0.10886044129029968,"z":-0.6851844028219153,"angularSizeDeg":0.095,"magnitude":7.6,"distancePc":1071.8113612004286,"distanceMethod":"parallax","constellation":"Cen","messier":null},{"id":"NGC6520","name":"NGC6520","kind":"cluster","x":0.013121126305642538,"y":-0.8837816370138911,"z":-0.467715569680461,"angularSizeDeg":0.09000000000000001,"magnitude":7.6,"distancePc":1798.5611510791366,"distanceMethod":"parallax","constellation":"Sgr","messier":null},{"id":"NGC6866","name":"NGC6866","kind":"cluster","x":0.36927684036482633,"y":-0.615068116202088,"z":0.6966533051682058,"angularSizeDeg":0.08499999999999999,"magnitude":7.6,"distancePc":1457.7259475218657,"distanceMethod":"parallax","constellation":"Cyg","messier":null},{"id":"NGC7039","name":"NGC7039","kind":"cluster","x":0.517284763699231,"y":-0.4707066720374081,"z":0.7147389048765279,"angularSizeDeg":0.13,"magnitude":7.6,"distancePc":773.3952049497293,"distanceMethod":"parallax","constellation":"Cyg","messier":null},{"id":"IC0447","name":"IC0447","kind":"nebula","x":-0.13286665831022215,"y":0.9761157423145809,"z":0.17188516141636379,"angularSizeDeg":0.4166666666666667,"magnitude":7.7,"distancePc":null,"distanceMethod":null,"constellation":"Mon","messier":null},{"id":"IC1284","name":"IC1284","kind":"nebula","x":0.07248916110192435,"y":-0.938840670275189,"z":-0.33663558540352373,"angularSizeDeg":0.28300000000000003,"magnitude":7.7,"distancePc":null,"distanceMethod":null,"constellation":"Sgr","messier":null},{"id":"NGC1778","name":"NGC1778","kind":"cluster","x":0.17927745420380176,"y":0.7780071655252341,"z":0.6021332450592599,"angularSizeDeg":0.075,"magnitude":7.7,"distancePc":1721.1703958691912,"distanceMethod":"parallax","constellation":"Aur","messier":null},{"id":"NGC1817","name":"NGC1817","kind":"cluster","x":0.1973687266691095,"y":0.937348588045258,"z":0.2870944273624545,"angularSizeDeg":0.155,"magnitude":7.7,"distancePc":1814.8820326678765,"distanceMethod":"parallax","constellation":"Tau","messier":null},{"id":"NGC2252","name":"NGC2252","kind":"cluster","x":-0.15023786988573531,"y":0.9842165633964692,"z":0.09352186262174708,"angularSizeDeg":0.11,"magnitude":7.7,"distancePc":675.995403231258,"distanceMethod":"parallax","constellation":"Mon","messier":null},{"id":"NGC2345","name":"NGC2345","kind":"cluster","x":-0.28592618556980215,"y":0.9306721162905272,"z":-0.22824466776863506,"angularSizeDeg":0.115,"magnitude":7.7,"distancePc":2873.5632183908046,"distanceMethod":"parallax","constellation":"CMa","messier":null},{"id":"NGC3242","name":"Jupiter's Ghost Nebula","kind":"nebula","x":-0.866901178414325,"y":0.3824935741192227,"z":-0.3196576490893925,"angularSizeDeg":0.007,"magnitude":7.7,"distancePc":1466.4906877841327,"distanceMethod":"parallax","constellation":"Hya","messier":null},{"id":"NGC3519","name":"NGC3519","kind":"cluster","x":-0.4649680102122966,"y":0.1158300646506712,"z":-0.8777175773574544,"angularSizeDeg":0.08,"magnitude":7.7,"distancePc":2293.5779816513764,"distanceMethod":"parallax","constellation":"Car","messier":null},{"id":"NGC5606","name":"NGC5606","kind":"cluster","x":-0.4040300088915653,"y":-0.3038728953247934,"z":-0.8627983631196856,"angularSizeDeg":0.060000000000000005,"magnitude":7.7,"distancePc":2673.7967914438505,"distanceMethod":"parallax","constellation":"Cen","messier":null},{"id":"NGC6715","name":"M54","kind":"cluster","x":0.20504141174984333,"y":-0.837072851463048,"z":-0.5072150045209163,"angularSizeDeg":0.08499999999999999,"magnitude":7.7,"distancePc":37453.18352059925,"distanceMethod":"parallax","constellation":"Sgr","messier":"M54"},{"id":"NGC7209","name":"NGC7209","kind":"cluster","x":0.6038704100568503,"y":-0.3308468747893323,"z":0.7251764428743555,"angularSizeDeg":0.1,"magnitude":7.7,"distancePc":1219.5121951219512,"distanceMethod":"parallax","constellation":"Lac","messier":null},{"id":"NGC7234","name":"NGC7234","kind":"cluster","x":0.4821785407117109,"y":-0.24457816522749634,"z":0.8412403794225979,"angularSizeDeg":0.04,"magnitude":7.7,"distancePc":null,"distanceMethod":null,"constellation":"Cep","messier":null},{"id":"NGC4833","name":"NGC4833","kind":"cluster","x":-0.31662710139477585,"y":-0.0842218228355864,"z":-0.9448036638479941,"angularSizeDeg":0.14,"magnitude":7.79,"distancePc":8123.4768480909825,"distanceMethod":"parallax","constellation":"Mus","messier":null},{"id":"NGC5024","name":"M53","kind":"cluster","x":-0.9024505709847364,"y":-0.2972365242266607,"z":0.3118227310427787,"angularSizeDeg":0.15,"magnitude":7.79,"distancePc":63694.26751592357,"distanceMethod":"parallax","constellation":"Com","messier":"M53"},{"id":"NGC2670","name":"NGC2670","kind":"cluster","x":-0.43543740253403695,"y":0.49437912898051894,"z":-0.7523187790444118,"angularSizeDeg":0.07,"magnitude":7.8,"distancePc":1545.5950540958268,"distanceMethod":"parallax","constellation":"Vel","messier":null},{"id":"NGC6664","name":"NGC6664","kind":"cluster","x":0.15719737401021033,"y":-0.9771609874841086,"z":-0.1429873775666081,"angularSizeDeg":0.1,"magnitude":7.8,"distancePc":2136.7521367521367,"distanceMethod":"parallax","constellation":"Sct","messier":null},{"id":"NGC6834","name":"NGC6834","kind":"cluster","x":0.4096796405142184,"y":-0.7688004726696303,"z":0.49102792728224093,"angularSizeDeg":0.075,"magnitude":7.8,"distancePc":3676.4705882352937,"distanceMethod":"parallax","constellation":"Cyg","messier":null},{"id":"NGC6939","name":"NGC6939","kind":"cluster","x":0.3008097746728914,"y":-0.3867476521379331,"z":0.8717452225432845,"angularSizeDeg":0.2,"magnitude":7.8,"distancePc":1976.2845849802372,"distanceMethod":"parallax","constellation":"Cep","messier":null},{"id":"NGC0659","name":"NGC0659","kind":"cluster","x":0.4399161432699264,"y":0.2154726300726123,"z":0.8718057883382656,"angularSizeDeg":0.07,"magnitude":7.9,"distancePc":3484.3205574912895,"distanceMethod":"parallax","constellation":"Cas","messier":null},{"id":"NGC0744","name":"NGC0744","kind":"cluster","x":0.4926842614206407,"y":0.28016424106531296,"z":0.8238751219551995,"angularSizeDeg":0.19499999999999998,"magnitude":7.9,"distancePc":1371.7421124828534,"distanceMethod":"parallax","constellation":"Per","messier":null},{"id":"NGC2367","name":"NGC2367","kind":"cluster","x":-0.3176619882148523,"y":0.8718733828590736,"z":-0.372730016909374,"angularSizeDeg":0.09000000000000001,"magnitude":7.9,"distancePc":2531.645569620253,"distanceMethod":"parallax","constellation":"CMa","messier":null},{"id":"NGC2414","name":"NGC2414","kind":"cluster","x":-0.38129619045013446,"y":0.8852180844457317,"z":-0.26646230149581057,"angularSizeDeg":0.09000000000000001,"magnitude":7.9,"distancePc":6172.83950617284,"distanceMethod":"parallax","constellation":"Pup","messier":null},{"id":"NGC2489","name":"NGC2489","kind":"cluster","x":-0.4204220234835698,"y":0.7565218197132119,"z":-0.5009192135143063,"angularSizeDeg":0.07,"magnitude":7.9,"distancePc":2150.537634408602,"distanceMethod":"parallax","constellation":"Pup","messier":null},{"id":"NGC5457","name":"M101","kind":"galaxy","x":-0.5006307218472164,"y":-0.2984624524756317,"z":0.8125817157707688,"angularSizeDeg":0.3998333333333333,"magnitude":7.9,"distancePc":null,"distanceMethod":null,"constellation":"UMa","messier":"M101"},{"id":"NGC5823","name":"NGC5823","kind":"cluster","x":-0.38973477304989274,"y":-0.4089419307601806,"z":-0.8251504735149124,"angularSizeDeg":0.065,"magnitude":7.9,"distancePc":1919.3857965451054,"distanceMethod":"parallax","constellation":"Cir","messier":null},{"id":"NGC6830","name":"NGC6830","kind":"cluster","x":0.4282569391097828,"y":-0.8140428928743809,"z":0.39233934631263917,"angularSizeDeg":0.07,"magnitude":7.9,"distancePc":2358.490566037736,"distanceMethod":"parallax","constellation":"Vul","messier":null},{"id":"NGC7510","name":"NGC7510","kind":"cluster","x":0.4801875679749055,"y":-0.10412072827035308,"z":0.8709642779740148,"angularSizeDeg":0.065,"magnitude":7.9,"distancePc":3496.503496503497,"distanceMethod":"parallax","constellation":"Cep","messier":null},{"id":"NGC4590","name":"M68","kind":"cluster","x":-0.8798248996971865,"y":-0.153027286328659,"z":-0.4499897726745826,"angularSizeDeg":0.11,"magnitude":7.96,"distancePc":16181.229773462783,"distanceMethod":"parallax","constellation":"Hya","messier":"M68"},{"id":"IC5070","name":"Pelican Nebula","kind":"nebula","x":0.48499962315803613,"y":-0.5246145263589382,"z":0.6996820451246051,"angularSizeDeg":1,"magnitude":8,"distancePc":null,"distanceMethod":null,"constellation":"Cyg","messier":null},{"id":"NGC2068","name":"M78","kind":"nebula","x":0.0577222421369993,"y":0.998331721880003,"z":0.0013841426175988907,"angularSizeDeg":0.075,"magnitude":8,"distancePc":null,"distanceMethod":null,"constellation":"Ori","messier":"M78"},{"id":"NGC2071","name":"NGC2071","kind":"nebula","x":0.05616492604980646,"y":0.9984082965423505,"z":0.005135608748892719,"angularSizeDeg":0.11666666666666667,"magnitude":8,"distancePc":null,"distanceMethod":null,"constellation":"Ori","messier":null},{"id":"NGC2374","name":"NGC2374","kind":"cluster","x":-0.3485490530164968,"y":0.9087775517202351,"z":-0.22942737223504664,"angularSizeDeg":0.15,"magnitude":8,"distancePc":1315.7894736842104,"distanceMethod":"parallax","constellation":"CMa","messier":null},{"id":"NGC2395","name":"NGC2395","kind":"cluster","x":-0.36099834153750293,"y":0.9023984661439702,"z":0.23528112059445452,"angularSizeDeg":0.077,"magnitude":8,"distancePc":null,"distanceMethod":null,"constellation":"Gem","messier":null},{"id":"NGC6259","name":"NGC6259","kind":"cluster","x":-0.1818418961054305,"y":-0.6877174201475921,"z":-0.7028358804488586,"angularSizeDeg":0.1,"magnitude":8,"distancePc":2450.9803921568628,"distanceMethod":"parallax","constellation":"Sco","messier":null},{"id":"NGC6441","name":"NGC6441","kind":"cluster","x":-0.034068042050894524,"y":-0.7973711766866547,"z":-0.6025268252120881,"angularSizeDeg":0.08,"magnitude":8,"distancePc":21598.272138228942,"distanceMethod":"parallax","constellation":"Sco","messier":null},{"id":"NGC6546","name":"NGC6546","kind":"cluster","x":0.02955422915649259,"y":-0.9179968444435751,"z":-0.3954849442527541,"angularSizeDeg":0.115,"magnitude":8,"distancePc":null,"distanceMethod":null,"constellation":"Sgr","messier":null},{"id":"NGC6647","name":"NGC6647","kind":"cluster","x":0.13632133792613063,"y":-0.9453519803816032,"z":-0.29618596525597,"angularSizeDeg":0.060000000000000005,"magnitude":8,"distancePc":null,"distanceMethod":null,"constellation":"Sgr","messier":null},{"id":"NGC6883","name":"NGC6883","kind":"cluster","x":0.43956581566758357,"y":-0.6812291391937416,"z":0.5854133186133073,"angularSizeDeg":0.075,"magnitude":8,"distancePc":null,"distanceMethod":null,"constellation":"Cyg","messier":null},{"id":"NGC7009","name":"Saturn Nebula","kind":"nebula","x":0.7057732995384749,"y":-0.6804879146001018,"z":-0.19702854549983084,"angularSizeDeg":0.011666666666666665,"magnitude":8,"distancePc":1154.0680900173109,"distanceMethod":"parallax","constellation":"Aqr","messier":null},{"id":"NGC0188","name":"NGC0188","kind":"cluster","x":0.08070478431145112,"y":0.01695525413978136,"z":0.99659382756783,"angularSizeDeg":0.295,"magnitude":8.1,"distancePc":1979.0223629527015,"distanceMethod":"parallax","constellation":"Cep","messier":null},{"id":"NGC3918","name":"Blue Planetary","kind":"nebula","x":-0.5414819310383762,"y":0.022933474914383904,"z":-0.8403995324173512,"angularSizeDeg":0.005333333333333333,"magnitude":8.1,"distancePc":null,"distanceMethod":null,"constellation":"Cen","messier":null},{"id":"NGC6152","name":"NGC6152","kind":"cluster","x":-0.2254307297632738,"y":-0.5633342455463936,"z":-0.7948808174016236,"angularSizeDeg":0.11,"magnitude":8.1,"distancePc":1683.5016835016836,"distanceMethod":"parallax","constellation":"Nor","messier":null},{"id":"NGC6572","name":"NGC6572","kind":"nebula","x":0.052409723257961685,"y":-0.9914698132613378,"z":0.11933495003372624,"angularSizeDeg":0.003,"magnitude":8.1,"distancePc":212.7659574468085,"distanceMethod":"parallax","constellation":"Oph","messier":null},{"id":"NGC0221","name":"M32","kind":"galaxy","x":0.7431639665203472,"y":0.1400769592498661,"z":0.6542826333879386,"angularSizeDeg":0.129,"magnitude":8.13,"distancePc":null,"distanceMethod":null,"constellation":"And","messier":"M32"},{"id":"NGC0288","name":"NGC0288","kind":"cluster","x":0.870614783326577,"y":0.2041641164935468,"z":-0.4476012875200724,"angularSizeDeg":0.16,"magnitude":8.13,"distancePc":7225.433526011561,"distanceMethod":"parallax","constellation":"Scl","messier":null},{"id":"NGC0205","name":"M110","kind":"galaxy","x":0.7352538849856904,"y":0.1308627489502934,"z":0.6650388451441176,"angularSizeDeg":0.2703333333333333,"magnitude":8.15,"distancePc":null,"distanceMethod":null,"constellation":"And","messier":"M110"},{"id":"NGC1904","name":"M79","kind":"cluster","x":0.14162980042648352,"y":0.898694240413855,"z":-0.4150778985661832,"angularSizeDeg":0.12000000000000001,"magnitude":8.16,"distancePc":27027.027027027027,"distanceMethod":"parallax","constellation":"Lep","messier":"M79"},{"id":"NGC6584","name":"NGC6584","kind":"cluster","x":0.04974392507873128,"y":-0.610675219572814,"z":-0.7903172262563033,"angularSizeDeg":0.08499999999999999,"magnitude":8.17,"distancePc":23255.813953488374,"distanceMethod":"parallax","constellation":"Tel","messier":null},{"id":"IC2714","name":"IC2714","kind":"cluster","x":-0.4503868908112641,"y":0.08458088668429554,"z":-0.8888181603641213,"angularSizeDeg":0.12000000000000001,"magnitude":8.2,"distancePc":1390.8205841446454,"distanceMethod":"parallax","constellation":"Car","messier":null},{"id":"NGC0637","name":"NGC0637","kind":"cluster","x":0.39428051326335384,"y":0.19028827452189448,"z":0.8990735506287939,"angularSizeDeg":0.075,"magnitude":8.2,"distancePc":2958.5798816568044,"distanceMethod":"parallax","constellation":"Cas","messier":null},{"id":"NGC1907","name":"NGC1907","kind":"cluster","x":0.11328102001192437,"y":0.8079760989218407,"z":0.578223169784904,"angularSizeDeg":0.09000000000000001,"magnitude":8.2,"distancePc":1636.6612111292964,"distanceMethod":"parallax","constellation":"Aur","messier":null},{"id":"NGC3496","name":"NGC3496","kind":"cluster","x":-0.47779160941448057,"y":0.1289997073272531,"z":-0.8689500868764577,"angularSizeDeg":0.1,"magnitude":8.2,"distancePc":2557.544757033248,"distanceMethod":"parallax","constellation":"Car","messier":null},{"id":"NGC3590","name":"NGC3590","kind":"cluster","x":-0.4777929991826664,"y":0.09941842416159356,"z":-0.872828635454439,"angularSizeDeg":0.08499999999999999,"magnitude":8.2,"distancePc":2770.083102493075,"distanceMethod":"parallax","constellation":"Car","messier":null},{"id":"NGC6204","name":"NGC6204","kind":"cluster","x":-0.21588577756793254,"y":-0.6466990031929305,"z":-0.7315556918739402,"angularSizeDeg":0.075,"magnitude":8.2,"distancePc":1242.2360248447205,"distanceMethod":"parallax","constellation":"Ara","messier":null},{"id":"NGC6249","name":"NGC6249","kind":"cluster","x":-0.19050499974430116,"y":-0.6833673826924234,"z":-0.7047814309021846,"angularSizeDeg":0.12666666666666665,"magnitude":8.2,"distancePc":1269.0355329949239,"distanceMethod":"parallax","constellation":"Sco","messier":null},{"id":"NGC6451","name":"NGC6451","kind":"cluster","x":-0.03514296271565198,"y":-0.8634579782442768,"z":-0.5031950834198128,"angularSizeDeg":0.12000000000000001,"magnitude":8.2,"distancePc":3289.4736842105262,"distanceMethod":"parallax","constellation":"Sco","messier":null},{"id":"NGC6469","name":"NGC6469","kind":"cluster","x":-0.027443640207750624,"y":-0.9249674294935678,"z":-0.37905421906134884,"angularSizeDeg":0.15,"magnitude":8.2,"distancePc":1703.5775127768316,"distanceMethod":"parallax","constellation":"Sgr","messier":null},{"id":"NGC6743","name":"NGC6743","kind":"cluster","x":0.2306967579432382,"y":-0.8412011315035297,"z":0.48903953033641384,"angularSizeDeg":0.115,"magnitude":8.2,"distancePc":null,"distanceMethod":null,"constellation":"Lyr","messier":null},{"id":"NGC1968","name":"NGC1968","kind":"cluster","x":0.05438597805054959,"y":0.37938819381866773,"z":-0.9236377881954019,"angularSizeDeg":0.08333333333333333,"magnitude":8.22,"distancePc":null,"distanceMethod":null,"constellation":"Dor","messier":null},{"id":"NGC3201","name":"NGC3201","kind":"cluster","x":-0.6218098721392956,"y":0.29787911809952106,"z":-0.7243069196896882,"angularSizeDeg":0.16,"magnitude":8.24,"distancePc":5813.9534883720935,"distanceMethod":"parallax","constellation":"Vel","messier":null},{"id":"NGC4736","name":"M94","kind":"galaxy","x":-0.734837253464672,"y":-0.16588688435442187,"z":0.6576440925908786,"angularSizeDeg":0.129,"magnitude":8.24,"distancePc":null,"distanceMethod":null,"constellation":"CVn","messier":"M94"},{"id":"NGC6864","name":"M75","kind":"cluster","x":0.48499586536026557,"y":-0.7908163931674456,"z":-0.37334761668059335,"angularSizeDeg":0.060000000000000005,"magnitude":8.26,"distancePc":49751.24378109453,"distanceMethod":"parallax","constellation":"Sgr","messier":"M75"},{"id":"NGC4472","name":"M49","kind":"galaxy","x":-0.981919048386044,"y":-0.1283103992806889,"z":0.13918126257896926,"angularSizeDeg":0.1701666666666667,"magnitude":8.28,"distancePc":13576315.598,"distanceMethod":"redshift","constellation":"Vir","messier":"M49"},{"id":"NGC2420","name":"NGC2420","kind":"cluster","x":-0.38711122030517287,"y":0.845540483727635,"z":0.367703948158674,"angularSizeDeg":0.125,"magnitude":8.3,"distancePc":2754.8209366391184,"distanceMethod":"parallax","constellation":"Gem","messier":null},{"id":"NGC2421","name":"NGC2421","kind":"cluster","x":-0.3814333371220238,"y":0.8547369184240838,"z":-0.3520417725424891,"angularSizeDeg":0.08499999999999999,"magnitude":8.3,"distancePc":2915.4518950437314,"distanceMethod":"parallax","constellation":"Pup","messier":null},{"id":"NGC2453","name":"NGC2453","kind":"cluster","x":-0.40231371241510344,"y":0.7932709959025106,"z":-0.457017290551043,"angularSizeDeg":0.05,"magnitude":8.3,"distancePc":4878.048780487805,"distanceMethod":"parallax","constellation":"Pup","messier":null},{"id":"NGC2925","name":"NGC2925","kind":"cluster","x":-0.4780558420788649,"y":0.35639025301015265,"z":-0.8027755598008866,"angularSizeDeg":0.125,"magnitude":8.3,"distancePc":772.2007722007722,"distanceMethod":"parallax","constellation":"Vel","messier":null},{"id":"NGC3034","name":"Cigar Galaxy","kind":"galaxy","x":-0.29757647270936083,"y":0.1790161769397314,"z":0.9377640701603671,"angularSizeDeg":0.18316666666666667,"magnitude":8.3,"distancePc":null,"distanceMethod":null,"constellation":"UMa","messier":"M82"},{"id":"NGC3960","name":"NGC3960","kind":"cluster","x":-0.5634819194070827,"y":0.02323883401310486,"z":-0.8258014792279204,"angularSizeDeg":0.1,"magnitude":8.3,"distancePc":2500,"distanceMethod":"parallax","constellation":"Cen","messier":null},{"id":"NGC6738","name":"NGC6738","kind":"cluster","x":0.25912582901090103,"y":-0.9446237720076851,"z":0.20134481393218534,"angularSizeDeg":0.09000000000000001,"magnitude":8.3,"distancePc":null,"distanceMethod":null,"constellation":"Aql","messier":null},{"id":"NGC7062","name":"NGC7062","kind":"cluster","x":0.535117658233172,"y":-0.43542913009730944,"z":0.7239133680971408,"angularSizeDeg":0.060000000000000005,"magnitude":8.3,"distancePc":2512.5628140703516,"distanceMethod":"parallax","constellation":"Cyg","messier":null},{"id":"NGC7662","name":"Copeland's Blue Snowball","kind":"nebula","x":0.728722945447531,"y":-0.10923888094134931,"z":0.6760397441489343,"angularSizeDeg":0.004666666666666667,"magnitude":8.3,"distancePc":82.64462809917356,"distanceMethod":"parallax","constellation":"And","messier":null},{"id":"NGC5286","name":"NGC5286","kind":"cluster","x":-0.5581156148963172,"y":-0.27961446728773126,"z":-0.7812315342408446,"angularSizeDeg":0.11,"magnitude":8.31,"distancePc":43103.44827586207,"distanceMethod":"parallax","constellation":"Cen","messier":null},{"id":"NGC6637","name":"M69","kind":"cluster","x":0.11533794588616858,"y":-0.8369038691170169,"z":-0.5350598771125774,"angularSizeDeg":0.095,"magnitude":8.31,"distancePc":11695.906432748538,"distanceMethod":"parallax","constellation":"Sgr","messier":"M69"},{"id":"NGC5194","name":"Whirlpool Galaxy","kind":"galaxy","x":-0.6279168639679685,"y":-0.2597017662331031,"z":0.7336725458677315,"angularSizeDeg":0.2285,"magnitude":8.36,"distancePc":null,"distanceMethod":null,"constellation":"CVn","messier":"M51"},{"id":"NGC2055","name":"NGC2055","kind":"cluster","x":0.03547576747065117,"y":0.34842827883244626,"z":-0.9366638695029436,"angularSizeDeg":0.03166666666666666,"magnitude":8.39,"distancePc":null,"distanceMethod":null,"constellation":"Dor","messier":null},{"id":"IC2157","name":"IC2157","kind":"cluster","x":-0.019093397171014083,"y":0.9128408758981368,"z":0.4078688238563868,"angularSizeDeg":0.045000000000000005,"magnitude":8.4,"distancePc":2232.142857142857,"distanceMethod":"parallax","constellation":"Gem","messier":null},{"id":"NGC1245","name":"NGC1245","kind":"cluster","x":0.44834778562902494,"y":0.5098543215531107,"z":0.734188554742695,"angularSizeDeg":0.19,"magnitude":8.4,"distancePc":3460.2076124567475,"distanceMethod":"parallax","constellation":"Per","messier":null},{"id":"NGC1513","name":"NGC1513","kind":"cluster","x":0.2999977726617906,"y":0.5757484491017708,"z":0.7606017747513283,"angularSizeDeg":0.08499999999999999,"magnitude":8.4,"distancePc":1536.0983102918585,"distanceMethod":"parallax","constellation":"Per","messier":null},{"id":"NGC1952","name":"Crab Nebula","kind":"nebula","x":0.1028076389703679,"y":0.9213712505463852,"z":0.37484077691191603,"angularSizeDeg":0.13333333333333333,"magnitude":8.4,"distancePc":null,"distanceMethod":null,"constellation":"Tau","messier":"M1"},{"id":"NGC2324","name":"NGC2324","kind":"cluster","x":-0.27614785034576045,"y":0.9609422455489043,"z":0.018230893254168205,"angularSizeDeg":0.145,"magnitude":8.4,"distancePc":5235.602094240838,"distanceMethod":"parallax","constellation":"Mon","messier":null},{"id":"NGC2383","name":"NGC2383","kind":"cluster","x":-0.33721096684583957,"y":0.870903012115022,"z":-0.35751462533423134,"angularSizeDeg":0.07,"magnitude":8.4,"distancePc":3521.1267605633807,"distanceMethod":"parallax","constellation":"CMa","messier":null},{"id":"NGC2627","name":"NGC2627","kind":"cluster","x":-0.5489411242668305,"y":0.670382516988026,"z":-0.4992503610469073,"angularSizeDeg":0.09000000000000001,"magnitude":8.4,"distancePc":1919.3857965451054,"distanceMethod":"parallax","constellation":"Pyx","messier":null},{"id":"NGC4439","name":"NGC4439","kind":"cluster","x":-0.4946063891859174,"y":-0.061692322306662065,"z":-0.8669247817111239,"angularSizeDeg":0.075,"magnitude":8.4,"distancePc":2109.704641350211,"distanceMethod":"parallax","constellation":"Cru","messier":null},{"id":"NGC5925","name":"NGC5925","kind":"cluster","x":-0.35836698014798035,"y":-0.456414515291956,"z":-0.8144070835708798,"angularSizeDeg":0.08,"magnitude":8.4,"distancePc":1472.7540500736377,"distanceMethod":"parallax","constellation":"Nor","messier":null},{"id":"NGC6779","name":"M56","kind":"cluster","x":0.2835343100707465,"y":-0.8165870517951239,"z":0.502786119391987,"angularSizeDeg":0.09666666666666666,"magnitude":8.4,"distancePc":13245.033112582782,"distanceMethod":"parallax","constellation":"Lyr","messier":"M56"},{"id":"NGC7086","name":"NGC7086","kind":"cluster","x":0.49354052302312446,"y":-0.37713836852678606,"z":0.7836991789704866,"angularSizeDeg":0.08,"magnitude":8.4,"distancePc":1694.9152542372883,"distanceMethod":"parallax","constellation":"Cyg","messier":null},{"id":"NGC7261","name":"NGC7261","kind":"cluster","x":0.4796783098357758,"y":-0.22340635571974143,"z":0.8485271470596081,"angularSizeDeg":0.115,"magnitude":8.4,"distancePc":3597.122302158273,"distanceMethod":"parallax","constellation":"Cep","messier":null},{"id":"NGC6333","name":"M9","kind":"cluster","x":-0.167932636001636,"y":-0.9332446818417924,"z":-0.31757360340487034,"angularSizeDeg":0.115,"magnitude":8.42,"distancePc":8952.551477170995,"distanceMethod":"parallax","constellation":"Oph","messier":"M9"},{"id":"NGC2403","name":"NGC2403","kind":"galaxy","x":-0.16941752184683792,"y":0.37672193284510325,"z":0.9107020855388033,"angularSizeDeg":0.33249999999999996,"magnitude":8.43,"distancePc":null,"distanceMethod":null,"constellation":"Cam","messier":null},{"id":"NGC2215","name":"NGC2215","kind":"cluster","x":-0.08999100715064134,"y":0.9878398124148258,"z":-0.12678376725849058,"angularSizeDeg":0.14,"magnitude":8.45,"distancePc":979.4319294809012,"distanceMethod":"parallax","constellation":"Mon","messier":null},{"id":"NGC1316","name":"Fornax A","kind":"galaxy","x":0.5047332726006032,"y":0.616089287199575,"z":-0.6047134145426284,"angularSizeDeg":0.22433333333333336,"magnitude":8.48,"distancePc":25315331.703399997,"distanceMethod":"redshift","constellation":"For","messier":null},{"id":"NGC2074","name":"NGC2074","kind":"nebula","x":0.03195656643930092,"y":0.3487773173315264,"z":-0.9366606433369742,"angularSizeDeg":0.06666666666666667,"magnitude":8.5,"distancePc":null,"distanceMethod":null,"constellation":"Dor","messier":null},{"id":"NGC2194","name":"NGC2194","kind":"cluster","x":-0.05853253835397588,"y":0.9733652538937071,"z":0.22166195989903054,"angularSizeDeg":0.145,"magnitude":8.5,"distancePc":3802.2813688212927,"distanceMethod":"parallax","constellation":"Ori","messier":null},{"id":"NGC2236","name":"NGC2236","kind":"cluster","x":-0.12814632890414587,"y":0.9845977704771238,"z":0.1189359018962209,"angularSizeDeg":0.09000000000000001,"magnitude":8.5,"distancePc":2857.1428571428573,"distanceMethod":"parallax","constellation":"Mon","messier":null},{"id":"NGC2247","name":"NGC2247","kind":"nebula","x":-0.14153844402827964,"y":0.9735809482984521,"z":0.17918427936719322,"angularSizeDeg":0.03333333333333333,"magnitude":8.5,"distancePc":null,"distanceMethod":null,"constellation":"Mon","messier":null},{"id":"NGC2331","name":"NGC2331","kind":"cluster","x":-0.2561742164457959,"y":0.8512118639996413,"z":0.4580536359583215,"angularSizeDeg":0.08,"magnitude":8.5,"distancePc":null,"distanceMethod":null,"constellation":"Gem","messier":null},{"id":"NGC6031","name":"NGC6031","kind":"cluster","x":-0.27677780644864014,"y":-0.5183025234947669,"z":-0.8091702787401647,"angularSizeDeg":0.08,"magnitude":8.5,"distancePc":1872.6591760299625,"distanceMethod":"parallax","constellation":"Nor","messier":null},{"id":"NGC6192","name":"NGC6192","kind":"cluster","x":-0.24745307394356697,"y":-0.6835613789767369,"z":-0.6866664527755099,"angularSizeDeg":0.125,"magnitude":8.5,"distancePc":1751.3134851138354,"distanceMethod":"parallax","constellation":"Sco","messier":null},{"id":"NGC6396","name":"NGC6396","kind":"cluster","x":-0.079889809483651,"y":-0.81498679699524,"z":-0.5739461116377611,"angularSizeDeg":0.055,"magnitude":8.5,"distancePc":2832.8611898016998,"distanceMethod":"parallax","constellation":"Sco","messier":null},{"id":"NGC6514","name":"Trifid Nebula","kind":"nebula","x":0.010853813113793994,"y":-0.9206324692416822,"z":-0.3902794528525094,"angularSizeDeg":0.4666666666666667,"magnitude":8.5,"distancePc":1265.8227848101264,"distanceMethod":"parallax","constellation":"Sgr","messier":"M20"},{"id":"NGC6645","name":"NGC6645","kind":"cluster","x":0.13578527100296522,"y":-0.9472122016439182,"z":-0.290433133845869,"angularSizeDeg":0.09000000000000001,"magnitude":8.5,"distancePc":1841.6206261510129,"distanceMethod":"parallax","constellation":"Sgr","messier":null},{"id":"NGC7027","name":"NGC7027","kind":"nebula","x":0.5393249741065488,"y":-0.5072331765366175,"z":0.672192737929787,"angularSizeDeg":0.0038333333333333336,"magnitude":8.5,"distancePc":104.16666666666667,"distanceMethod":"parallax","constellation":"Cyg","messier":null},{"id":"NGC7790","name":"NGC7790","kind":"cluster","x":0.48161496683288507,"y":-0.003353254695115962,"z":0.8763765055074837,"angularSizeDeg":0.060000000000000005,"magnitude":8.5,"distancePc":3717.472118959108,"distanceMethod":"parallax","constellation":"Cas","messier":null},{"id":"NGC4826","name":"Black Eye Galaxy","kind":"galaxy","x":-0.9009219693617888,"y":-0.2276643856638416,"z":0.3694706113097247,"angularSizeDeg":0.1753333333333333,"magnitude":8.52,"distancePc":null,"distanceMethod":null,"constellation":"Com","messier":"M64"},{"id":"NGC5897","name":"NGC5897","kind":"cluster","x":-0.6081066098326963,"y":-0.7082800701268108,"z":-0.35853269493721895,"angularSizeDeg":0.165,"magnitude":8.52,"distancePc":17182.13058419244,"distanceMethod":"parallax","constellation":"Lib","messier":null},{"id":"NGC0055","name":"NGC0055","kind":"galaxy","x":0.7733457754243924,"y":0.050326293361762364,"z":-0.6319838414308637,"angularSizeDeg":0.4975,"magnitude":8.54,"distancePc":null,"distanceMethod":null,"constellation":"Scl","messier":null},{"id":"IC4499","name":"IC4499","kind":"cluster","x":-0.09566612762852965,"y":-0.09593434921628284,"z":-0.9907797901981097,"angularSizeDeg":0.08499999999999999,"magnitude":8.56,"distancePc":54644.8087431694,"distanceMethod":"parallax","constellation":"Aps","messier":null},{"id":"NGC4594","name":"Sombrero Galaxy","kind":"galaxy","x":-0.9646205883506601,"y":-0.1700474079538563,"z":-0.20147208138648157,"angularSizeDeg":0.1408333333333333,"magnitude":8.59,"distancePc":15597773.314799998,"distanceMethod":"redshift","constellation":"Vir","messier":"M104"},{"id":"NGC2158","name":"NGC2158","kind":"cluster","x":-0.029576714496041965,"y":0.9123822256278615,"z":0.4082693869468658,"angularSizeDeg":0.14,"magnitude":8.6,"distancePc":5455.537370430988,"distanceMethod":"parallax","constellation":"Gem","messier":null},{"id":"NGC2204","name":"NGC2204","kind":"cluster","x":-0.06417793423636405,"y":0.9452249030284343,"z":-0.3200485517105802,"angularSizeDeg":0.105,"magnitude":8.6,"distancePc":4784.6889952153115,"distanceMethod":"parallax","constellation":"CMa","messier":null},{"id":"NGC2659","name":"NGC2659","kind":"cluster","x":-0.4605146928168619,"y":0.5365789841578319,"z":-0.707113294642337,"angularSizeDeg":0.08499999999999999,"magnitude":8.6,"distancePc":2212.3893805309735,"distanceMethod":"parallax","constellation":"Vel","messier":null},{"id":"NGC4815","name":"NGC4815","kind":"cluster","x":-0.4097551083777408,"y":-0.10591805819085479,"z":-0.9060254500329583,"angularSizeDeg":0.065,"magnitude":8.6,"distancePc":3831.417624521073,"distanceMethod":"parallax","constellation":"Mus","messier":null},{"id":"NGC6568","name":"NGC6568","kind":"cluster","x":0.0516383495999372,"y":-0.9281609534311784,"z":-0.3685793338974941,"angularSizeDeg":0.13,"magnitude":8.6,"distancePc":1068.3760683760684,"distanceMethod":"parallax","constellation":"Sgr","messier":null},{"id":"NGC5055","name":"Sunflower Galaxy","kind":"galaxy","x":-0.7025212894615012,"y":-0.2412878943054844,"z":0.6695102612469619,"angularSizeDeg":0.19716666666666666,"magnitude":8.61,"distancePc":null,"distanceMethod":null,"constellation":"CVn","messier":"M63"},{"id":"NGC1261","name":"NGC1261","kind":"cluster","x":0.38124756634616896,"y":0.4243693947303097,"z":-0.8213165711048049,"angularSizeDeg":0.08499999999999999,"magnitude":8.63,"distancePc":83333.33333333333,"distanceMethod":"parallax","constellation":"Hor","messier":null},{"id":"NGC0300","name":"NGC0300","kind":"galaxy","x":0.7687996224385386,"y":0.18773776416188995,"z":-0.6113114365410278,"angularSizeDeg":0.3235,"magnitude":8.66,"distancePc":null,"distanceMethod":null,"constellation":"Scl","messier":null},{"id":"NGC6712","name":"NGC6712","kind":"cluster","x":0.22690201516862982,"y":-0.9620847520827088,"z":-0.15135522892311112,"angularSizeDeg":0.095,"magnitude":8.69,"distancePc":7256.894049346879,"distanceMethod":"parallax","constellation":"Sct","messier":null},{"id":"NGC1269","name":"NGC1269","kind":"galaxy","x":0.4910633979269339,"y":0.5714676085322073,"z":-0.6574811872707338,"angularSizeDeg":0.18616666666666667,"magnitude":8.7,"distancePc":null,"distanceMethod":null,"constellation":"Eri","messier":null},{"id":"NGC2186","name":"NGC2186","kind":"cluster","x":-0.052614066762763465,"y":0.9940738234095554,"z":0.09512619823472086,"angularSizeDeg":0.07,"magnitude":8.7,"distancePc":2421.3075060532688,"distanceMethod":"parallax","constellation":"Ori","messier":null},{"id":"NGC4649","name":"M60","kind":"galaxy","x":-0.9620114834444127,"y":-0.18554299215759548,"z":0.2002690784476899,"angularSizeDeg":0.113,"magnitude":8.79,"distancePc":14518520.465999998,"distanceMethod":"redshift","constellation":"Vir","messier":"M60"},{"id":"NGC0189","name":"NGC0189","kind":"cluster","x":0.4761709989355739,"y":0.08309443817469993,"z":0.8754178968453459,"angularSizeDeg":0.045000000000000005,"magnitude":8.8,"distancePc":1295.3367875647668,"distanceMethod":"parallax","constellation":"Cas","messier":null},{"id":"NGC0436","name":"NGC0436","kind":"cluster","x":0.48958981308259086,"y":0.16849088471744728,"z":0.8555189283077761,"angularSizeDeg":0.095,"magnitude":8.8,"distancePc":3816.793893129771,"distanceMethod":"parallax","constellation":"Cas","messier":null},{"id":"NGC2660","name":"NGC2660","kind":"cluster","x":-0.4426819386011019,"y":0.5154236955629677,"z":-0.7337377701117637,"angularSizeDeg":0.060000000000000005,"magnitude":8.8,"distancePc":3246.753246753247,"distanceMethod":"parallax","constellation":"Vel","messier":null},{"id":"NGC3033","name":"NGC3033","kind":"cluster","x":-0.4645126109006128,"y":0.2999779450713205,"z":-0.8332114178196814,"angularSizeDeg":0.08,"magnitude":8.8,"distancePc":1879.6992481203006,"distanceMethod":"parallax","constellation":"Vel","messier":null},{"id":"NGC4052","name":"NGC4052","kind":"cluster","x":-0.45049316939170214,"y":-0.004101436335641265,"z":-0.8927704534489276,"angularSizeDeg":0.08499999999999999,"magnitude":8.8,"distancePc":3095.9752321981423,"distanceMethod":"parallax","constellation":"Cru","messier":null},{"id":"NGC5749","name":"NGC5749","kind":"cluster","x":-0.4300431389652681,"y":-0.39027793834108176,"z":-0.8140921504799895,"angularSizeDeg":0.12000000000000001,"magnitude":8.8,"distancePc":1123.5955056179776,"distanceMethod":"parallax","constellation":"Lup","messier":null},{"id":"NGC6400","name":"NGC6400","kind":"cluster","x":-0.06891238620714166,"y":-0.7962076392173977,"z":-0.6010860822536952,"angularSizeDeg":0.1,"magnitude":8.8,"distancePc":1254.7051442910915,"distanceMethod":"parallax","constellation":"Sco","messier":null},{"id":"NGC6720","name":"Ring Nebula","kind":"nebula","x":0.1942384367743555,"y":-0.8155880741217657,"z":0.5450573575595543,"angularSizeDeg":0.021166666666666667,"magnitude":8.8,"distancePc":786.9678130164476,"distanceMethod":"parallax","constellation":"Lyr","messier":"M57"},{"id":"NGC6802","name":"NGC6802","kind":"cluster","x":0.36121228635793523,"y":-0.8657968981740567,"z":0.34629671568794185,"angularSizeDeg":0.075,"magnitude":8.8,"distancePc":3236.2459546925566,"distanceMethod":"parallax","constellation":"Vul","messier":null},{"id":"NGC6171","name":"M107","kind":"cluster","x":-0.36282877551340836,"y":-0.9040692026168811,"z":-0.22586313674240327,"angularSizeDeg":0.13,"magnitude":8.85,"distancePc":6706.9081153588195,"distanceMethod":"parallax","constellation":"Oph","messier":"M107"},{"id":"NGC4406","name":"M86","kind":"galaxy","x":-0.9682214843878345,"y":-0.1111523419199296,"z":0.22403641234302366,"angularSizeDeg":0.19216666666666665,"magnitude":8.86,"distancePc":null,"distanceMethod":null,"constellation":"Vir","messier":"M86"},{"id":"NGC5927","name":"NGC5927","kind":"cluster","x":-0.39015889892314665,"y":-0.49941280705347174,"z":-0.7735391921176649,"angularSizeDeg":0.11,"magnitude":8.86,"distancePc":9276.437847866418,"distanceMethod":"parallax","constellation":"Lup","messier":null},{"id":"NGC6362","name":"NGC6362","kind":"cluster","x":-0.04766965814395998,"y":-0.38703748460584075,"z":-0.9208309232440132,"angularSizeDeg":0.14,"magnitude":8.86,"distancePc":10050.251256281406,"distanceMethod":"parallax","constellation":"Ara","messier":null},{"id":"NGC1955","name":"NGC1955","kind":"nebula","x":0.05629625358515306,"y":0.37856250120873935,"z":-0.9238620917165393,"angularSizeDeg":0.06666666666666667,"magnitude":8.87,"distancePc":null,"distanceMethod":null,"constellation":"Dor","messier":null},{"id":"NGC6352","name":"NGC6352","kind":"cluster","x":-0.09956251267255484,"y":-0.6561189176273738,"z":-0.7480610082084286,"angularSizeDeg":0.12000000000000001,"magnitude":8.87,"distancePc":6666.666666666667,"distanceMethod":"parallax","constellation":"Ara","messier":null},{"id":"NGC6694","name":"M26","kind":"cluster","x":0.1937927193880103,"y":-0.9673991499964322,"z":-0.16304375639189753,"angularSizeDeg":0.1,"magnitude":8.87,"distancePc":1908.3969465648854,"distanceMethod":"parallax","constellation":"Sct","messier":"M26"},{"id":"NGC2298","name":"NGC2298","kind":"cluster","x":-0.1715978010984249,"y":0.7905533643728445,"z":-0.5878601642711998,"angularSizeDeg":0.08,"magnitude":8.89,"distancePc":12224.938875305625,"distanceMethod":"parallax","constellation":"Pup","messier":null},{"id":"NGC0956","name":"NGC0956","kind":"cluster","x":0.5601606299645665,"y":0.4396761066223134,"z":0.7020719264456771,"angularSizeDeg":0.075,"magnitude":8.9,"distancePc":null,"distanceMethod":null,"constellation":"And","messier":null},{"id":"NGC2250","name":"NGC2250","kind":"cluster","x":-0.14650276600208287,"y":0.9852323323611267,"z":-0.08862387276571253,"angularSizeDeg":0.08499999999999999,"magnitude":8.9,"distancePc":null,"distanceMethod":null,"constellation":"Mon","messier":null},{"id":"NGC2299","name":"NGC2299","kind":"cluster","x":-0.22279005997461432,"y":0.9670373050785254,"z":-0.12330222935117877,"angularSizeDeg":0.08,"magnitude":8.9,"distancePc":null,"distanceMethod":null,"constellation":"Mon","messier":null},{"id":"NGC4337","name":"NGC4337","kind":"cluster","x":-0.5251797529590662,"y":-0.055326432067530934,"z":-0.8491909166945506,"angularSizeDeg":0.11,"magnitude":8.9,"distancePc":2801.120448179272,"distanceMethod":"parallax","constellation":"Cru","messier":null},{"id":"NGC4852","name":"NGC4852","kind":"cluster","x":-0.4886119200149365,"y":-0.13109037247619085,"z":-0.8625970704004106,"angularSizeDeg":0.09000000000000001,"magnitude":8.9,"distancePc":1329.787234042553,"distanceMethod":"parallax","constellation":"Cen","messier":null},{"id":"NGC6649","name":"NGC6649","kind":"cluster","x":0.1431128778862355,"y":-0.9730951402137709,"z":-0.18056730677357188,"angularSizeDeg":0.093,"magnitude":8.9,"distancePc":2141.327623126338,"distanceMethod":"parallax","constellation":"Sct","messier":null},{"id":"NGC2903","name":"NGC2903","kind":"galaxy","x":-0.7434717270168367,"y":0.5593895981491029,"z":0.36651475906051506,"angularSizeDeg":0.19899999999999998,"magnitude":8.91,"distancePc":null,"distanceMethod":null,"constellation":"Leo","messier":null},{"id":"NGC3627","name":"M66","kind":"galaxy","x":-0.959783453499323,"y":0.16815929063206633,"z":0.22480697356406223,"angularSizeDeg":0.1713333333333333,"magnitude":8.92,"distancePc":null,"distanceMethod":null,"constellation":"Leo","messier":"M66"},{"id":"NGC1850","name":"NGC1850","kind":"cluster","x":0.08033955642982955,"y":0.35322705584730524,"z":-0.9320816502271155,"angularSizeDeg":0.05,"magnitude":8.96,"distancePc":null,"distanceMethod":null,"constellation":"Dor","messier":null},{"id":"NGC6981","name":"M72","kind":"cluster","x":0.670287091165663,"y":-0.7096445625650775,"z":-0.21707097972436104,"angularSizeDeg":0.075,"magnitude":8.96,"distancePc":66666.66666666667,"distanceMethod":"parallax","constellation":"Aqr","messier":"M72"},{"id":"NGC2014","name":"NGC2014","kind":"nebula","x":0.045719573854350756,"y":0.3768571497182207,"z":-0.9251423724340136,"angularSizeDeg":0.08499999999999999,"magnitude":8.97,"distancePc":null,"distanceMethod":null,"constellation":"Dor","messier":null},{"id":"NGC1982","name":"Mairan's Nebula","kind":"nebula","x":0.10614797261943014,"y":0.9901032258985207,"z":-0.09180528293147161,"angularSizeDeg":0.3333333333333333,"magnitude":9,"distancePc":null,"distanceMethod":null,"constellation":"Ori","messier":"M43"},{"id":"NGC2180","name":"NGC2180","kind":"cluster","x":-0.04175224907758421,"y":0.9957457966934327,"z":0.08214047762415612,"angularSizeDeg":0.07,"magnitude":9,"distancePc":null,"distanceMethod":null,"constellation":"Ori","messier":null},{"id":"NGC2182","name":"NGC2182","kind":"nebula","x":-0.04125594694871113,"y":0.993053592476043,"z":-0.11019305473481716,"angularSizeDeg":0.05,"magnitude":9,"distancePc":null,"distanceMethod":null,"constellation":"Mon","messier":null},{"id":"NGC4486","name":"Virgo Galaxy","kind":"galaxy","x":-0.967885302560046,"y":-0.13096500471266134,"z":0.21458380327708945,"angularSizeDeg":0.11850000000000001,"magnitude":9,"distancePc":17987547.48,"distanceMethod":"redshift","constellation":"Vir","messier":"M87"},{"id":"NGC5999","name":"NGC5999","kind":"cluster","x":-0.29239772028509786,"y":-0.46858830383440886,"z":-0.833623760866777,"angularSizeDeg":0.055,"magnitude":9,"distancePc":3067.484662576687,"distanceMethod":"parallax","constellation":"Nor","messier":null},{"id":"NGC6625","name":"NGC6625","kind":"cluster","x":0.09711600800338861,"y":-0.973478349205171,"z":-0.20714339144240387,"angularSizeDeg":0.1,"magnitude":9,"distancePc":null,"distanceMethod":null,"constellation":"Sct","messier":null},{"id":"NGC6543","name":"Cat's Eye Nebula","kind":"nebula","x":-0.0024980530406913675,"y":-0.39660825374946423,"z":0.9179845602126467,"angularSizeDeg":0.015000000000000001,"magnitude":9.01,"distancePc":1625.4876462938882,"distanceMethod":"parallax","constellation":"Dra","messier":null},{"id":"NGC4382","name":"M85","kind":"galaxy","x":-0.9441889985406101,"y":-0.10508116606213593,"z":0.31219398388486225,"angularSizeDeg":0.11583333333333333,"magnitude":9.05,"distancePc":null,"distanceMethod":null,"constellation":"Com","messier":"M85"},{"id":"NGC6946","name":"Fireworks Galaxy","kind":"galaxy","x":0.3112876099001924,"y":-0.38830039112833753,"z":0.8673654536423535,"angularSizeDeg":0.19,"magnitude":9.05,"distancePc":null,"distanceMethod":null,"constellation":"Cyg","messier":null},{"id":"NGC6681","name":"M70","kind":"cluster","x":0.15843909325493713,"y":-0.8303568468016151,"z":-0.5342326840415441,"angularSizeDeg":0.11,"magnitude":9.06,"distancePc":7905.138339920949,"distanceMethod":"parallax","constellation":"Sgr","messier":"M70"},{"id":"NGC3115","name":"Spindle Galaxy","kind":"galaxy","x":-0.8692674596685833,"y":0.47574736331377476,"z":-0.13430759420568483,"angularSizeDeg":0.11833333333333333,"magnitude":9.09,"distancePc":null,"distanceMethod":null,"constellation":"Sex","messier":null},{"id":"NGC3132","name":"Eight-Burst Nebula","kind":"nebula","x":-0.6705126892617619,"y":0.3601707598450521,"z":-0.6486060108352357,"angularSizeDeg":0.008333333333333333,"magnitude":9.2,"distancePc":864.528399757932,"distanceMethod":"parallax","constellation":"Vel","messier":null},{"id":"NGC3368","name":"M96","kind":"galaxy","x":-0.9292434740696845,"y":0.30748733253352983,"z":0.20483677948629728,"angularSizeDeg":0.13766666666666666,"magnitude":9.21,"distancePc":12899641.192799998,"distanceMethod":"redshift","constellation":"Leo","messier":"M96"},{"id":"NGC4631","name":"Whale Galaxy","kind":"galaxy","x":-0.8287962772690898,"y":-0.15410772494781322,"z":0.5379103455933034,"angularSizeDeg":0.24083333333333332,"magnitude":9.24,"distancePc":null,"distanceMethod":null,"constellation":"CVn","messier":null},{"id":"NGC3379","name":"M105","kind":"galaxy","x":-0.9279899481685332,"y":0.3022990928843399,"z":0.21783001294465443,"angularSizeDeg":0.08149999999999999,"magnitude":9.27,"distancePc":null,"distanceMethod":null,"constellation":"Leo","messier":"M105"},{"id":"NGC1068","name":"M77","kind":"galaxy","x":0.7584799151769579,"y":0.651696374525202,"z":-0.0002317409374961276,"angularSizeDeg":0.10183333333333335,"magnitude":9.29,"distancePc":16317275.213999998,"distanceMethod":"redshift","constellation":"Cet","messier":"M77"},{"id":"NGC4258","name":"M106","kind":"galaxy","x":-0.6757899528489851,"y":-0.056030106267005844,"z":0.7349616090790556,"angularSizeDeg":0.28300000000000003,"magnitude":9.29,"distancePc":null,"distanceMethod":null,"constellation":"CVn","messier":"M106"},{"id":"NGC6818","name":"Little Gem Nebula","kind":"nebula","x":0.42492075291064824,"y":-0.8715817950965172,"z":-0.2445148833960427,"angularSizeDeg":0.012833333333333334,"magnitude":9.3,"distancePc":null,"distanceMethod":null,"constellation":"Sgr","messier":null},{"id":"NGC0628","name":"M74","kind":"galaxy","x":0.8779083499746243,"y":0.3940681410462717,"z":0.2720059360697269,"angularSizeDeg":0.16483333333333333,"magnitude":9.31,"distancePc":null,"distanceMethod":null,"constellation":"Psc","messier":"M74"},{"id":"NGC3623","name":"M65","kind":"galaxy","x":-0.9584102508532905,"y":0.17360248606573453,"z":0.2265214512824983,"angularSizeDeg":0.12733333333333333,"magnitude":9.32,"distancePc":null,"distanceMethod":null,"constellation":"Leo","messier":"M65"},{"id":"NGC6826","name":"Blinking Planetary","kind":"nebula","x":0.2806879799059917,"y":-0.5704217782877261,"z":0.7719023596228748,"angularSizeDeg":0.007,"magnitude":9.44,"distancePc":1575.299306868305,"distanceMethod":"parallax","constellation":"Cyg","messier":null},{"id":"NGC4321","name":"M100","kind":"galaxy","x":-0.9573096125041197,"y":-0.096032450963298,"z":0.272646427024441,"angularSizeDeg":0.10166666666666666,"magnitude":9.47,"distancePc":22488717.0994,"distanceMethod":"redshift","constellation":"Com","messier":"M100"},{"id":"NGC4569","name":"M90","kind":"galaxy","x":-0.9611803516756183,"y":-0.15580565118020567,"z":0.22772116856596078,"angularSizeDeg":0.152,"magnitude":9.54,"distancePc":null,"distanceMethod":null,"constellation":"Vir","messier":"M90"},{"id":"NGC4621","name":"M59","kind":"galaxy","x":-0.9629804808308045,"y":-0.1786401429887253,"z":0.20188187846322533,"angularSizeDeg":0.07583333333333334,"magnitude":9.56,"distancePc":null,"distanceMethod":null,"constellation":"Vir","messier":"M59"},{"id":"NGC6302","name":"Bug Nebula","kind":"nebula","x":-0.15987952903299946,"y":-0.7813615967020806,"z":-0.603251681634922,"angularSizeDeg":0.012333333333333333,"magnitude":9.6,"distancePc":null,"distanceMethod":null,"constellation":"Sco","messier":null},{"id":"NGC2392","name":"Eskimo Nebula","kind":"nebula","x":-0.3543837401621925,"y":0.8642988333823161,"z":0.3569309335468462,"angularSizeDeg":0.014333333333333333,"magnitude":9.61,"distancePc":1998.4012789768187,"distanceMethod":"parallax","constellation":"Gem","messier":null},{"id":"NGC3351","name":"M95","kind":"galaxy","x":-0.9258072842731733,"y":0.3189542331212465,"z":0.2028523343734777,"angularSizeDeg":0.12050000000000001,"magnitude":9.77,"distancePc":null,"distanceMethod":null,"constellation":"Leo","messier":"M95"},{"id":"NGC4374","name":"M84","kind":"galaxy","x":-0.9689890789625433,"y":-0.10638827854600237,"z":0.2230284713648471,"angularSizeDeg":0.1235,"magnitude":9.79,"distancePc":14004590.537999999,"distanceMethod":"redshift","constellation":"Vir","messier":"M84"},{"id":"NGC4254","name":"Coma Pinwheel","kind":"galaxy","x":-0.9652455335699835,"y":-0.07947070013665869,"z":0.24896880877529076,"angularSizeDeg":0.084,"magnitude":9.84,"distancePc":34416174.178399995,"distanceMethod":"redshift","constellation":"Com","messier":"M99"},{"id":"NGC3992","name":"M109","kind":"galaxy","x":-0.5965490078845754,"y":0.006248142707907837,"z":0.8025523296985876,"angularSizeDeg":0.1345,"magnitude":9.88,"distancePc":14972491.9024,"distanceMethod":"redshift","constellation":"UMa","messier":"M109"},{"id":"NGC3587","name":"Owl Nebula","kind":"nebula","x":-0.5621882963449338,"y":0.11234863632638169,"z":0.8193424823407994,"angularSizeDeg":0.059666666666666666,"magnitude":9.9,"distancePc":879.430129276229,"distanceMethod":"parallax","constellation":"UMa","messier":"M97"},{"id":"NGC1555","name":"Hind's Nebula","kind":"nebula","x":0.3908575675204817,"y":0.8575644520400286,"z":0.33438536527374746,"angularSizeDeg":0.030333333333333334,"magnitude":9.98,"distancePc":null,"distanceMethod":null,"constellation":"Tau","messier":null},{"id":"IC0405","name":"Flaming Star Nebula","kind":"nebula","x":0.15578381696026386,"y":0.8107137205854839,"z":0.5643355966335395,"angularSizeDeg":0.8333333333333334,"magnitude":10,"distancePc":null,"distanceMethod":null,"constellation":"Aur","messier":null},{"id":"NGC3556","name":"M108","kind":"galaxy","x":-0.5513279696623433,"y":0.11840543192700194,"z":0.8258435829854092,"angularSizeDeg":0.06633333333333333,"magnitude":10.05,"distancePc":null,"distanceMethod":null,"constellation":"UMa","messier":"M108"},{"id":"NGC6822","name":"Barnard's Galaxy","kind":"galaxy","x":0.4274657415204694,"y":-0.8671740351190159,"z":-0.2555038798957053,"angularSizeDeg":0.28966666666666663,"magnitude":10.05,"distancePc":null,"distanceMethod":null,"constellation":"Sgr","messier":null},{"id":"NGC4552","name":"M89","kind":"galaxy","x":-0.9642886867226605,"y":-0.1512773186833745,"z":0.21739940549747477,"angularSizeDeg":0.1355,"magnitude":10.08,"distancePc":null,"distanceMethod":null,"constellation":"Vir","messier":"M89"},{"id":"NGC0650","name":"Barbell Nebula","kind":"nebula","x":0.5605579175979962,"y":0.26835835671750363,"z":0.7834274780718883,"angularSizeDeg":0.018666666666666668,"magnitude":10.1,"distancePc":null,"distanceMethod":null,"constellation":"Per","messier":"M76"},{"id":"NGC4038","name":"Antennae Galaxies","kind":"galaxy","x":-0.9462363601579861,"y":-0.0077766474473434455,"z":-0.3233825512754915,"angularSizeDeg":0.09033333333333333,"magnitude":10.2,"distancePc":23953417.394199997,"distanceMethod":"redshift","constellation":"Crv","messier":null},{"id":"NGC4303","name":"M61","kind":"galaxy","x":-0.992398921923714,"y":-0.09518568498426429,"z":0.0780004175548215,"angularSizeDeg":0.11483333333333333,"magnitude":10.25,"distancePc":22398779.362000003,"distanceMethod":"redshift","constellation":"Vir","messier":"M61"},{"id":"NGC4579","name":"M58","kind":"galaxy","x":-0.9655716315136954,"y":-0.16039190995485594,"z":0.2048068837637389,"angularSizeDeg":0.08349999999999999,"magnitude":10.3,"distancePc":24069051.628,"distanceMethod":"redshift","constellation":"Vir","messier":"M58"},{"id":"NGC4501","name":"M88","kind":"galaxy","x":-0.9590775117688947,"y":-0.13472981066117662,"z":0.24903454486956078,"angularSizeDeg":0.14416666666666667,"magnitude":10.33,"distancePc":32557460.9388,"distanceMethod":"redshift","constellation":"Com","messier":"M88"},{"id":"IC2574","name":"Coddington's Nebula","kind":"galaxy","x":-0.3389240830733946,"y":0.14318224166248214,"z":0.9298544572057313,"angularSizeDeg":0.21516666666666667,"magnitude":10.46,"distancePc":null,"distanceMethod":null,"constellation":"UMa","messier":null},{"id":"NGC4568","name":"Butterfly Galaxies","kind":"galaxy","x":-0.9683622531497224,"y":-0.15584765076694124,"z":0.19490011910006275,"angularSizeDeg":0.07183333333333333,"magnitude":10.8,"distancePc":31863655.536,"distanceMethod":"redshift","constellation":"Vir","messier":null},{"id":"NGC4651","name":"Umbrella Galaxy","kind":"galaxy","x":-0.9419511247606434,"y":-0.1818622403786154,"z":0.28223076389123414,"angularSizeDeg":0.0645,"magnitude":10.82,"distancePc":null,"distanceMethod":null,"constellation":"Com","messier":null},{"id":"NGC4192","name":"M98","kind":"galaxy","x":-0.9646219895439826,"y":-0.05817432995743823,"z":0.2571384152984769,"angularSizeDeg":0.184,"magnitude":10.84,"distancePc":null,"distanceMethod":null,"constellation":"Com","messier":"M98"},{"id":"NGC4565","name":"Needle Galaxy","kind":"galaxy","x":-0.8876080711654695,"y":-0.14195860727490953,"z":0.43817766467779123,"angularSizeDeg":0.2791666666666667,"magnitude":10.86,"distancePc":18030374.974,"distanceMethod":"redshift","constellation":"Com","messier":null},{"id":"NGC1317","name":"Fornax B","kind":"galaxy","x":0.5053162624422823,"y":0.6170361037024079,"z":-0.6032594148781384,"angularSizeDeg":0.051666666666666666,"magnitude":10.88,"distancePc":27572340.6372,"distanceMethod":"redshift","constellation":"For","messier":null},{"id":"NGC4438","name":"Eyes","kind":"galaxy","x":-0.9671967254771718,"y":-0.11772812750170497,"z":0.22510127103412494,"angularSizeDeg":0.15266666666666667,"magnitude":10.92,"distancePc":null,"distanceMethod":null,"constellation":"Vir","messier":null},{"id":"NGC4435","name":"Eyes","kind":"galaxy","x":-0.9669661543600755,"y":-0.11733613430916431,"z":0.22629336691001026,"angularSizeDeg":0.050499999999999996,"magnitude":10.96,"distancePc":null,"distanceMethod":null,"constellation":"Vir","messier":null},{"id":"NGC4548","name":"M91","kind":"galaxy","x":-0.9566106354468098,"y":-0.14912064822207213,"z":0.25031804654055567,"angularSizeDeg":0.0925,"magnitude":10.96,"distancePc":null,"distanceMethod":null,"constellation":"Com","messier":"M91"},{"id":"IC0434","name":"Flame Nebula","kind":"nebula","x":0.08266856216356266,"y":0.9956570296331706,"z":-0.042813411121525265,"angularSizeDeg":1.5,"magnitude":11,"distancePc":null,"distanceMethod":null,"constellation":"Ori","messier":null},{"id":"NGC7635","name":"Bubble Nebula","kind":"nebula","x":0.47452325388634087,"y":-0.0820496916963071,"z":0.8764105941929617,"angularSizeDeg":0.25,"magnitude":11,"distancePc":null,"distanceMethod":null,"constellation":"Cas","messier":null},{"id":"NGC4039","name":"Antennae Galaxies","kind":"galaxy","x":-0.9461311445596915,"y":-0.007810187365697263,"z":-0.3236894472599949,"angularSizeDeg":0.08933333333333333,"magnitude":11.04,"distancePc":23443770.2156,"distanceMethod":"redshift","constellation":"Crv","messier":null},{"id":"NGC6905","name":"Blue Flash Nebula","kind":"nebula","x":0.5465964404092263,"y":-0.7635963160104338,"z":0.3437339050883065,"angularSizeDeg":0.011166666666666667,"magnitude":11.1,"distancePc":2502.5025025025025,"distanceMethod":"parallax","constellation":"Del","messier":null},{"id":"NGC0253","name":"Sculptor Filament","kind":"galaxy","x":0.884777868866463,"y":0.18625841303615728,"z":-0.4271720102457231,"angularSizeDeg":0.4465,"magnitude":11.11,"distancePc":null,"distanceMethod":null,"constellation":"Scl","messier":null},{"id":"NGC6445","name":"Little Gem","kind":"nebula","x":-0.04405404649264451,"y":-0.9386026120075923,"z":-0.3421759455603928,"angularSizeDeg":0.009166666666666667,"magnitude":11.2,"distancePc":null,"distanceMethod":null,"constellation":"Sgr","messier":null},{"id":"NGC4567","name":"Butterfly Galaxies","kind":"galaxy","x":-0.9683155756060505,"y":-0.15572816571337697,"z":0.1952272635731752,"angularSizeDeg":0.04566666666666667,"magnitude":11.32,"distancePc":32548895.439999994,"distanceMethod":"redshift","constellation":"Vir","messier":null},{"id":"NGC2685","name":"Helix Galaxy","kind":"galaxy","x":-0.3598447447493709,"y":0.37400423391799076,"z":0.8547704912359089,"angularSizeDeg":0.07216666666666667,"magnitude":11.35,"distancePc":null,"distanceMethod":null,"constellation":"UMa","messier":null},{"id":"NGC6369","name":"Little Ghost Nebula","kind":"nebula","x":-0.1220695274782851,"y":-0.907068126210613,"z":-0.4028975612658826,"angularSizeDeg":0.0105,"magnitude":11.4,"distancePc":1139.47128532361,"distanceMethod":"parallax","constellation":"Oph","messier":null},{"id":"NGC6309","name":"Box Nebula","kind":"nebula","x":-0.19402923248189397,"y":-0.9552129761532145,"z":-0.22342969169518492,"angularSizeDeg":0.004333333333333333,"magnitude":11.5,"distancePc":3030.30303030303,"distanceMethod":"parallax","constellation":"Oph","messier":null},{"id":"NGC6741","name":"Phantom Streak Nebula","kind":"nebula","x":0.2698219617521237,"y":-0.9628782854629185,"z":-0.007843235316241898,"angularSizeDeg":0.0021666666666666666,"magnitude":11.5,"distancePc":null,"distanceMethod":null,"constellation":"Aql","messier":null},{"id":"NGC6537","name":"Red Spider Nebula","kind":"nebula","x":0.021415531285782843,"y":-0.9403826300295044,"z":-0.33944349184884937,"angularSizeDeg":0.0028333333333333335,"magnitude":11.6,"distancePc":null,"distanceMethod":null,"constellation":"Sgr","messier":null},{"id":"NGC2537","name":"Bear Claw Nebula","kind":"galaxy","x":-0.3815650401384719,"y":0.5806343006805955,"z":0.7192161907363336,"angularSizeDeg":0.034499999999999996,"magnitude":11.71,"distancePc":null,"distanceMethod":null,"constellation":"Lyn","messier":null},{"id":"NGC2261","name":"Hubble's Nebula","kind":"nebula","x":-0.16805471340025227,"y":0.9739845069359457,"z":0.1520256345249107,"angularSizeDeg":0.03333333333333333,"magnitude":11.85,"distancePc":null,"distanceMethod":null,"constellation":"Mon","messier":null},{"id":"NGC0040","name":"Bow-Tie nebula","kind":"nebula","x":0.29985617847941526,"y":0.017049600619663617,"z":0.9538320519601085,"angularSizeDeg":0.013333333333333334,"magnitude":11.89,"distancePc":1983.7333862328903,"distanceMethod":"parallax","constellation":"Cep","messier":null},{"id":"IC0443","name":"Gem A","kind":"nebula","x":-0.06693831997599382,"y":0.9212391800444476,"z":0.3831939906494167,"angularSizeDeg":0.8333333333333334,"magnitude":12,"distancePc":null,"distanceMethod":null,"constellation":"Gem","messier":null},{"id":"NGC1275","name":"Perseus A","kind":"galaxy","x":0.4818262470502416,"y":0.5732150984615317,"z":0.6627729011880434,"angularSizeDeg":0.036000000000000004,"magnitude":12.24,"distancePc":75205079.46399999,"distanceMethod":"redshift","constellation":"Per","messier":null},{"id":"NGC3928","name":"Miniature Spiral","kind":"galaxy","x":-0.6597995213866114,"y":0.02363546708956302,"z":0.75106987442811,"angularSizeDeg":0.02266666666666667,"magnitude":12.48,"distancePc":14081680.027199998,"distanceMethod":"redshift","constellation":"UMa","messier":null},{"id":"NGC4194","name":"Medusa Galaxy Merger","kind":"galaxy","x":-0.5792146648213137,"y":-0.03582668796228815,"z":0.8143873896896895,"angularSizeDeg":0.02733333333333333,"magnitude":12.77,"distancePc":35032890.092,"distanceMethod":"redshift","constellation":"UMa","messier":null},{"id":"NGC2573","name":"Polarissima Australis","kind":"galaxy","x":0.01049123379886698,"y":0.004982847626113312,"z":-0.9999325503467283,"angularSizeDeg":0.032,"magnitude":13.53,"distancePc":35182786.320999995,"distanceMethod":"redshift","constellation":"Oct","messier":null},{"id":"NGC1049","name":"Fornax Dwarf Cluster 3","kind":"cluster","x":0.6336003744111834,"y":0.5307232808149628,"z":-0.5629239422399888,"angularSizeDeg":0.02,"magnitude":13.59,"distancePc":null,"distanceMethod":null,"constellation":"For","messier":null},{"id":"NGC4990","name":"Cocoon Galaxy","kind":"galaxy","x":-0.9506066742820898,"y":-0.2964822309125129,"z":-0.09189797366364706,"angularSizeDeg":0.015000000000000001,"magnitude":13.82,"distancePc":45110199.4302,"distanceMethod":"redshift","constellation":"Vir","messier":null},{"id":"IC2431 NED02","name":"Browning","kind":"galaxy","x":-0.6978202065767134,"y":0.6704803832964035,"z":0.25199804544468335,"angularSizeDeg":0.01,"magnitude":14.04,"distancePc":213584995.32739997,"distanceMethod":"redshift","constellation":"Cnc","messier":null},{"id":"NGC3561","name":"the Guitar","kind":"galaxy","x":-0.8573816738596958,"y":0.185294162647438,"z":0.4801694894704691,"angularSizeDeg":0.0285,"magnitude":14.7,"distancePc":123129045.24999999,"distanceMethod":"redshift","constellation":"UMa","messier":null},{"id":"NGC3172","name":"Polarissima Borealis","kind":"galaxy","x":-0.01580395363392359,"y":0.0008812724253978144,"z":0.9998747213568553,"angularSizeDeg":0.019,"magnitude":15,"distancePc":87068295.302,"distanceMethod":"redshift","constellation":"UMi","messier":null},{"id":"IC0348","name":"omi Per Cloud","kind":"nebula","x":0.4716319837181382,"y":0.7029871732218153,"z":0.5323272548157715,"angularSizeDeg":0.16666666666666666,"magnitude":null,"distancePc":324.78077297823967,"distanceMethod":"parallax","constellation":"Per","messier":null},{"id":"IC0349","name":"Barnard's Merope Nebula","kind":"nebula","x":0.5033399620242317,"y":0.7628854357870489,"z":0.40577665592472106,"angularSizeDeg":0.42833333333333334,"magnitude":null,"distancePc":null,"distanceMethod":null,"constellation":"Tau","messier":null},{"id":"IC2220","name":"Toby Jug Nebula","kind":"nebula","x":-0.25044378864775796,"y":0.4478907426168544,"z":-0.8582958647260744,"angularSizeDeg":0.08333333333333333,"magnitude":null,"distancePc":null,"distanceMethod":null,"constellation":"Car","messier":null},{"id":"IC2602","name":"tet Car Cluster","kind":"cluster","x":-0.4079874570382135,"y":0.14255982216225474,"z":-0.9017887402294166,"angularSizeDeg":0.8,"magnitude":null,"distancePc":152.18383807639628,"distanceMethod":"parallax","constellation":"Car","messier":null},{"id":"NGC1432","name":"Maia Nebula","kind":"nebula","x":0.5033427117383886,"y":0.759218472460118,"z":0.4125935355954703,"angularSizeDeg":1,"magnitude":null,"distancePc":null,"distanceMethod":null,"constellation":"Tau","messier":null},{"id":"NGC1435","name":"Merope Nebula","kind":"nebula","x":0.5045760240905881,"y":0.7635479240253201,"z":0.40298586033452605,"angularSizeDeg":0.5,"magnitude":null,"distancePc":null,"distanceMethod":null,"constellation":"Tau","messier":null},{"id":"NGC1909","name":"the Witch Head Nebula","kind":"nebula","x":0.2360968954119782,"y":0.9634644066040075,"z":-0.12646973228410566,"angularSizeDeg":3,"magnitude":null,"distancePc":null,"distanceMethod":null,"constellation":"Eri","messier":null},{"id":"NGC1977","name":"the Running Man Nebula","kind":"nebula","x":0.1073398117557633,"y":0.9906293724950249,"z":-0.08444886714663716,"angularSizeDeg":0.16999999999999998,"magnitude":null,"distancePc":null,"distanceMethod":null,"constellation":"Ori","messier":null},{"id":"NGC2174","name":"Monkey Head Nebula","kind":"nebula","x":-0.038341081513896966,"y":0.9349072760710067,"z":0.3528148900171806,"angularSizeDeg":0.6666666666666666,"magnitude":null,"distancePc":null,"distanceMethod":null,"constellation":"Ori","messier":null},{"id":"NGC2237","name":"Rosette A","kind":"nebula","x":-0.1339407520744398,"y":0.9870734599402065,"z":0.08801056536236486,"angularSizeDeg":1.3333333333333333,"magnitude":null,"distancePc":null,"distanceMethod":null,"constellation":"Mon","messier":null},{"id":"NGC2246","name":"Rosette B","kind":"nebula","x":-0.14103992051641528,"y":0.985960398102509,"z":0.08938587245345335,"angularSizeDeg":0.16666666666666666,"magnitude":null,"distancePc":null,"distanceMethod":null,"constellation":"Mon","messier":null},{"id":"NGC2736","name":"Pencil Nebula","kind":"nebula","x":-0.4922638920453516,"y":0.491052532558318,"z":-0.7187097264239747,"angularSizeDeg":0.5,"magnitude":null,"distancePc":null,"distanceMethod":null,"constellation":"Vel","messier":null},{"id":"NGC4676","name":"Mice Galaxy","kind":"galaxy","x":-0.8422190808312809,"y":-0.17203399624332447,"z":0.5109513910542413,"angularSizeDeg":0.05,"magnitude":null,"distancePc":null,"distanceMethod":null,"constellation":"Com","messier":null},{"id":"NGC4755","name":"Herschel's Jewel Box","kind":"cluster","x":-0.48113062449471794,"y":-0.11466141605988796,"z":-0.8691179907472081,"angularSizeDeg":0.13,"magnitude":null,"distancePc":2164.5021645021643,"distanceMethod":"parallax","constellation":"Cru","messier":null},{"id":"NGC6188","name":"Rim Nebula","kind":"nebula","x":-0.22563937033906195,"y":-0.6207592330101122,"z":-0.7508294408090888,"angularSizeDeg":0.3333333333333333,"magnitude":null,"distancePc":null,"distanceMethod":null,"constellation":"Ara","messier":null},{"id":"NGC6357","name":"the War and Peace Nebula","kind":"nebula","x":-0.1267929160971821,"y":-0.8172907656943454,"z":-0.5621026247388669,"angularSizeDeg":0.065,"magnitude":null,"distancePc":1763.6684303350971,"distanceMethod":"parallax","constellation":"Sco","messier":null}] \ No newline at end of file diff --git a/tools/etl/build.ts b/tools/etl/build.ts index 649666b..0f586d0 100644 --- a/tools/etl/build.ts +++ b/tools/etl/build.ts @@ -1,8 +1,10 @@ import { statSync } from 'node:fs'; import { BodyRecord } from '../../src/app/shared/models/body.model'; +import { DeepSkyRecord } from '../../src/app/shared/models/deepsky.model'; import { ExoplanetRecord } from '../../src/app/shared/models/exoplanet.model'; import { StarRecord } from '../../src/app/shared/models/star.model'; +import { fetchDeepSky } from './fetchDeepSky'; import { fetchExoplanets } from './fetchExoplanets'; import { fetchSolarSystem } from './fetchSolarSystem'; import { fetchStars } from './fetchStars'; @@ -66,6 +68,42 @@ function validateExoplanets(exoplanets: ExoplanetRecord[], starIds: Set) console.log(` ${crossReferenced}/${exoplanets.length} exoplanets cross-referenced to a HYG host star.`); } +const UNIT_VECTOR_TOLERANCE = 1e-6; + +function validateDeepSky(objects: DeepSkyRecord[]): void { + assertCondition(objects.length > 0, 'No deep-sky objects were produced.'); + + const ids = new Set(); + for (const object of objects) { + assertCondition(!!object.id, `Deep-sky object has no id: ${JSON.stringify(object)}`); + assertCondition(!ids.has(object.id), `Duplicate deep-sky id: ${object.id}`); + ids.add(object.id); + assertCondition(!!object.name, `Deep-sky object ${object.id} has no name.`); + + // Positions are directions, so every one of them must be a unit vector — a zero-length + // or mis-scaled entry would silently collapse onto the origin on the backdrop shell. + const length = Math.hypot(object.x, object.y, object.z); + assertCondition(Math.abs(length - 1) < UNIT_VECTOR_TOLERANCE, `Deep-sky object ${object.id} has a non-unit direction (length ${length}).`); + + assertCondition(object.angularSizeDeg >= 0, `Deep-sky object ${object.id} has a negative angular size.`); + assertCondition(object.distancePc === null || object.distancePc > 0, `Deep-sky object ${object.id} has a non-positive distance.`); + // The distance and its provenance have to travel together, or the UI cannot say where a + // number came from. + assertCondition( + (object.distancePc === null) === (object.distanceMethod === null), + `Deep-sky object ${object.id} has a distance/method mismatch.` + ); + } + + const kinds = new Set(objects.map((object) => object.kind)); + for (const kind of ['galaxy', 'nebula', 'cluster'] as const) { + assertCondition(kinds.has(kind), `No deep-sky objects of kind "${kind}" were produced.`); + } + + const withDistance = objects.filter((object) => object.distancePc !== null).length; + console.log(` ${withDistance}/${objects.length} deep-sky objects have a derived distance.`); +} + /** * Orchestrates the whole ETL pipeline: fetches every source (each caches its own raw * responses under `tools/etl/.cache/`), writes the static assets under `src/assets/data/`, @@ -80,16 +118,20 @@ async function build(): Promise { console.log(); const exoplanets = await fetchExoplanets(stars); console.log(); + const deepSky = await fetchDeepSky(); + console.log(); console.log('Validating output...'); validateStars(stars); validateBodies(bodies); validateExoplanets(exoplanets, new Set(stars.map((star) => star.id))); + validateDeepSky(deepSky); console.log('\nETL completed successfully:'); console.log(` stars: ${stars.length}`); console.log(` bodies: ${bodies.length}`); console.log(` exoplanets: ${exoplanets.length}`); + console.log(` deep sky: ${deepSky.length}`); } build().catch((error) => { diff --git a/tools/etl/fetchDeepSky.ts b/tools/etl/fetchDeepSky.ts new file mode 100644 index 0000000..f2d4caa --- /dev/null +++ b/tools/etl/fetchDeepSky.ts @@ -0,0 +1,147 @@ +import { writeFileSync } from 'node:fs'; + +import { parseSexagesimal, raDecToUnitVector } from '../../src/app/shared/astro/coordinates'; +import { classifyOpenNgcType, estimateDeepSkyDistancePc, isNotableDeepSkyObject } from '../../src/app/shared/astro/deep-sky'; +import { DeepSkyRecord } from '../../src/app/shared/models/deepsky.model'; +import { parseCsvObjects } from './lib/csv'; +import { fetchTextCached } from './lib/http'; +import { dataPath, ensureDataDir } from './lib/paths'; + +const OPENNGC_CSV_URL = 'https://raw.githubusercontent.com/mattiaverga/OpenNGC/master/database_files/NGC.csv'; +/** OpenNGC publishes semicolon-separated files, not comma-separated. */ +const OPENNGC_DELIMITER = ';'; + +const ARCMIN_PER_DEGREE = 60; + +/** + * Reads a numeric catalog column. Empty strings mean "not measured" and must become `null` + * rather than `0`: `Number('')` is `0`, which would silently turn every unphotometered object + * into a magnitude-0 blaze brighter than Sirius. + */ +function numericField(value: string | undefined): number | null { + if (value === undefined || value.trim() === '') { + return null; + } + const parsed = Number(value); + return Number.isFinite(parsed) ? parsed : null; +} + +function textField(value: string | undefined): string | null { + const trimmed = value?.trim(); + return trimmed ? trimmed : null; +} + +/** OpenNGC stores Messier numbers zero-padded ("031"); render them as "M31". */ +function messierDesignation(value: string | undefined): string | null { + const raw = textField(value); + if (!raw) { + return null; + } + const number = Number(raw); + return Number.isFinite(number) ? `M${number}` : `M${raw}`; +} + +/** OpenNGC's `Common names` column is comma-separated; the first entry is the best known. */ +function primaryCommonName(value: string | undefined): string | null { + const raw = textField(value); + return raw ? (textField(raw.split(',')[0]) ?? null) : null; +} + +/** + * Display name, most recognisable first: a common name ("Andromeda Galaxy") beats a Messier + * number ("M31"), which beats the raw catalog designation ("NGC0224"). + */ +function resolveName(commonName: string | null, messier: string | null, designation: string): string { + return commonName ?? messier ?? designation; +} + +/** + * Downloads the OpenNGC catalog, keeps the objects notable enough to be worth drawing, and + * writes `deepsky.json` — each entry a unit direction on the celestial sphere plus its kind, + * apparent size, magnitude and (where derivable) distance. + * + * See `DeepSkyRecord` for why these are stored as directions rather than positions. + */ +export async function fetchDeepSky(): Promise { + console.log('Fetching OpenNGC deep-sky catalog...'); + const csv = await fetchTextCached(OPENNGC_CSV_URL, 'openngc.csv'); + const rows = parseCsvObjects(csv, OPENNGC_DELIMITER); + + const records: DeepSkyRecord[] = []; + let skippedUnclassified = 0; + let skippedNotNotable = 0; + let skippedUnpositioned = 0; + + for (const row of rows) { + const kind = classifyOpenNgcType(row['Type']); + if (!kind) { + skippedUnclassified++; + continue; + } + + const magnitude = numericField(row['V-Mag']) ?? numericField(row['B-Mag']); + const messier = messierDesignation(row['M']); + const commonName = primaryCommonName(row['Common names']); + + if (!isNotableDeepSkyObject({ messier, commonName, magnitude })) { + skippedNotNotable++; + continue; + } + + const raHours = parseSexagesimal(row['RA']); + const decDeg = parseSexagesimal(row['Dec']); + if (raHours === null || decDeg === null) { + skippedUnpositioned++; + continue; + } + + const designation = textField(row['Name']) ?? ''; + if (!designation) { + skippedUnpositioned++; + continue; + } + + const { x, y, z } = raDecToUnitVector(raHours, decDeg); + const distance = estimateDeepSkyDistancePc({ + kind, + redshift: numericField(row['Redshift']), + parallaxMas: numericField(row['Pax']) + }); + + records.push({ + id: designation, + name: resolveName(commonName, messier, designation), + kind, + x, + y, + z, + angularSizeDeg: (numericField(row['MajAx']) ?? 0) / ARCMIN_PER_DEGREE, + magnitude, + distancePc: distance?.distancePc ?? null, + distanceMethod: distance?.method ?? null, + constellation: textField(row['Const']) ?? 'Unknown', + messier + }); + } + + // Brightest first, so a consumer taking a prefix gets the most prominent objects. Objects + // with no measured magnitude sort last rather than being treated as infinitely bright. + records.sort((a, b) => (a.magnitude ?? Infinity) - (b.magnitude ?? Infinity) || a.id.localeCompare(b.id)); + + ensureDataDir(); + writeFileSync(dataPath('deepsky.json'), JSON.stringify(records)); + + const withDistance = records.filter((record) => record.distancePc !== null).length; + console.log(` kept ${records.length} deep-sky objects (of ${rows.length} catalog rows).`); + console.log(` skipped: ${skippedUnclassified} not deep-sky, ${skippedNotNotable} too faint, ${skippedUnpositioned} unusable coordinates.`); + console.log(` ${withDistance}/${records.length} have a derivable distance.`); + + return records; +} + +if (require.main === module) { + fetchDeepSky().catch((error) => { + console.error(error); + process.exitCode = 1; + }); +}