@johnmorrisdotca/chikyu-earth 0.4.0 → 0.5.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 (43) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/NOTICE.md +28 -1
  3. package/README.md +30 -0
  4. package/dist/element.d.ts +6 -1
  5. package/dist/element.js +55 -3
  6. package/dist/markers/cluster.d.ts +11 -0
  7. package/dist/markers/cluster.js +26 -0
  8. package/dist/markers/index.d.ts +5 -0
  9. package/dist/markers/index.js +6 -0
  10. package/dist/markers/labels.d.ts +24 -0
  11. package/dist/markers/labels.js +52 -0
  12. package/dist/markers/layer.d.ts +49 -0
  13. package/dist/markers/layer.js +434 -0
  14. package/dist/markers/project.d.ts +21 -0
  15. package/dist/markers/project.js +43 -0
  16. package/dist/markers/types.d.ts +38 -0
  17. package/dist/markers/types.js +1 -0
  18. package/dist/realistic/earth.d.ts +56 -0
  19. package/dist/realistic/earth.js +256 -0
  20. package/dist/realistic/element.d.ts +53 -0
  21. package/dist/realistic/element.js +280 -0
  22. package/dist/realistic/gate.d.ts +20 -0
  23. package/dist/realistic/gate.js +40 -0
  24. package/dist/realistic/index.d.ts +5 -0
  25. package/dist/realistic/index.js +6 -0
  26. package/dist/realistic/shaders.d.ts +5 -0
  27. package/dist/realistic/shaders.js +79 -0
  28. package/dist/realistic/sun.d.ts +7 -0
  29. package/dist/realistic/sun.js +28 -0
  30. package/dist/realistic/tiers.d.ts +20 -0
  31. package/dist/realistic/tiers.js +30 -0
  32. package/dist/textures/clouds-1024.webp +0 -0
  33. package/dist/textures/clouds-2048.webp +0 -0
  34. package/dist/textures/day-1024.webp +0 -0
  35. package/dist/textures/day-2048.webp +0 -0
  36. package/dist/textures/day-4096.webp +0 -0
  37. package/dist/textures/night-1024.webp +0 -0
  38. package/dist/textures/night-2048.webp +0 -0
  39. package/dist/textures/water-1024.webp +0 -0
  40. package/dist/textures/water-2048.webp +0 -0
  41. package/dist/view.d.ts +2 -0
  42. package/dist/view.js +5 -0
  43. package/package.json +45 -7
