@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,44 @@
1
+ import type { GeoPoint } from "../types.ts";
2
+ /**
3
+ * Julian day number of a moment in milliseconds since the Unix epoch.
4
+ *
5
+ * @param ms - Milliseconds since 1970-01-01T00:00Z.
6
+ * @returns The Julian day number, with its fraction.
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * import { julianDay } from "@johnmorrisdotca/chikyu-earth/realistic";
11
+ *
12
+ * console.log(julianDay(Date.UTC(2000, 0, 1, 12)));
13
+ * ```
14
+ */
15
+ export declare const julianDay: (ms: number) => number;
16
+ /**
17
+ * The point where the Sun is at the zenith at `ms`: latitude is the Sun's declination, longitude runs west at 15 degrees an hour.
18
+ *
19
+ * @param ms - A moment, in milliseconds since the Unix epoch.
20
+ * @returns The longitude and latitude on the Earth that has the Sun straight overhead.
21
+ *
22
+ * @example
23
+ * ```ts
24
+ * import { subsolarPoint } from "@johnmorrisdotca/chikyu-earth/realistic";
25
+ *
26
+ * const sun = subsolarPoint(Date.UTC(2026, 5, 21, 12));
27
+ * console.log(sun.lat.toFixed(1), sun.lon.toFixed(1));
28
+ * ```
29
+ */
30
+ export declare function subsolarPoint(ms: number): GeoPoint;
31
+ /**
32
+ * A point on the globe as a unit vector: +z is longitude 0 on the equator, +x east, +y north.
33
+ *
34
+ * @param point - A longitude and latitude in degrees.
35
+ * @returns The `[x, y, z]` of the point on a sphere of radius 1.
36
+ *
37
+ * @example
38
+ * ```ts
39
+ * import { unitVector } from "@johnmorrisdotca/chikyu-earth/realistic";
40
+ *
41
+ * console.log(unitVector({ lon: 90, lat: 0 }).map((n) => Math.round(n)));
42
+ * ```
43
+ */
44
+ export declare function unitVector(point: GeoPoint): [number, number, number];
@@ -0,0 +1,65 @@
1
+ const RAD = Math.PI / 180;
2
+ const wrap360 = (degrees) => ((degrees % 360) + 360) % 360;
3
+ /**
4
+ * Julian day number of a moment in milliseconds since the Unix epoch.
5
+ *
6
+ * @param ms - Milliseconds since 1970-01-01T00:00Z.
7
+ * @returns The Julian day number, with its fraction.
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * import { julianDay } from "@johnmorrisdotca/chikyu-earth/realistic";
12
+ *
13
+ * console.log(julianDay(Date.UTC(2000, 0, 1, 12)));
14
+ * ```
15
+ */
16
+ export const julianDay = (ms) => ms / 86_400_000 + 2_440_587.5;
17
+ /**
18
+ * The point where the Sun is at the zenith at `ms`: latitude is the Sun's declination, longitude runs west at 15 degrees an hour.
19
+ *
20
+ * @param ms - A moment, in milliseconds since the Unix epoch.
21
+ * @returns The longitude and latitude on the Earth that has the Sun straight overhead.
22
+ *
23
+ * @example
24
+ * ```ts
25
+ * import { subsolarPoint } from "@johnmorrisdotca/chikyu-earth/realistic";
26
+ *
27
+ * const sun = subsolarPoint(Date.UTC(2026, 5, 21, 12));
28
+ * console.log(sun.lat.toFixed(1), sun.lon.toFixed(1));
29
+ * ```
30
+ */
31
+ export function subsolarPoint(ms) {
32
+ const jd = julianDay(ms);
33
+ const t = (jd - 2_451_545) / 36_525;
34
+ const meanLongitude = wrap360(280.46646 + t * (36_000.76983 + t * 0.0003032));
35
+ const meanAnomaly = wrap360(357.52911 + t * (35_999.05029 - t * 0.0001537)) * RAD;
36
+ const centre = (1.914602 - t * (0.004817 + 0.000014 * t)) * Math.sin(meanAnomaly) +
37
+ (0.019993 - 0.000101 * t) * Math.sin(2 * meanAnomaly) +
38
+ 0.000289 * Math.sin(3 * meanAnomaly);
39
+ const node = (125.04 - 1934.136 * t) * RAD;
40
+ const apparentLongitude = (meanLongitude + centre - 0.00569 - 0.00478 * Math.sin(node)) * RAD;
41
+ const obliquity = (23 + (26 + (21.448 - t * (46.815 + t * (0.00059 - t * 0.001813))) / 60) / 60 + 0.00256 * Math.cos(node)) * RAD;
42
+ const declination = Math.asin(Math.sin(obliquity) * Math.sin(apparentLongitude));
43
+ const rightAscension = Math.atan2(Math.cos(obliquity) * Math.sin(apparentLongitude), Math.cos(apparentLongitude)) / RAD;
44
+ const siderealGreenwich = wrap360(280.46061837 + 360.98564736629 * (jd - 2_451_545) + 0.000387933 * t * t);
45
+ const lon = ((wrap360(rightAscension - siderealGreenwich) + 180) % 360) - 180;
46
+ return { lon, lat: declination / RAD };
47
+ }
48
+ /**
49
+ * A point on the globe as a unit vector: +z is longitude 0 on the equator, +x east, +y north.
50
+ *
51
+ * @param point - A longitude and latitude in degrees.
52
+ * @returns The `[x, y, z]` of the point on a sphere of radius 1.
53
+ *
54
+ * @example
55
+ * ```ts
56
+ * import { unitVector } from "@johnmorrisdotca/chikyu-earth/realistic";
57
+ *
58
+ * console.log(unitVector({ lon: 90, lat: 0 }).map((n) => Math.round(n)));
59
+ * ```
60
+ */
61
+ export function unitVector(point) {
62
+ const lon = point.lon * RAD;
63
+ const lat = point.lat * RAD;
64
+ return [Math.cos(lat) * Math.sin(lon), Math.sin(lat), Math.cos(lat) * Math.cos(lon)];
65
+ }
@@ -0,0 +1,40 @@
1
+ import type * as THREE from "three";
2
+ import type { AssetCache } from "@johnmorrisdotca/kazoku-ui/renderer";
3
+ import { type Layer } from "./tiers.ts";
4
+ /**
5
+ * The parts of three.js the Earth and the space view use. A page can hand over a module that re-exports just these (see `setThreeLoader`) instead of all of three.
6
+ *
7
+ * @example
8
+ * ```ts no-run
9
+ * import type { ThreeParts } from "@johnmorrisdotca/chikyu-earth/realistic";
10
+ *
11
+ * const load = (): Promise<ThreeParts> => import("three");
12
+ * console.log(typeof load);
13
+ * ```
14
+ */
15
+ export type ThreeParts = {
16
+ [Name in "Texture" | "BufferGeometry" | "BufferAttribute" | "ShaderMaterial" | "Group" | "Mesh" | "Scene" | "OrthographicCamera" | "PerspectiveCamera" | "Vector3" | "WebGLRenderer" | "RepeatWrapping" | "ClampToEdgeWrapping" | "LinearMipmapLinearFilter" | "LinearFilter" | "BackSide" | "DoubleSide" | "LineLoop" | "LineBasicMaterial" | "Color"]: (typeof THREE)[Name];
17
+ };
18
+ /** Milliseconds a lower texture size must be enough before the larger one is let go. */
19
+ export declare const DOWNGRADE_AFTER_MS = 4000;
20
+ export interface TextureDeps {
21
+ assets: AssetCache;
22
+ /** Ends with a slash. */
23
+ textureBase: () => string;
24
+ /** The widest texture, 4096 at most. */
25
+ cap: () => number;
26
+ invalidate: () => void;
27
+ onError?: (error: unknown) => void;
28
+ onTier?: (layer: Layer, width: number) => void;
29
+ }
30
+ export interface TextureSet {
31
+ /** The `{ value }` a shader uniform takes, one a layer; the value is the texture now in use. */
32
+ readonly uniforms: Record<string, {
33
+ value: THREE.Texture | null;
34
+ }>;
35
+ /** Hold the width each layer needs for a disc of this many device pixels across the radius (`radiusPx[layer]`, or one number for all). */
36
+ sync(radiusPx: number | Partial<Record<Layer, number>>): void;
37
+ dispose(): void;
38
+ }
39
+ /** Load the base picture of each layer, then hand back the set. Rejects (after freeing what it took) if one will not load. */
40
+ export declare function createTextureSet(three: ThreeParts, layers: readonly Layer[], deps: TextureDeps): Promise<TextureSet>;
@@ -0,0 +1,141 @@
1
+ import { chooseTier, TIERS } from "./tiers.js";
2
+ /** Milliseconds a lower texture size must be enough before the larger one is let go. */
3
+ export const DOWNGRADE_AFTER_MS = 4000;
4
+ /** Load the base picture of each layer, then hand back the set. Rejects (after freeing what it took) if one will not load. */
5
+ export async function createTextureSet(three, layers, deps) {
6
+ let disposed = false;
7
+ const held = [];
8
+ const keyOf = (layer, texels) => `chikyu-earth:${layer}:${texels}`;
9
+ const take = (layer, texels) => {
10
+ const key = keyOf(layer, texels);
11
+ const entry = deps.assets.acquire(key, () => {
12
+ const texture = new three.Texture();
13
+ texture.wrapS = three.RepeatWrapping;
14
+ texture.wrapT = three.ClampToEdgeWrapping;
15
+ texture.minFilter = three.LinearMipmapLinearFilter;
16
+ texture.magFilter = three.LinearFilter;
17
+ texture.anisotropy = 8;
18
+ const controller = new AbortController();
19
+ const made = { width: texels, texture, closed: false, ready: Promise.resolve(), release: () => undefined };
20
+ made.ready = fetch(`${deps.textureBase()}${layer}-${texels}.webp`, { signal: controller.signal })
21
+ .then((response) => {
22
+ if (!response.ok)
23
+ throw new Error(`${layer}-${texels}.webp: ${response.status}`);
24
+ return response.blob();
25
+ })
26
+ .then((blob) => createImageBitmap(blob, { premultiplyAlpha: "none", colorSpaceConversion: "none" }))
27
+ .then((bitmap) => {
28
+ if (made.closed)
29
+ bitmap.close();
30
+ else {
31
+ texture.image = bitmap;
32
+ texture.needsUpdate = true;
33
+ }
34
+ });
35
+ made.ready.catch(() => undefined);
36
+ made.release = () => {
37
+ made.closed = true;
38
+ controller.abort();
39
+ texture.dispose();
40
+ texture.image?.close?.();
41
+ };
42
+ return made;
43
+ }, (value) => value.release());
44
+ held.push(key);
45
+ return entry;
46
+ };
47
+ const letGo = (layer, texels) => {
48
+ const key = keyOf(layer, texels);
49
+ const at = held.indexOf(key);
50
+ if (at >= 0)
51
+ held.splice(at, 1);
52
+ deps.assets.release(key);
53
+ };
54
+ const slots = new Map();
55
+ for (const layer of layers) {
56
+ const baseWidth = TIERS[layer][0];
57
+ const base = take(layer, baseWidth);
58
+ slots.set(layer, { layer, uniform: { value: null }, base, baseWidth, entry: base, width: baseWidth, loading: undefined, timer: undefined });
59
+ }
60
+ try {
61
+ await Promise.all([...slots.values()].map((slot) => slot.base.ready));
62
+ }
63
+ catch (error) {
64
+ for (const slot of slots.values())
65
+ letGo(slot.layer, slot.baseWidth);
66
+ deps.onError?.(error);
67
+ throw error;
68
+ }
69
+ for (const slot of slots.values())
70
+ slot.uniform.value = slot.base.texture;
71
+ const swap = (slot, entry, texels) => {
72
+ const previous = slot.entry;
73
+ slot.entry = entry;
74
+ slot.width = texels;
75
+ slot.uniform.value = entry.texture;
76
+ if (previous !== slot.base)
77
+ letGo(slot.layer, previous.width);
78
+ deps.onTier?.(slot.layer, texels);
79
+ };
80
+ const moveTo = (slot, texels) => {
81
+ if (disposed || texels === slot.width || slot.loading === texels)
82
+ return;
83
+ if (texels === slot.baseWidth) {
84
+ swap(slot, slot.base, texels);
85
+ deps.invalidate();
86
+ return;
87
+ }
88
+ slot.loading = texels;
89
+ const entry = take(slot.layer, texels);
90
+ entry.ready.then(() => {
91
+ if (disposed || slot.loading !== texels) {
92
+ letGo(slot.layer, texels);
93
+ return;
94
+ }
95
+ slot.loading = undefined;
96
+ swap(slot, entry, texels);
97
+ deps.invalidate();
98
+ }, (error) => {
99
+ letGo(slot.layer, texels);
100
+ if (slot.loading === texels)
101
+ slot.loading = undefined;
102
+ deps.onError?.(error);
103
+ });
104
+ };
105
+ let lastRadius = 0;
106
+ const wantedFor = (slot, radius) => {
107
+ const px = typeof radius === "number" ? radius : (radius[slot.layer] ?? 0);
108
+ return chooseTier(slot.layer, px, deps.cap());
109
+ };
110
+ return {
111
+ uniforms: Object.fromEntries([...slots].map(([layer, slot]) => [layer, slot.uniform])),
112
+ sync(radiusPx) {
113
+ lastRadius = radiusPx;
114
+ for (const slot of slots.values()) {
115
+ const wanted = wantedFor(slot, radiusPx);
116
+ if (slot.timer !== undefined && wanted >= slot.width) {
117
+ clearTimeout(slot.timer);
118
+ slot.timer = undefined;
119
+ }
120
+ if (wanted > slot.width)
121
+ moveTo(slot, wanted);
122
+ else if (wanted < slot.width && slot.timer === undefined) {
123
+ slot.timer = setTimeout(() => {
124
+ slot.timer = undefined;
125
+ const now = wantedFor(slot, lastRadius);
126
+ if (now < slot.width)
127
+ moveTo(slot, now);
128
+ }, DOWNGRADE_AFTER_MS);
129
+ }
130
+ }
131
+ },
132
+ dispose() {
133
+ disposed = true;
134
+ for (const slot of slots.values())
135
+ if (slot.timer !== undefined)
136
+ clearTimeout(slot.timer);
137
+ for (const key of held.splice(0))
138
+ deps.assets.release(key);
139
+ },
140
+ };
141
+ }
@@ -0,0 +1,94 @@
1
+ /**
2
+ * One of the pictures the realistic Earth and the space view draw with: `day`, `night`, `clouds`, `water` and `moon`.
3
+ *
4
+ * @example
5
+ * ```ts
6
+ * import { TIERS, type Layer } from "@johnmorrisdotca/chikyu-earth/realistic";
7
+ *
8
+ * const layer: Layer = "clouds";
9
+ * console.log(TIERS[layer]);
10
+ * ```
11
+ */
12
+ export type Layer = "day" | "night" | "clouds" | "water" | "moon";
13
+ /**
14
+ * The widths each layer is shipped in (every picture is 2:1, so the height is half).
15
+ *
16
+ * @example
17
+ * ```ts
18
+ * import { TIERS } from "@johnmorrisdotca/chikyu-earth/realistic";
19
+ *
20
+ * console.log(TIERS.day);
21
+ * ```
22
+ */
23
+ export declare const TIERS: Record<Layer, readonly number[]>;
24
+ /**
25
+ * The most pixels across a texture the package ever asks the GPU for.
26
+ *
27
+ * @example
28
+ * ```ts
29
+ * import { MAX_TEXTURE } from "@johnmorrisdotca/chikyu-earth/realistic";
30
+ *
31
+ * console.log(MAX_TEXTURE);
32
+ * ```
33
+ */
34
+ export declare const MAX_TEXTURE = 4096;
35
+ /**
36
+ * Texels across the whole circumference wanted per device pixel of the globe's radius: 3 is a little under 2 pi, so a texel is a little larger than a pixel.
37
+ *
38
+ * @example
39
+ * ```ts
40
+ * import { TEXELS_PER_RADIUS_PIXEL } from "@johnmorrisdotca/chikyu-earth/realistic";
41
+ *
42
+ * console.log(TEXELS_PER_RADIUS_PIXEL);
43
+ * ```
44
+ */
45
+ export declare const TEXELS_PER_RADIUS_PIXEL = 3;
46
+ /**
47
+ * The width to hold for a layer when the globe's disc has radius `radiusPx` device pixels: the smallest shipped width that gives
48
+ * `TEXELS_PER_RADIUS_PIXEL` texels per pixel, else the largest, never more than `cap`, never less than the smallest.
49
+ *
50
+ * @param layer - Which picture.
51
+ * @param radiusPx - The disc's radius in device pixels.
52
+ * @param cap - The most pixels across to ask for; {@link MAX_TEXTURE} by default.
53
+ * @returns One of the widths in {@link TIERS} for that layer.
54
+ *
55
+ * @example
56
+ * ```ts
57
+ * import { chooseTier } from "@johnmorrisdotca/chikyu-earth/realistic";
58
+ *
59
+ * console.log(chooseTier("day", 200), chooseTier("day", 900), chooseTier("day", 900, 2048));
60
+ * ```
61
+ */
62
+ export declare function chooseTier(layer: Layer, radiusPx: number, cap?: number): number;
63
+ /**
64
+ * The cap for this device: low-memory phones stop at 2048, and the GPU's own limit and the option are never passed.
65
+ *
66
+ * @param options - `max` (the page's own limit), `gpuMax` (the GPU's `MAX_TEXTURE_SIZE`) and `deviceMemory` (gigabytes, from `navigator.deviceMemory`); each may be left out.
67
+ * @returns The most pixels across any picture may have on this device.
68
+ *
69
+ * @example
70
+ * ```ts
71
+ * import { textureCap } from "@johnmorrisdotca/chikyu-earth/realistic";
72
+ *
73
+ * console.log(textureCap(), textureCap({ deviceMemory: 2 }), textureCap({ gpuMax: 1024 }));
74
+ * ```
75
+ */
76
+ export declare function textureCap(options?: {
77
+ max?: number;
78
+ gpuMax?: number;
79
+ deviceMemory?: number;
80
+ }): number;
81
+ /**
82
+ * Bytes of GPU memory a texture of this width costs, with its mipmaps, at 4 bytes a pixel (the most it can be).
83
+ *
84
+ * @param width - Pixels across; the picture is 2:1.
85
+ * @returns The bytes the GPU holds for it.
86
+ *
87
+ * @example
88
+ * ```ts
89
+ * import { textureBytes } from "@johnmorrisdotca/chikyu-earth/realistic";
90
+ *
91
+ * console.log(textureBytes(4096));
92
+ * ```
93
+ */
94
+ export declare const textureBytes: (width: number) => number;
@@ -0,0 +1,94 @@
1
+ // Which size of each picture to hold on the GPU. Small first, sharper on zoom, and never past a cap. Pure.
2
+ /**
3
+ * The widths each layer is shipped in (every picture is 2:1, so the height is half).
4
+ *
5
+ * @example
6
+ * ```ts
7
+ * import { TIERS } from "@johnmorrisdotca/chikyu-earth/realistic";
8
+ *
9
+ * console.log(TIERS.day);
10
+ * ```
11
+ */
12
+ export const TIERS = {
13
+ day: [1024, 2048, 4096],
14
+ night: [1024, 2048],
15
+ clouds: [1024, 2048],
16
+ water: [1024, 2048],
17
+ moon: [1024, 2048],
18
+ };
19
+ /**
20
+ * The most pixels across a texture the package ever asks the GPU for.
21
+ *
22
+ * @example
23
+ * ```ts
24
+ * import { MAX_TEXTURE } from "@johnmorrisdotca/chikyu-earth/realistic";
25
+ *
26
+ * console.log(MAX_TEXTURE);
27
+ * ```
28
+ */
29
+ export const MAX_TEXTURE = 4096;
30
+ /**
31
+ * Texels across the whole circumference wanted per device pixel of the globe's radius: 3 is a little under 2 pi, so a texel is a little larger than a pixel.
32
+ *
33
+ * @example
34
+ * ```ts
35
+ * import { TEXELS_PER_RADIUS_PIXEL } from "@johnmorrisdotca/chikyu-earth/realistic";
36
+ *
37
+ * console.log(TEXELS_PER_RADIUS_PIXEL);
38
+ * ```
39
+ */
40
+ export const TEXELS_PER_RADIUS_PIXEL = 3;
41
+ /**
42
+ * The width to hold for a layer when the globe's disc has radius `radiusPx` device pixels: the smallest shipped width that gives
43
+ * `TEXELS_PER_RADIUS_PIXEL` texels per pixel, else the largest, never more than `cap`, never less than the smallest.
44
+ *
45
+ * @param layer - Which picture.
46
+ * @param radiusPx - The disc's radius in device pixels.
47
+ * @param cap - The most pixels across to ask for; {@link MAX_TEXTURE} by default.
48
+ * @returns One of the widths in {@link TIERS} for that layer.
49
+ *
50
+ * @example
51
+ * ```ts
52
+ * import { chooseTier } from "@johnmorrisdotca/chikyu-earth/realistic";
53
+ *
54
+ * console.log(chooseTier("day", 200), chooseTier("day", 900), chooseTier("day", 900, 2048));
55
+ * ```
56
+ */
57
+ export function chooseTier(layer, radiusPx, cap = MAX_TEXTURE) {
58
+ const widths = TIERS[layer].filter((width) => width <= Math.max(cap, TIERS[layer][0]));
59
+ const wanted = radiusPx * TEXELS_PER_RADIUS_PIXEL;
60
+ return widths.find((width) => width >= wanted) ?? widths[widths.length - 1];
61
+ }
62
+ /**
63
+ * The cap for this device: low-memory phones stop at 2048, and the GPU's own limit and the option are never passed.
64
+ *
65
+ * @param options - `max` (the page's own limit), `gpuMax` (the GPU's `MAX_TEXTURE_SIZE`) and `deviceMemory` (gigabytes, from `navigator.deviceMemory`); each may be left out.
66
+ * @returns The most pixels across any picture may have on this device.
67
+ *
68
+ * @example
69
+ * ```ts
70
+ * import { textureCap } from "@johnmorrisdotca/chikyu-earth/realistic";
71
+ *
72
+ * console.log(textureCap(), textureCap({ deviceMemory: 2 }), textureCap({ gpuMax: 1024 }));
73
+ * ```
74
+ */
75
+ export function textureCap(options = {}) {
76
+ let cap = Math.min(MAX_TEXTURE, options.max ?? MAX_TEXTURE, options.gpuMax ?? MAX_TEXTURE);
77
+ if (options.deviceMemory !== undefined && options.deviceMemory <= 2)
78
+ cap = Math.min(cap, 2048);
79
+ return cap;
80
+ }
81
+ /**
82
+ * Bytes of GPU memory a texture of this width costs, with its mipmaps, at 4 bytes a pixel (the most it can be).
83
+ *
84
+ * @param width - Pixels across; the picture is 2:1.
85
+ * @returns The bytes the GPU holds for it.
86
+ *
87
+ * @example
88
+ * ```ts
89
+ * import { textureBytes } from "@johnmorrisdotca/chikyu-earth/realistic";
90
+ *
91
+ * console.log(textureBytes(4096));
92
+ * ```
93
+ */
94
+ export const textureBytes = (width) => Math.round(width * (width / 2) * 4 * (4 / 3));
package/dist/rotate.d.ts CHANGED
@@ -1,14 +1,69 @@
1
1
  import type { GeoPoint, Rotation } from "./types.ts";
