@johnmorrisdotca/chikyu-earth 0.4.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/NOTICE.md +39 -1
  3. package/README.md +199 -24
  4. package/dist/countries.d.ts +11 -0
  5. package/dist/countries.js +11 -0
  6. package/dist/element.d.ts +24 -1
  7. package/dist/element.js +70 -5
  8. package/dist/globe.d.ts +15 -1
  9. package/dist/globe.js +15 -1
  10. package/dist/index.d.ts +12 -1
  11. package/dist/index.js +12 -1
  12. package/dist/markers/cluster.d.ts +38 -0
  13. package/dist/markers/cluster.js +42 -0
  14. package/dist/markers/index.d.ts +5 -0
  15. package/dist/markers/index.js +6 -0
  16. package/dist/markers/labels.d.ts +64 -0
  17. package/dist/markers/labels.js +70 -0
  18. package/dist/markers/layer.d.ts +129 -0
  19. package/dist/markers/layer.js +455 -0
  20. package/dist/markers/project.d.ts +82 -0
  21. package/dist/markers/project.js +93 -0
  22. package/dist/markers/types.d.ts +83 -0
  23. package/dist/markers/types.js +1 -0
  24. package/dist/realistic/earth.d.ts +81 -0
  25. package/dist/realistic/earth.js +163 -0
  26. package/dist/realistic/element.d.ts +93 -0
  27. package/dist/realistic/element.js +284 -0
  28. package/dist/realistic/gate.d.ts +64 -0
  29. package/dist/realistic/gate.js +72 -0
  30. package/dist/realistic/index.d.ts +5 -0
  31. package/dist/realistic/index.js +6 -0
  32. package/dist/realistic/shaders.d.ts +10 -0
  33. package/dist/realistic/shaders.js +124 -0
  34. package/dist/realistic/shared.d.ts +23 -0
  35. package/dist/realistic/shared.js +51 -0
  36. package/dist/realistic/sun.d.ts +44 -0
  37. package/dist/realistic/sun.js +65 -0
  38. package/dist/realistic/textures.d.ts +40 -0
  39. package/dist/realistic/textures.js +141 -0
  40. package/dist/realistic/tiers.d.ts +94 -0
  41. package/dist/realistic/tiers.js +94 -0
  42. package/dist/rotate.d.ts +59 -4
  43. package/dist/rotate.js +59 -4
  44. package/dist/space/element.d.ts +134 -0
  45. package/dist/space/element.js +394 -0
  46. package/dist/space/ephemeris.d.ts +151 -0
  47. package/dist/space/ephemeris.js +162 -0
  48. package/dist/space/index.d.ts +3 -0
  49. package/dist/space/index.js +5 -0
  50. package/dist/space/scale.d.ts +124 -0
  51. package/dist/space/scale.js +141 -0
  52. package/dist/space/scene.d.ts +131 -0
  53. package/dist/space/scene.js +295 -0
  54. package/dist/styles.d.ts +65 -2
  55. package/dist/styles.js +43 -2
  56. package/dist/textures/clouds-1024.webp +0 -0
  57. package/dist/textures/clouds-2048.webp +0 -0
  58. package/dist/textures/day-1024.webp +0 -0
  59. package/dist/textures/day-2048.webp +0 -0
  60. package/dist/textures/day-4096.webp +0 -0
  61. package/dist/textures/moon-1024.webp +0 -0
  62. package/dist/textures/moon-2048.webp +0 -0
  63. package/dist/textures/night-1024.webp +0 -0
  64. package/dist/textures/night-2048.webp +0 -0
  65. package/dist/textures/water-1024.webp +0 -0
  66. package/dist/textures/water-2048.webp +0 -0
  67. package/dist/types.d.ts +55 -5
  68. package/dist/view.d.ts +64 -3
  69. package/dist/view.js +56 -3
  70. package/package.json +93 -8