@@ -0,0 +1,79 @@
1
+ // The three programs of the realistic Earth, as GLSL strings: the surface (day, night, sun glint, rim), the cloud layer and the
2
+ // atmosphere's glow. Colours are shaded in the textures' own (sRGB) space: a look, not a radiometric model.
3
+ export const SURFACE_VERT = /* glsl */ `
4
+ varying vec2 vUv;
5
+ varying vec3 vNormal;
6
+ void main() {
7
+ vUv = uv;
8
+ vNormal = normalize(mat3(modelMatrix) * position);
9
+ gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
10
+ }`;
11
+ export const SURFACE_FRAG = /* glsl */ `
12
+ uniform sampler2D dayMap;
13
+ uniform sampler2D nightMap;
14
+ uniform sampler2D waterMap;
15
+ uniform vec3 sunDir;
16
+ uniform float lights;
17
+ varying vec2 vUv;
18
+ varying vec3 vNormal;
19
+ void main() {
20
+ vec3 n = normalize(vNormal);
21
+ float ndl = dot(n, sunDir);
22
+ float day = smoothstep(-0.10, 0.22, ndl);
23
+ vec3 albedo = texture2D(dayMap, vUv).rgb;
24
+ float water = texture2D(waterMap, vUv).r;
25
+
26
+ vec3 lit = albedo * (0.07 + 1.0 * pow(max(ndl, 0.0), 0.5));
27
+ float dusk = smoothstep(-0.14, 0.0, ndl) * (1.0 - smoothstep(0.0, 0.30, ndl));
28
+ lit += vec3(0.55, 0.28, 0.14) * dusk * 0.30 * (0.35 + 0.65 * albedo.g);
29
+
30
+ vec3 h = normalize(sunDir + vec3(0.0, 0.0, 1.0));
31
+ float glint = pow(max(dot(n, h), 0.0), 220.0) * water * day;
32
+ lit += vec3(1.0, 0.93, 0.80) * glint * 0.7;
33
+
34
+ vec3 night = texture2D(nightMap, vUv).rgb;
35
+ night = night * night * vec3(1.6, 1.5, 1.3) * lights + night * 0.25;
36
+ vec3 col = mix(night, lit, day);
37
+
38
+ float rim = pow(1.0 - clamp(n.z, 0.0, 1.0), 3.0);
39
+ float sunSide = 0.12 + 0.88 * smoothstep(-0.25, 0.5, ndl);
40
+ col += vec3(0.20, 0.45, 1.0) * rim * sunSide * 0.55;
41
+ gl_FragColor = vec4(col, 1.0);
42
+ }`;
43
+ export const CLOUD_FRAG = /* glsl */ `
44
+ uniform sampler2D cloudMap;
45
+ uniform vec3 sunDir;
46
+ uniform float shift;
47
+ uniform float opacity;
48
+ varying vec2 vUv;
49
+ varying vec3 vNormal;
50
+ void main() {
51
+ vec3 n = normalize(vNormal);
52
+ float ndl = dot(n, sunDir);
53
+ float c = texture2D(cloudMap, vec2(vUv.x + shift, vUv.y)).r;
54
+ float a = smoothstep(0.28, 0.88, c) * opacity;
55
+ float day = smoothstep(-0.10, 0.25, ndl);
56
+ vec3 col = mix(vec3(0.015, 0.02, 0.04), vec3(1.0), day * (0.18 + 0.82 * pow(max(ndl, 0.0), 0.45)));
57
+ col += vec3(0.55, 0.28, 0.14) * smoothstep(-0.14, 0.0, ndl) * (1.0 - smoothstep(0.0, 0.25, ndl)) * 0.35;
58
+ gl_FragColor = vec4(col, a);
59
+ }`;
60
+ export const ATMOSPHERE_VERT = /* glsl */ `
61
+ varying vec3 vView;
62
+ void main() {
63
+ vec4 view = modelViewMatrix * vec4(position, 1.0);
64
+ vView = view.xyz;
65
+ gl_Position = projectionMatrix * view;
66
+ }`;
67
+ export const ATMOSPHERE_FRAG = /* glsl */ `
68
+ uniform vec3 sunDir;
69
+ uniform float outer;
70
+ varying vec3 vView;
71
+ void main() {
72
+ float rho = length(vView.xy);
73
+ float t = clamp((rho - 1.0) / (outer - 1.0), 0.0, 1.0);
74
+ vec2 dir = rho > 0.0 ? vView.xy / rho : vec2(1.0, 0.0);
75
+ float side = smoothstep(-0.40, 0.55, dot(vec3(dir, 0.0), sunDir));
76
+ float glow = pow(1.0 - t, 3.0) * (0.10 + 0.90 * side);
77
+ vec3 col = mix(vec3(0.10, 0.22, 0.60), vec3(0.42, 0.68, 1.0), side);
78
+ gl_FragColor = vec4(col, glow * 0.9);
79
+ }`;
@@ -0,0 +1,7 @@
1
+ import type { GeoPoint } from "../types.ts";
2
+ /** Julian day number of a moment in milliseconds since the Unix epoch. */
3
+ export declare const julianDay: (ms: number) => number;
4
+ /** 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. */
5
+ export declare function subsolarPoint(ms: number): GeoPoint;
6
+ /** A point on the globe as a unit vector: +z is longitude 0 on the equator, +x east, +y north. */
7
+ export declare function unitVector(point: GeoPoint): [number, number, number];
@@ -0,0 +1,28 @@
1
+ const RAD = Math.PI / 180;
2
+ const wrap360 = (degrees) => ((degrees % 360) + 360) % 360;
3
+ /** Julian day number of a moment in milliseconds since the Unix epoch. */
4
+ export const julianDay = (ms) => ms / 86_400_000 + 2_440_587.5;
5
+ /** 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. */
6
+ export function subsolarPoint(ms) {
7
+ const jd = julianDay(ms);
8
+ const t = (jd - 2_451_545) / 36_525;
9
+ const meanLongitude = wrap360(280.46646 + t * (36_000.76983 + t * 0.0003032));
10
+ const meanAnomaly = wrap360(357.52911 + t * (35_999.05029 - t * 0.0001537)) * RAD;
11
+ const centre = (1.914602 - t * (0.004817 + 0.000014 * t)) * Math.sin(meanAnomaly) +
12
+ (0.019993 - 0.000101 * t) * Math.sin(2 * meanAnomaly) +
13
+ 0.000289 * Math.sin(3 * meanAnomaly);
14
+ const node = (125.04 - 1934.136 * t) * RAD;
15
+ const apparentLongitude = (meanLongitude + centre - 0.00569 - 0.00478 * Math.sin(node)) * RAD;
16
+ const obliquity = (23 + (26 + (21.448 - t * (46.815 + t * (0.00059 - t * 0.001813))) / 60) / 60 + 0.00256 * Math.cos(node)) * RAD;
17
+ const declination = Math.asin(Math.sin(obliquity) * Math.sin(apparentLongitude));
18
+ const rightAscension = Math.atan2(Math.cos(obliquity) * Math.sin(apparentLongitude), Math.cos(apparentLongitude)) / RAD;
19
+ const siderealGreenwich = wrap360(280.46061837 + 360.98564736629 * (jd - 2_451_545) + 0.000387933 * t * t);
20
+ const lon = ((wrap360(rightAscension - siderealGreenwich) + 180) % 360) - 180;
21
+ return { lon, lat: declination / RAD };
22
+ }
23
+ /** A point on the globe as a unit vector: +z is longitude 0 on the equator, +x east, +y north. */
24
+ export function unitVector(point) {
25
+ const lon = point.lon * RAD;
26
+ const lat = point.lat * RAD;
27
+ return [Math.cos(lat) * Math.sin(lon), Math.sin(lat), Math.cos(lat) * Math.cos(lon)];
28
+ }
@@ -0,0 +1,20 @@
1
+ export type Layer = "day" | "night" | "clouds" | "water";
2
+ /** The widths each layer is shipped in (every picture is 2:1, so the height is half). */
3
+ export declare const TIERS: Record<Layer, readonly number[]>;
4
+ /** The most pixels across a texture the package ever asks the GPU for. */
5
+ export declare const MAX_TEXTURE = 4096;
6
+ /** 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. */
7
+ export declare const TEXELS_PER_RADIUS_PIXEL = 3;
8
+ /**
9
+ * The width to hold for a layer when the globe's disc has radius `radiusPx` device pixels: the smallest shipped width that gives
10
+ * `TEXELS_PER_RADIUS_PIXEL` texels per pixel, else the largest, never more than `cap`, never less than the smallest.
11
+ */
12
+ export declare function chooseTier(layer: Layer, radiusPx: number, cap?: number): number;
13
+ /** The cap for this device: low-memory phones stop at 2048, and the GPU's own limit and the option are never passed. */
14
+ export declare function textureCap(options?: {
15
+ max?: number;
16
+ gpuMax?: number;
17
+ deviceMemory?: number;
18
+ }): number;
19
+ /** Bytes of GPU memory a texture of this width costs, with its mipmaps, at 4 bytes a pixel (the most it can be). */
20
+ export declare const textureBytes: (width: number) => number;
@@ -0,0 +1,30 @@
1
+ // Which size of each picture to hold on the GPU. Small first, sharper on zoom, and never past a cap. Pure.
2
+ /** The widths each layer is shipped in (every picture is 2:1, so the height is half). */
3
+ export const TIERS = {
4
+ day: [1024, 2048, 4096],
5
+ night: [1024, 2048],
6
+ clouds: [1024, 2048],
7
+ water: [1024, 2048],
8
+ };
9
+ /** The most pixels across a texture the package ever asks the GPU for. */
10
+ export const MAX_TEXTURE = 4096;
11
+ /** 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. */
12
+ export const TEXELS_PER_RADIUS_PIXEL = 3;
13
+ /**
14
+ * The width to hold for a layer when the globe's disc has radius `radiusPx` device pixels: the smallest shipped width that gives
15
+ * `TEXELS_PER_RADIUS_PIXEL` texels per pixel, else the largest, never more than `cap`, never less than the smallest.
16
+ */
17
+ export function chooseTier(layer, radiusPx, cap = MAX_TEXTURE) {
18
+ const widths = TIERS[layer].filter((width) => width <= Math.max(cap, TIERS[layer][0]));
19
+ const wanted = radiusPx * TEXELS_PER_RADIUS_PIXEL;
20
+ return widths.find((width) => width >= wanted) ?? widths[widths.length - 1];
21
+ }
22
+ /** The cap for this device: low-memory phones stop at 2048, and the GPU's own limit and the option are never passed. */
23
+ export function textureCap(options = {}) {
24
+ let cap = Math.min(MAX_TEXTURE, options.max ?? MAX_TEXTURE, options.gpuMax ?? MAX_TEXTURE);
25
+ if (options.deviceMemory !== undefined && options.deviceMemory <= 2)
26
+ cap = Math.min(cap, 2048);
27
+ return cap;
28
+ }
29
+ /** Bytes of GPU memory a texture of this width costs, with its mipmaps, at 4 bytes a pixel (the most it can be). */
30
+ export const textureBytes = (width) => Math.round(width * (width / 2) * 4 * (4 / 3));
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
package/dist/view.d.ts CHANGED
@@ -8,5 +8,7 @@ export interface ProjectionView {
8
8
  }