2
- /** One sidereal day — the Earth's turn against the stars, 23 h 56 min 4.0905 s — in milliseconds. */
2
+ /**
3
+ * One sidereal day — the Earth's turn against the stars, 23 h 56 min 4.0905 s — in milliseconds.
4
+ *
5
+ * @example
6
+ * ```ts
7
+ * import { SIDEREAL_DAY_MS } from "@johnmorrisdotca/chikyu-earth";
8
+ *
9
+ * console.log(SIDEREAL_DAY_MS / 3_600_000, "hours");
10
+ * ```
11
+ */
3
12
  export declare const SIDEREAL_DAY_MS = 86164090.5;
4
- /** How far the Earth turns in one millisecond, in degrees. */
13
+ /**
14
+ * How far the Earth turns in one millisecond, in degrees.
15
+ *
16
+ * @example
17
+ * ```ts
18
+ * import { DEGREES_PER_MS } from "@johnmorrisdotca/chikyu-earth";
19
+ *
20
+ * console.log(DEGREES_PER_MS * 1000, "degrees a second");
21
+ * ```
22
+ */
5
23
  export declare const DEGREES_PER_MS: number;
6
- /** How far the Earth has turned after `ms` milliseconds, in degrees. */
24
+ /**
25
+ * How far the Earth has turned after `ms` milliseconds, in degrees.
26
+ *
27
+ * @param ms - A time span in milliseconds.
28
+ * @returns The turn in degrees; a whole {@link SIDEREAL_DAY_MS} is 360.
29
+ *
30
+ * @example
31
+ * ```ts
32
+ * import { SIDEREAL_DAY_MS, spinAt } from "@johnmorrisdotca/chikyu-earth";
33
+ *
34
+ * console.log(spinAt(SIDEREAL_DAY_MS / 4));
35
+ * ```
36
+ */
7
37
  export declare const spinAt: (ms: number) => number;