@@ -0,0 +1,295 @@
1
+ import { ATMOSPHERE_OUTER, sphere } from "../realistic/earth.js";
2
+ import { ATMOSPHERE_FRAG, ATMOSPHERE_VERT, BODY_FRAG, CLOUD_FRAG, GLOW_FRAG, GLOW_VERT, SURFACE_FRAG, SURFACE_VERT } from "../realistic/shaders.js";
3
+ import { createTextureSet } from "../realistic/textures.js";
4
+ import { createTrack, orbitPath, PLANETS } from "./ephemeris.js";
5
+ import { placeAround, placeMoon, placeSun, RADIUS } from "./scale.js";
6
+ const RAD = Math.PI / 180;
7
+ /** The camera's distance, in Earth radii, at which the Earth fills the view at zoom 1. */
8
+ export const FIT_DISTANCE = 3.4;
9
+ const FOV = 35;
10
+ const LAYERS = ["day", "night", "clouds", "water", "moon"];
11
+ const PLAIN_COLOR = (name) => (name === "Sun" ? "#ffb347" : COLORS[name]);
12
+ /**
13
+ * The ten bodies the space view places, names and lets a reader choose: the Earth, the Sun, the Moon and the seven other planets.
14
+ *
15
+ * @example
16
+ * ```ts no-run
17
+ * import { BODY_NAMES } from "@johnmorrisdotca/chikyu-earth/space/element";
18
+ *
19
+ * console.log(BODY_NAMES.join(", "));
20
+ * ```
21
+ */
22
+ export const BODY_NAMES = ["Earth", "Sun", "Moon", ...PLANETS];
23
+ const COLORS = { Mercury: "#a79f96", Venus: "#e3c98a", Mars: "#c1593a", Jupiter: "#d2b48c", Saturn: "#e4d4a0", Uranus: "#9fd6dc", Neptune: "#4a6bd6" };
24
+ const ORBIT_COLORS = { Mercury: "#6b655f", Venus: "#8c7c55", Mars: "#7a4034", Jupiter: "#85735a", Saturn: "#8d8567", Uranus: "#5f8a8e", Neptune: "#34499a" };
25
+ export const newSpaceModel = () => ({
26
+ time: Date.now(),
27
+ mode: "compressed",
28
+ view: { yaw: 0, tilt: 0, zoom: 1 },
29
+ centre: { lon: 0, lat: 0 },
30
+ cameraSpin: null,
31
+ orbits: true,
32
+ labels: true,
33
+ stepMs: 1000,
34
+ focus: "Earth",
35
+ drawn: null,
36
+ lookups: 0,
37
+ ratio: () => Math.min(globalThis.devicePixelRatio || 1, 2),
38
+ });
39
+ /** An inertial direction from the Earth's centre, in scene axes, for right ascension and declination in degrees. */
40
+ const direction = (ra, dec) => [Math.cos(dec * RAD) * Math.cos(ra * RAD), Math.sin(dec * RAD), -Math.cos(dec * RAD) * Math.sin(ra * RAD)];
41
+ const unit = (v) => {
42
+ const l = Math.hypot(v[0], v[1], v[2]) || 1;
43
+ return [v[0] / l, v[1] / l, v[2] / l];
44
+ };
45
+ /** A square facing +z, 2 x 2, with uvs 0 to 1. */
46
+ function quad(three) {
47
+ const g = new three.BufferGeometry();
48
+ g.setAttribute("position", new three.BufferAttribute(new Float32Array([-1, -1, 0, 1, -1, 0, 1, 1, 0, -1, 1, 0]), 3));
49
+ g.setAttribute("uv", new three.BufferAttribute(new Float32Array([0, 0, 1, 0, 1, 1, 0, 1]), 2));
50
+ g.setIndex([0, 1, 2, 0, 2, 3]);
51
+ return g;
52
+ }
53
+ /**
54
+ * The default look: a sphere in the planet's colour, lit by the Sun, and for the Sun a glowing disc.
55
+ *
56
+ * @example
57
+ * ```ts no-run
58
+ * import { plainPainter } from "@johnmorrisdotca/chikyu-earth/space";
59
+ *
60
+ * console.log(typeof plainPainter.make);
61
+ * ```
62
+ */
63
+ export const plainPainter = {
64
+ make(three, name) {
65
+ if (name === "Sun") {
66
+ const geometry = quad(three);
67
+ const material = new three.ShaderMaterial({ vertexShader: GLOW_VERT, fragmentShader: GLOW_FRAG, uniforms: { tint: { value: new three.Color(PLAIN_COLOR(name)) } }, transparent: true, depthWrite: false });
68
+ const mesh = new three.Mesh(geometry, material);
69
+ mesh.renderOrder = 4;
70
+ mesh.frustumCulled = false;
71
+ mesh.scale.setScalar(3.2);
72
+ const group = new three.Group();
73
+ group.add(mesh);
74
+ return {
75
+ object: group,
76
+ billboard: true,
77
+ dispose() {
78
+ geometry.dispose();
79
+ material.dispose();
80
+ },
81
+ };
82
+ }
83
+ const geometry = sphere(three, 48, 24);
84
+ const sun = { value: new three.Vector3(1, 0, 0) };
85
+ const material = new three.ShaderMaterial({ vertexShader: SURFACE_VERT, fragmentShader: BODY_FRAG, uniforms: { map: { value: null }, tint: { value: new three.Color(PLAIN_COLOR(name)) }, textured: { value: 0 }, sunDir: sun, ambient: { value: 0.2 } } });
86
+ return {
87
+ object: new three.Mesh(geometry, material),
88
+ light(toSun) {
89
+ sun.value.set(toSun[0], toSun[1], toSun[2]);
90
+ },
91
+ dispose() {
92
+ geometry.dispose();
93
+ material.dispose();
94
+ },
95
+ };
96
+ },
97
+ };
98
+ export function createSpaceItem(model, deps) {
99
+ return {
100
+ key: "chikyu-earth:space",
101
+ async build({ three, width, height }) {
102
+ let size = { width, height };
103
+ const textures = await createTextureSet(three, LAYERS, {
104
+ assets: deps.assets,
105
+ textureBase: deps.textureBase,
106
+ cap: deps.cap,
107
+ invalidate: deps.invalidate,
108
+ ...(deps.onError ? { onError: deps.onError } : {}),
109
+ ...(deps.onTier ? { onTier: deps.onTier } : {}),
110
+ });
111
+ const uniform = textures.uniforms;
112
+ const track = createTrack(model.stepMs);
113
+ let trackStep = model.stepMs;
114
+ const sunEarth = { value: new three.Vector3(1, 0, 0) };
115
+ const sunView = { value: new three.Vector3(1, 0, 0) };
116
+ const grid = sphere(three, 96, 48);
117
+ const solid = sphere(three, 48, 24);
118
+ const earthMaterial = new three.ShaderMaterial({
119
+ vertexShader: SURFACE_VERT,
120
+ fragmentShader: SURFACE_FRAG,
121
+ uniforms: { dayMap: uniform.day, nightMap: uniform.night, waterMap: uniform.water, sunDir: sunEarth, lights: { value: 1 }, ortho: { value: 0 } },
122
+ });
123
+ const cloudShift = { value: 0 };
124
+ const cloudMaterial = new three.ShaderMaterial({
125
+ vertexShader: SURFACE_VERT,
126
+ fragmentShader: CLOUD_FRAG,
127
+ uniforms: { cloudMap: uniform.clouds, sunDir: sunEarth, shift: cloudShift, opacity: { value: 0.9 } },
128
+ transparent: true,
129
+ depthWrite: false,
130
+ });
131
+ const atmosphereMaterial = new three.ShaderMaterial({
132
+ vertexShader: ATMOSPHERE_VERT,
133
+ fragmentShader: ATMOSPHERE_FRAG,
134
+ uniforms: { sunView, outer: { value: ATMOSPHERE_OUTER }, ortho: { value: 0 } },
135
+ transparent: true,
136
+ depthWrite: false,
137
+ side: three.BackSide,
138
+ });
139
+ const earth = new three.Mesh(grid, earthMaterial);
140
+ const clouds = new three.Mesh(grid, cloudMaterial);
141
+ clouds.scale.setScalar(1.006);
142
+ clouds.renderOrder = 3;
143
+ const glow = new three.Mesh(grid, atmosphereMaterial);
144
+ glow.scale.setScalar(ATMOSPHERE_OUTER);
145
+ glow.renderOrder = 2;
146
+ glow.frustumCulled = false;
147
+ const moonSun = { value: new three.Vector3(1, 0, 0) };
148
+ const moon = new three.Mesh(solid, new three.ShaderMaterial({ vertexShader: SURFACE_VERT, fragmentShader: BODY_FRAG, uniforms: { map: uniform.moon, tint: { value: new three.Color("#bbbbbb") }, textured: { value: 1 }, sunDir: moonSun, ambient: { value: 0.03 } } }));
149
+ const painter = deps.painter ?? plainPainter;
150
+ const scene = new three.Scene();
151
+ scene.add(earth, clouds, glow, moon);
152
+ const sunDrawing = painter.make(three, "Sun");
153
+ scene.add(sunDrawing.object);
154
+ const planets = new Map();
155
+ for (const name of PLANETS) {
156
+ const drawing = painter.make(three, name);
157
+ const positions = new Float32Array(96 * 3);
158
+ const lineGeometry = new three.BufferGeometry();
159
+ lineGeometry.setAttribute("position", new three.BufferAttribute(positions, 3));
160
+ const orbit = new three.LineLoop(lineGeometry, new three.LineBasicMaterial({ color: ORBIT_COLORS[name], transparent: true, opacity: 0.7 }));
161
+ orbit.frustumCulled = false;
162
+ scene.add(drawing.object, orbit);
163
+ planets.set(name, { drawing, orbit, positions, samples: [], epoch: Number.NaN });
164
+ }
165
+ const camera = new three.PerspectiveCamera(FOV, width / Math.max(1, height), 0.05, 1000);
166
+ const target = new three.Vector3();
167
+ const projected = new three.Vector3();
168
+ const labelFor = (v) => {
169
+ projected.set(v[0], v[1], v[2]).project(camera);
170
+ if (projected.z > 1 || projected.z < -1)
171
+ return null;
172
+ return { x: ((projected.x + 1) / 2) * size.width, y: ((1 - projected.y) / 2) * size.height };
173
+ };
174
+ return {
175
+ scene,
176
+ camera,
177
+ update() {
178
+ const frame = track.at(model.time);
179
+ model.lookups = track.lookups;
180
+ if (model.stepMs !== trackStep) {
181
+ trackStep = model.stepMs;
182
+ track.setStep(model.stepMs);
183
+ }
184
+ const mode = model.mode;
185
+ const sunPos = placeSun(mode, frame.sun);
186
+ const moonPos = placeMoon(mode, frame.moon);
187
+ const helioOf = (planet) => [planet[0] - frame.sun[0], planet[1] - frame.sun[1], planet[2] - frame.sun[2]];
188
+ // the Earth, turned by the sidereal time; light from the Sun
189
+ earth.rotation.set(0, (frame.spin + 90) * RAD, 0);
190
+ clouds.rotation.copy(earth.rotation);
191
+ const toSun = unit(frame.sun);
192
+ sunEarth.value.set(toSun[0], toSun[1], toSun[2]);
193
+ cloudShift.value = -(((model.time / 3_600_000) * 1.5) % 360) / 360;
194
+ earthMaterial.uniforms["lights"].value = 1;
195
+ // the Moon, with its near side to the Earth
196
+ moon.position.set(moonPos[0], moonPos[1], moonPos[2]);
197
+ moon.scale.setScalar(RADIUS["Moon"][mode]);
198
+ moon.up.set(0, 1, 0);
199
+ moon.lookAt(0, 0, 0);
200
+ const moonToSun = unit([frame.sun[0] - frame.moon[0], frame.sun[1] - frame.moon[1], frame.sun[2] - frame.moon[2]]);
201
+ moonSun.value.set(moonToSun[0], moonToSun[1], moonToSun[2]);
202
+ // where everything is, and how big
203
+ const pos = { Earth: [0, 0, 0], Moon: moonPos, Sun: sunPos };
204
+ for (const name of PLANETS)
205
+ pos[name] = placeAround(mode, sunPos, helioOf(frame.planets[name]));
206
+ const radiusOf = (name) => (name === "Earth" ? 1 : RADIUS[name][mode]);
207
+ // the Sun and the planets, drawn by the painter, and the planets' orbits round the Sun
208
+ sunDrawing.object.position.set(sunPos[0], sunPos[1], sunPos[2]);
209
+ sunDrawing.object.scale.setScalar(radiusOf("Sun"));
210
+ if (sunDrawing.billboard)
211
+ sunDrawing.object.quaternion.copy(camera.quaternion);
212
+ for (const name of PLANETS) {
213
+ const p = planets.get(name);
214
+ p.drawing.object.position.set(pos[name][0], pos[name][1], pos[name][2]);
215
+ p.drawing.object.scale.setScalar(radiusOf(name));
216
+ p.drawing.light?.(unit([frame.sun[0] - frame.planets[name][0], frame.sun[1] - frame.planets[name][1], frame.sun[2] - frame.planets[name][2]]));
217
+ p.orbit.visible = model.orbits;
218
+ if (model.orbits) {
219
+ if (!Number.isFinite(p.epoch) || Math.abs(model.time - p.epoch) > 365 * 86_400_000) {
220
+ p.samples = orbitPath(name, model.time, 96).slice(0, 96);
221
+ p.epoch = model.time;
222
+ }
223
+ for (let i = 0; i < 96; i += 1) {
224
+ const o = placeAround(mode, sunPos, p.samples[i]);
225
+ p.positions[i * 3] = o[0];
226
+ p.positions[i * 3 + 1] = o[1];
227
+ p.positions[i * 3 + 2] = o[2];
228
+ }
229
+ p.orbit.geometry.getAttribute("position").needsUpdate = true;
230
+ }
231
+ }
232
+ // the camera: looks at the focus body from the direction the sky was in when it was last moved, turned by yaw and tilt, as far as the zoom says
233
+ const focus = model.focus;
234
+ const focusRadius = radiusOf(focus);
235
+ const spin = model.cameraSpin ?? frame.spin;
236
+ const lon = model.centre.lon - model.view.yaw;
237
+ const lat = Math.max(-90, Math.min(90, model.centre.lat + model.view.tilt));
238
+ const aim = direction(spin + lon, lat);
239
+ const distance = Math.max(1.05 * focusRadius, (FIT_DISTANCE * focusRadius) / Math.max(1e-9, model.view.zoom));
240
+ target.set(pos[focus][0], pos[focus][1], pos[focus][2]);
241
+ camera.position.set(target.x + aim[0] * distance, target.y + aim[1] * distance, target.z + aim[2] * distance);
242
+ camera.up.set(0, 1, 0);
243
+ camera.lookAt(target);
244
+ camera.near = Math.max(0.0005, distance * 0.02 - focusRadius * 0.5, focusRadius * 0.01);
245
+ camera.far = Math.max(2000, distance * 4 + (mode === "true" ? 40 * 23_481 : 400));
246
+ camera.aspect = size.width / Math.max(1, size.height);
247
+ camera.updateProjectionMatrix();
248
+ camera.updateMatrixWorld();
249
+ sunView.value.copy(sunEarth.value).transformDirection(camera.matrixWorldInverse);
250
+ // how many pixels across each body is: for the pictures the Earth and the Moon need, and for what is chosen
251
+ const half = Math.tan((FOV / 2) * RAD);
252
+ const radiusPxOf = (name) => {
253
+ const d = Math.hypot(pos[name][0] - camera.position.x, pos[name][1] - camera.position.y, pos[name][2] - camera.position.z);
254
+ const r = radiusOf(name);
255
+ return d <= r * 1.001 ? 1e5 : ((size.height / 2) * (r / Math.sqrt(d * d - r * r))) / half;
256
+ };
257
+ const px = {};
258
+ for (const name of BODY_NAMES)
259
+ px[name] = radiusPxOf(name);
260
+ model.drawn = { frame, distance, radiusPx: px.Earth };
261
+ const ratio = model.ratio();
262
+ const earthPx = Math.min(px.Earth, 1e5) * ratio;
263
+ textures.sync({ day: earthPx, night: earthPx, clouds: earthPx, water: earthPx, moon: Math.min(px.Moon, 1e5) * ratio });
264
+ if (deps.onLabels && model.labels) {
265
+ const places = {};
266
+ for (const name of BODY_NAMES) {
267
+ const at = labelFor(pos[name]);
268
+ places[name] = at ? { x: at.x, y: at.y, r: px[name] } : null;
269
+ }
270
+ deps.onLabels(places);
271
+ }
272
+ return false;
273
+ },
274
+ resize(w, h) {
275
+ size = { width: w, height: h };
276
+ },
277
+ dispose() {
278
+ textures.dispose();
279
+ sunDrawing.dispose();
280
+ for (const p of planets.values()) {
281
+ p.drawing.dispose();
282
+ p.orbit.geometry.dispose();
283
+ p.orbit.material.dispose();
284
+ }
285
+ grid.dispose();
286
+ solid.dispose();
287
+ earthMaterial.dispose();
288
+ cloudMaterial.dispose();
289
+ atmosphereMaterial.dispose();
290
+ moon.material.dispose();
291
+ },
292
+ };
293
+ },
294
+ };
295
+ }
package/dist/styles.d.ts CHANGED
@@ -1,3 +1,14 @@
1
+ /**
2
+ * The lines and fills of one look: colours and widths only, never the geometry.
3
+ *
4
+ * @example
5
+ * ```ts
6
+ * import { STYLES, type Style } from "@johnmorrisdotca/chikyu-earth";
7
+ *
8
+ * const thick: Style = STYLES.thick;
9
+ * console.log(thick.outline, thick.line);
10
+ * ```
11
+ */
1
12
  export interface Style {
2
13
  /** The colour of the outlines and the rim. */
3
14
  line: string;
@@ -12,6 +23,16 @@ export interface Style {
12
23
  /** Graticule width, in SVG units. */
13
24
  graticule: number;
14
25
  }
26
+ /**
27
+ * The looks the globe is drawn in, by name: `line` (thin outlines) and `thick`.
28
+ *
29
+ * @example
30
+ * ```ts
31
+ * import { STYLES } from "@johnmorrisdotca/chikyu-earth";
32
+ *
33
+ * console.log(Object.keys(STYLES), STYLES.line.ocean);
34
+ * ```
35
+ */
15
36
  export declare const STYLES: {
16
37
  readonly line: {
17
38
  readonly line: "#8fa1b8";
@@ -30,9 +51,51 @@ export declare const STYLES: {
30
51
  readonly graticule: 0.5;
31
52
  };
32
53
  };
54
+ /**
55
+ * The name of a look: `"line"` or `"thick"`.
56
+ *
57
+ * @example
58
+ * ```ts
59
+ * import { globe, type StyleName } from "@johnmorrisdotca/chikyu-earth";
60
+ *
61
+ * const look: StyleName = "thick";
62
+ * console.log(globe({ style: look, size: 100 }).includes("stroke-width"));
63
+ * ```
64
+ */
33
65
  export type StyleName = keyof typeof STYLES;
34
- /** The style a drawing is in when none is named. */
66
+ /**
67
+ * The style a drawing is in when none is named.
68
+ *
69
+ * @example
70
+ * ```ts
71
+ * import { DEFAULT_STYLE } from "@johnmorrisdotca/chikyu-earth";
72
+ *
73
+ * console.log(DEFAULT_STYLE);
74
+ * ```
75
+ */
35
76
  export declare const DEFAULT_STYLE: StyleName;
77
+ /**
78
+ * Every style's name, in the order they are listed.
79
+ *
80
+ * @example
81
+ * ```ts
82
+ * import { STYLE_NAMES } from "@johnmorrisdotca/chikyu-earth";
83
+ *
84
+ * console.log(STYLE_NAMES.join(", "));
85
+ * ```
86
+ */
36
87
  export declare const STYLE_NAMES: StyleName[];
37
- /** A value from outside as a style's name: the default for anything else. */
88
+ /**
89
+ * A value from outside (an attribute, a query parameter) as a style's name: the default for anything else.
90
+ *
91
+ * @param value - Anything.
92
+ * @returns A valid style name; {@link DEFAULT_STYLE} when `value` names none.
93
+ *
94
+ * @example
95
+ * ```ts
96
+ * import { cleanStyle } from "@johnmorrisdotca/chikyu-earth";
97
+ *
98
+ * console.log(cleanStyle("thick"), cleanStyle("neon"), cleanStyle(null));
99
+ * ```
100
+ */
38
101
  export declare const cleanStyle: (value: unknown) => StyleName;
package/dist/styles.js CHANGED
@@ -1,11 +1,52 @@
1
1
  // The two looks the globe is drawn in, the same line colours as hako-devices and uchusen-rockets: one table entry each,
2
2
  // a look changes lines and fills, never the geometry. Pure.
3
+ /**
4
+ * The looks the globe is drawn in, by name: `line` (thin outlines) and `thick`.
5
+ *
6
+ * @example
7
+ * ```ts
8
+ * import { STYLES } from "@johnmorrisdotca/chikyu-earth";
9
+ *
10
+ * console.log(Object.keys(STYLES), STYLES.line.ocean);
11
+ * ```
12
+ */
3
13
  export const STYLES = {
4
14
  line: { line: "#8fa1b8", ocean: "#0b1424", land: "#13203a", outline: 0.5, horizon: 1, graticule: 0.3 },
5
15
  thick: { line: "#b4c2d4", ocean: "#0b1424", land: "#1a2b47", outline: 1.2, horizon: 2, graticule: 0.5 },
6
16
  };
7
- /** The style a drawing is in when none is named. */
17
+ /**
18
+ * The style a drawing is in when none is named.
19
+ *
20
+ * @example
21
+ * ```ts
22
+ * import { DEFAULT_STYLE } from "@johnmorrisdotca/chikyu-earth";
23
+ *
24
+ * console.log(DEFAULT_STYLE);
25
+ * ```
26
+ */
8
27
  export const DEFAULT_STYLE = "line";
28
+ /**
29
+ * Every style's name, in the order they are listed.
30
+ *
31
+ * @example
32
+ * ```ts
33
+ * import { STYLE_NAMES } from "@johnmorrisdotca/chikyu-earth";
34
+ *
35
+ * console.log(STYLE_NAMES.join(", "));
36
+ * ```
37
+ */
9
38
  export const STYLE_NAMES = Object.keys(STYLES);
10
- /** A value from outside as a style's name: the default for anything else. */
39
+ /**
40
+ * A value from outside (an attribute, a query parameter) as a style's name: the default for anything else.
41
+ *
42
+ * @param value - Anything.
43
+ * @returns A valid style name; {@link DEFAULT_STYLE} when `value` names none.
44
+ *
45
+ * @example
46
+ * ```ts
47
+ * import { cleanStyle } from "@johnmorrisdotca/chikyu-earth";
48
+ *
49
+ * console.log(cleanStyle("thick"), cleanStyle("neon"), cleanStyle(null));
50
+ * ```
51
+ */
11
52
  export const cleanStyle = (value) => typeof value === "string" && Object.hasOwn(STYLES, value) ? value : DEFAULT_STYLE;
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
package/dist/types.d.ts CHANGED
@@ -1,5 +1,15 @@
1
1
  import type { StyleName } from "./styles.ts";
2
- /** What one country's outline carries: its English and Japanese name and its ISO alpha-2 code, all from kuni. */
2
+ /**
3
+ * What one country's outline carries: its English and Japanese name and its ISO alpha-2 code, all from kuni.
4
+ *
5
+ * @example
6
+ * ```ts
7
+ * import { countryList, type CountryProps } from "@johnmorrisdotca/chikyu-earth";
8
+ *
9
+ * const first: CountryProps | undefined = countryList()[0];
10
+ * console.log(first?.name, first?.alpha2);
11
+ * ```
12
+ */
3
13
  export interface CountryProps {
4
14
  /** English name, from kuni (Natural Earth's own name where kuni has no entry). */
5
15
  name: string;
@@ -8,7 +18,17 @@ export interface CountryProps {
8
18
  /** ISO 3166-1 alpha-2 code, from kuni, or null where there is no code. */
9
19
  alpha2: string | null;
10
20
  }
11
- /** What a shared view controller tells the globe: its turn, tip, zoom and pan. */
21
+ /**
22
+ * What a shared view controller tells the globe: its turn (`yaw`, degrees), tip (`tilt`, degrees), zoom (1 is Fit) and pan (`panX` and `panY`, CSS pixels).
23
+ *
24
+ * @example
25
+ * ```ts
26
+ * import { HOME_VIEW, type GlobeView } from "@johnmorrisdotca/chikyu-earth";
27
+ *
28
+ * const turned: GlobeView = { ...HOME_VIEW, yaw: 30 };
29
+ * console.log(turned);
30
+ * ```
31
+ */
12
32
  export interface GlobeView {
13
33
  yaw: number;
14
34
  tilt: number;
@@ -16,14 +36,44 @@ export interface GlobeView {
16
36
  panX: number;
17
37
  panY: number;
18
38
  }
19
- /** A point on the globe, in degrees. */
39
+ /**
40
+ * A point on the globe, in degrees: `lon` from -180 to 180 (east is positive), `lat` from -90 to 90 (north is positive).
41
+ *
42
+ * @example
43
+ * ```ts
44
+ * import type { GeoPoint } from "@johnmorrisdotca/chikyu-earth";
45
+ *
46
+ * const tokyo: GeoPoint = { lon: 139.7, lat: 35.7 };
47
+ * console.log(tokyo);
48
+ * ```
49
+ */
20
50
  export interface GeoPoint {
21
51
  lon: number;
22
52
  lat: number;
23
53
  }
24
- /** How the globe is turned: [lambda, phi] in degrees, the pair d3-geo's `rotate` takes. */
54
+ /**
55
+ * How the globe is turned: [lambda, phi] in degrees, the pair d3-geo's `rotate` takes.
56
+ *
57
+ * @example
58
+ * ```ts
59
+ * import { globe, type Rotation } from "@johnmorrisdotca/chikyu-earth";
60
+ *
61
+ * const rotation: Rotation = [-10, -30];
62
+ * console.log(globe({ rotation, size: 100 }).length > 1000);
63
+ * ```
64
+ */
25
65
  export type Rotation = [lambda: number, phi: number];
26
- /** What `globe()` may be asked. */
66
+ /**
67
+ * What `globe()` may be asked.
68
+ *
69
+ * @example
70
+ * ```ts
71
+ * import { globe, type GlobeOptions } from "@johnmorrisdotca/chikyu-earth";
72
+ *
73
+ * const options: GlobeOptions = { size: 200, style: "thick", graticule: true, centre: { lon: 10, lat: 30 } };
74
+ * console.log(globe(options).startsWith("<svg"));
75
+ * ```
76
+ */
27
77
  export interface GlobeOptions {
28
78
  /** Width and height of the square SVG, in pixels. Default 320. */
29
79
  size?: number;
package/dist/view.d.ts CHANGED
@@ -1,12 +1,73 @@
1
1
  import type { GeoPoint, GlobeView, Rotation } from "./types.ts";
2
- /** The view when it opens: the home centre, upright, at 1x with no pan. */
2
+ /**
3
+ * The view when it opens: the home centre, upright, at 1x with no pan.
4
+ *
5
+ * @example
6
+ * ```ts
7
+ * import { HOME_VIEW } from "@johnmorrisdotca/chikyu-earth";
8
+ *
9
+ * console.log(HOME_VIEW);
10
+ * ```
11
+ */
3
12
  export declare const HOME_VIEW: GlobeView;
13
+ /**
14
+ * What d3-geo's orthographic projection is given for a view: its rotation, scale and translation.
15
+ *
16
+ * @example
17
+ * ```ts
18
+ * import { projectionView, type ProjectionView } from "@johnmorrisdotca/chikyu-earth";
19
+ *
20
+ * const view: ProjectionView = projectionView({ lon: 0, lat: 0 }, 400);
21
+ * console.log(view.scale, view.translate);
22
+ * ```
23
+ */
4
24
  export interface ProjectionView {
5
25
  rotation: Rotation;
6
26
  scale: number;
7
27
  translate: [number, number];
8
28
  }
9
- /** The radius of an unzoomed globe in a square of `size` pixels. */
29
+ /**
30
+ * The radius of an unzoomed globe in a square of `size` pixels.
31
+ *
32
+ * @param size - The width and height of the square, in pixels.
33
+ * @returns The radius in pixels, one less than half the size so the rim is not cut off.
34
+ *
35
+ * @example
36
+ * ```ts
37
+ * import { globeBaseScale } from "@johnmorrisdotca/chikyu-earth";
38
+ *
39
+ * console.log(globeBaseScale(420));
40
+ * ```
41
+ */
10
42
  export declare const globeBaseScale: (size: number) => number;
11
- /** yaw turns longitude, tilt tips latitude, zoom scales and pan moves the disc in CSS pixels. */
43
+ /**
44
+ * Where the view is centred on the globe: the home centre, turned by yaw (west) and tipped by tilt (north), held to the poles.
45
+ *
46
+ * @param centre - The home centre.
47
+ * @param view - The turn, tip, zoom and pan from the view controls.
48
+ * @returns The place now in the middle of the disc.
49
+ *
50
+ * @example
51
+ * ```ts
52
+ * import { lookAt } from "@johnmorrisdotca/chikyu-earth/realistic";
53
+ *
54
+ * console.log(lookAt({ lon: 10, lat: 80 }, { yaw: 25, tilt: 30, zoom: 1, panX: 0, panY: 0 }));
55
+ * ```
56
+ */
57
+ export declare const lookAt: (centre: GeoPoint, view: GlobeView) => GeoPoint;
58
+ /**
59
+ * What the projection is given for a view: yaw turns longitude, tilt tips latitude, zoom scales and pan moves the disc in CSS pixels.
60
+ *
61
+ * @param centre - The home centre.
62
+ * @param size - The width and height of the square the globe is drawn in, in pixels.
63
+ * @param view - The turn, tip, zoom and pan; the home view when left out.
64
+ * @returns The rotation, scale and translation for d3-geo's orthographic projection.
65
+ *
66
+ * @example
67
+ * ```ts
68
+ * import { projectionView } from "@johnmorrisdotca/chikyu-earth";
69
+ *
70
+ * console.log(projectionView({ lon: 139.7, lat: 35.7 }, 420, { yaw: 10, tilt: 5, zoom: 2, panX: 0, panY: 0 }));
71
+ * ```
72
+ */
12
73
  export declare function projectionView(centre: GeoPoint, size: number, view?: GlobeView): ProjectionView;