9
9
  /** The radius of an unzoomed globe in a square of `size` pixels. */
10
10
  export declare const globeBaseScale: (size: number) => number;
11
+ /** Where the view is centred on the globe: the home centre, turned by yaw (west) and tipped by tilt (north), held to the poles. */
12
+ export declare const lookAt: (centre: GeoPoint, view: GlobeView) => GeoPoint;
11
13
  /** yaw turns longitude, tilt tips latitude, zoom scales and pan moves the disc in CSS pixels. */
12
14
  export declare function projectionView(centre: GeoPoint, size: number, view?: GlobeView): ProjectionView;
package/dist/view.js CHANGED
@@ -2,6 +2,11 @@
2
2
  export const HOME_VIEW = Object.freeze({ yaw: 0, tilt: 0, zoom: 1, panX: 0, panY: 0 });
3
3
  /** The radius of an unzoomed globe in a square of `size` pixels. */
4
4
  export const globeBaseScale = (size) => size / 2 - 1;
5
+ /** Where the view is centred on the globe: the home centre, turned by yaw (west) and tipped by tilt (north), held to the poles. */
6
+ export const lookAt = (centre, view) => ({
7
+ lon: centre.lon - view.yaw,
8
+ lat: Math.max(-90, Math.min(90, centre.lat + view.tilt)),
9
+ });
5
10
  /** yaw turns longitude, tilt tips latitude, zoom scales and pan moves the disc in CSS pixels. */