8
- /** The rotation that puts `centre` in the middle of the disc: d3-geo's rotate is the negative of the point. */
38
+ /**
39
+ * The rotation that puts `centre` in the middle of the disc: d3-geo's rotate is the negative of the point.
40
+ *
41
+ * @param centre - The place to centre on.
42
+ * @returns The `[lambda, phi]` pair for `globe()`'s `rotation` option.
43
+ *
44
+ * @example
45
+ * ```ts
46
+ * import { centreRotation } from "@johnmorrisdotca/chikyu-earth";
47
+ *
48
+ * console.log(centreRotation({ lon: 139.7, lat: 35.7 }));
49
+ * ```
50
+ */
9
51
  export declare const centreRotation: (centre: GeoPoint) => Rotation;
10
52
  /**
11
53
  * The rotation at `now` for a globe that was centred on `centre` at `start`. The tilt stays; the spin advances at the
12
54
  * Earth's own rate, so the disc turns as the real Earth does and the centred place drifts with it.
55
+ *
56
+ * @param centre - The place the globe was centred on at `start`.
57
+ * @param now - The time to draw for, in milliseconds.
58
+ * @param start - The time the globe was centred, in the same milliseconds as `now`.
59
+ * @returns The `[lambda, phi]` rotation at `now`.
60
+ *
61
+ * @example
62
+ * ```ts
63
+ * import { rotationAt, SIDEREAL_DAY_MS } from "@johnmorrisdotca/chikyu-earth";
64
+ *
65
+ * const tokyo = { lon: 139.7, lat: 35.7 };
66
+ * console.log(rotationAt(tokyo, SIDEREAL_DAY_MS / 2, 0));
67
+ * ```
13
68
  */
14
69
  export declare function rotationAt(centre: GeoPoint, now: number, start: number): Rotation;
package/dist/rotate.js CHANGED
@@ -1,14 +1,69 @@
1
- /** One sidereal day — the Earth's turn against the stars, 23 h 56 min 4.0905 s — in milliseconds. */
1
+ /**
2
+ * One sidereal day — the Earth's turn against the stars, 23 h 56 min 4.0905 s — in milliseconds.
3
+ *
4
+ * @example
5
+ * ```ts
6
+ * import { SIDEREAL_DAY_MS } from "@johnmorrisdotca/chikyu-earth";
7
+ *
8
+ * console.log(SIDEREAL_DAY_MS / 3_600_000, "hours");
9
+ * ```
10
+ */
2
11
  export const SIDEREAL_DAY_MS = 86_164_090.5;
3
- /** How far the Earth turns in one millisecond, in degrees. */
12
+ /**
13
+ * How far the Earth turns in one millisecond, in degrees.
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * import { DEGREES_PER_MS } from "@johnmorrisdotca/chikyu-earth";
18
+ *
19
+ * console.log(DEGREES_PER_MS * 1000, "degrees a second");
20
+ * ```
21
+ */
4
22
  export const DEGREES_PER_MS = 360 / SIDEREAL_DAY_MS;