6
11
  export function projectionView(centre, size, view = HOME_VIEW) {
7
12
  return {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@johnmorrisdotca/chikyu-earth",
3
- "version": "0.4.0",
4
- "description": "Chikyu Earth 地球: a line-drawn globe with country outlines that turns at the Earth's own rate and opens centred on where you are. The outlines, names and codes come from chizu's world-geo (Natural Earth), drawn with d3-geo. SVG, no canvas.",
3
+ "version": "0.5.0",
4
+ "description": "Chikyu Earth 地球: a line-drawn globe with country outlines that turns at the Earth's own rate and opens centred on where you are. The outlines, names and codes come from chizu's world-geo (Natural Earth), drawn with d3-geo. SVG, with an optional realistic Earth from space (three.js) and markers.",
5
5
  "keywords": [
6
6
  "earth",
7
7
  "globe",
@@ -22,7 +22,11 @@
22
22
  "custom-element",
23
23
  "d3-geo",
24
24
  "natural-earth",
25
- "typescript"
25
+ "typescript",
26
+ "markers",
27
+ "satellite-imagery",
28
+ "webgl",
29
+ "three"
26
30
  ],
27
31
  "homepage": "https://johnmorrisdotca.github.io/chikyu-earth/",
28
32
  "repository": {
@@ -37,7 +41,9 @@
37
41
  "type": "module",
38
42
  "sideEffects": [
39
43
  "./dist/element.js",
40
- "./src/element.ts"
44
+ "./src/element.ts",
45
+ "./dist/realistic/element.js",
46
+ "./src/realistic/element.ts"
41
47
  ],
42
48
  "main": "./dist/index.js",
43
49
  "module": "./dist/index.js",
@@ -53,6 +59,21 @@
53
59
  "import": "./dist/element.js",
54
60
  "default": "./dist/element.js"
55
61
  },
62
+ "./realistic": {
63
+ "types": "./dist/realistic/index.d.ts",
64
+ "import": "./dist/realistic/index.js",
65
+ "default": "./dist/realistic/index.js"
66
+ },
67
+ "./realistic/element": {
68
+ "types": "./dist/realistic/element.d.ts",
69
+ "import": "./dist/realistic/element.js",
70
+ "default": "./dist/realistic/element.js"
71
+ },
72
+ "./markers": {
73
+ "types": "./dist/markers/index.d.ts",
74
+ "import": "./dist/markers/index.js",
75
+ "default": "./dist/markers/index.js"
76
+ },
56
77
  "./package.json": "./package.json"
57
78
  },
58
79
  "files": [
@@ -67,7 +88,7 @@
67
88
  "node": ">=24"
68
89
  },
69
90
  "scripts": {
70
- "build": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\" && tsc -p tsconfig.json",
91
+ "build": "node -e \"const fs=require('node:fs');fs.rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.json && node -e \"require('node:fs').cpSync('textures','dist/textures',{recursive:true})\"",
71
92
  "lint": "eslint .",
72
93
  "typecheck": "tsc -p tsconfig.check.json && tsc -p tsconfig.tools.json && tsc -p tsconfig.demo.json",
73
94
  "test": "node --test test/*.test.ts",
@@ -76,13 +97,27 @@
76
97
  "prepublishOnly": "pnpm check",
77
98
  "site": "pnpm build && node scripts/site.ts",
78
99
  "dev": "pnpm site && node scripts/dev.ts",
79
- "test:demo": "pnpm site && playwright test",
80
- "screenshots": "pnpm site && node scripts/screenshots.ts"
100
+ "test:demo": "pnpm site && playwright test --project=chromium-desk --project=chromium-phone --project=webkit-desk --project=webkit-phone --project=chromium-gl",
101
+ "screenshots": "pnpm site && node scripts/screenshots.ts",
102
+ "textures": "python3 scripts/make-textures.py",
103
+ "test:soak": "pnpm site && playwright test --project=soak"
81
104
  },
82
105
  "dependencies": {
83
106
  "@johnmorrisdotca/chizu": "^1.6.0",
84
107
  "d3-geo": "^3.1.1"
85
108
  },
109
+ "peerDependencies": {
110
+ "@johnmorrisdotca/kazoku-ui": ">=0.9.0",
111
+ "three": ">=0.160.0"
112
+ },
113
+ "peerDependenciesMeta": {
114
+ "@johnmorrisdotca/kazoku-ui": {
115
+ "optional": true
116
+ },
117
+ "three": {
118
+ "optional": true
119
+ }
120
+ },
86
121
  "devDependencies": {
87
122
  "@eslint/js": "^9.0.0",
88
123
  "@johnmorrisdotca/kazoku-ui": "^0.9.0",
@@ -90,8 +125,11 @@
90
125
  "@types/d3-geo": "^3.1.0",
91
126
  "@types/geojson": "^7946.0.14",
92
127
  "@types/node": "^24.0.0",
128
+ "@types/three": "^0.187.0",
129
+ "astronomy-engine": "2.1.19",
93
130
  "esbuild": "^0.25.0",
94
131
  "eslint": "^9.0.0",
132
+ "three": "0.186.1",
95
133
  "typescript": "^5.9.0",
96
134
  "typescript-eslint": "^8.0.0"
97
135
  },