5
- /** How far the Earth has turned after `ms` milliseconds, in degrees. */
23
+ /**
24
+ * How far the Earth has turned after `ms` milliseconds, in degrees.
25
+ *
26
+ * @param ms - A time span in milliseconds.
27
+ * @returns The turn in degrees; a whole {@link SIDEREAL_DAY_MS} is 360.
28
+ *
29
+ * @example
30
+ * ```ts
31
+ * import { SIDEREAL_DAY_MS, spinAt } from "@johnmorrisdotca/chikyu-earth";
32
+ *
33
+ * console.log(spinAt(SIDEREAL_DAY_MS / 4));
34
+ * ```
35
+ */
6
36
  export const spinAt = (ms) => ms * DEGREES_PER_MS;
7
- /** The rotation that puts `centre` in the middle of the disc: d3-geo's rotate is the negative of the point. */
37
+ /**
38
+ * The rotation that puts `centre` in the middle of the disc: d3-geo's rotate is the negative of the point.
39
+ *
40
+ * @param centre - The place to centre on.
41
+ * @returns The `[lambda, phi]` pair for `globe()`'s `rotation` option.
42
+ *
43
+ * @example
44
+ * ```ts
45
+ * import { centreRotation } from "@johnmorrisdotca/chikyu-earth";
46
+ *
47
+ * console.log(centreRotation({ lon: 139.7, lat: 35.7 }));
48
+ * ```
49
+ */
8
50
  export const centreRotation = (centre) => [-centre.lon, -centre.lat];
9
51
  /**
10
52
  * The rotation at `now` for a globe that was centred on `centre` at `start`. The tilt stays; the spin advances at the
11
53
  * Earth's own rate, so the disc turns as the real Earth does and the centred place drifts with it.
54
+ *
55
+ * @param centre - The place the globe was centred on at `start`.
56
+ * @param now - The time to draw for, in milliseconds.
57
+ * @param start - The time the globe was centred, in the same milliseconds as `now`.
58
+ * @returns The `[lambda, phi]` rotation at `now`.
59
+ *
60
+ * @example
61
+ * ```ts
62
+ * import { rotationAt, SIDEREAL_DAY_MS } from "@johnmorrisdotca/chikyu-earth";
63
+ *
64
+ * const tokyo = { lon: 139.7, lat: 35.7 };
65
+ * console.log(rotationAt(tokyo, SIDEREAL_DAY_MS / 2, 0));
66
+ * ```
12
67
  */
13
68
  export function rotationAt(centre, now, start) {
14
69
  const [lambda, phi] = centreRotation(centre);