@dxos/react-ui-geo 0.8.4-main.fffef41 → 0.8.4-staging.60fe92afc8
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.
- package/LICENSE +102 -5
- package/data/countries-10m.ts +12 -0
- package/data/countries-110m.ts +4 -10579
- package/data/countries-50m.ts +12 -0
- package/dist/lib/browser/chunk-SC2FBYFU.mjs +17 -0
- package/dist/lib/browser/chunk-SC2FBYFU.mjs.map +7 -0
- package/dist/lib/browser/countries-10m-CWWDOKH7.mjs +6 -0
- package/dist/lib/browser/countries-10m-CWWDOKH7.mjs.map +7 -0
- package/dist/lib/browser/countries-110m-72QBAA5E.mjs +6 -0
- package/dist/lib/browser/countries-110m-72QBAA5E.mjs.map +7 -0
- package/dist/lib/browser/countries-50m-H7SL7KVF.mjs +6 -0
- package/dist/lib/browser/countries-50m-H7SL7KVF.mjs.map +7 -0
- package/dist/lib/browser/data.mjs +1 -1
- package/dist/lib/browser/index.mjs +1046 -579
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/translations.mjs +19 -0
- package/dist/lib/browser/translations.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-VZENBYLJ.mjs +19 -0
- package/dist/lib/node-esm/chunk-VZENBYLJ.mjs.map +7 -0
- package/dist/lib/node-esm/countries-10m-DJZV66KG.mjs +8 -0
- package/dist/lib/node-esm/countries-10m-DJZV66KG.mjs.map +7 -0
- package/dist/lib/node-esm/countries-110m-H3WY6K4Q.mjs +8 -0
- package/dist/lib/node-esm/countries-110m-H3WY6K4Q.mjs.map +7 -0
- package/dist/lib/node-esm/countries-50m-ZY7Z3IWD.mjs +8 -0
- package/dist/lib/node-esm/countries-50m-ZY7Z3IWD.mjs.map +7 -0
- package/dist/lib/node-esm/data.mjs +1 -1
- package/dist/lib/node-esm/index.mjs +1046 -579
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/translations.mjs +21 -0
- package/dist/lib/node-esm/translations.mjs.map +7 -0
- package/dist/types/data/airports.d.ts +4 -4
- package/dist/types/data/airports.d.ts.map +1 -1
- package/dist/types/data/cities.d.ts.map +1 -1
- package/dist/types/data/countries-10m.d.ts +8 -0
- package/dist/types/data/countries-10m.d.ts.map +1 -0
- package/dist/types/data/countries-110m.d.ts +2 -30
- package/dist/types/data/countries-110m.d.ts.map +1 -1
- package/dist/types/data/countries-50m.d.ts +8 -0
- package/dist/types/data/countries-50m.d.ts.map +1 -0
- package/dist/types/data/countries-dots-3.d.ts.map +1 -1
- package/dist/types/data/countries-dots-4.d.ts.map +1 -1
- package/dist/types/src/components/Globe/Globe.d.ts +19 -9
- package/dist/types/src/components/Globe/Globe.d.ts.map +1 -1
- package/dist/types/src/components/Globe/Globe.stories.d.ts +17 -7
- package/dist/types/src/components/Globe/Globe.stories.d.ts.map +1 -1
- package/dist/types/src/components/Map/Map.d.ts +51 -9
- package/dist/types/src/components/Map/Map.d.ts.map +1 -1
- package/dist/types/src/components/Map/Map.stories.d.ts +9 -5
- package/dist/types/src/components/Map/Map.stories.d.ts.map +1 -1
- package/dist/types/src/components/Toolbar/Controls.d.ts.map +1 -1
- package/dist/types/src/data.d.ts +9 -1
- package/dist/types/src/data.d.ts.map +1 -1
- package/dist/types/src/hooks/context.d.ts +38 -3
- package/dist/types/src/hooks/context.d.ts.map +1 -1
- package/dist/types/src/hooks/index.d.ts +3 -0
- package/dist/types/src/hooks/index.d.ts.map +1 -1
- package/dist/types/src/hooks/useDrag.d.ts +22 -2
- package/dist/types/src/hooks/useDrag.d.ts.map +1 -1
- package/dist/types/src/hooks/useGlobeZoomHandler.d.ts +3 -2
- package/dist/types/src/hooks/useGlobeZoomHandler.d.ts.map +1 -1
- package/dist/types/src/hooks/useMapZoomHandler.d.ts +1 -1
- package/dist/types/src/hooks/useMapZoomHandler.d.ts.map +1 -1
- package/dist/types/src/hooks/useSimplifiedTopology.d.ts +32 -0
- package/dist/types/src/hooks/useSimplifiedTopology.d.ts.map +1 -0
- package/dist/types/src/hooks/useSpinner.d.ts +1 -1
- package/dist/types/src/hooks/useSpinner.d.ts.map +1 -1
- package/dist/types/src/hooks/useTopology.d.ts +26 -0
- package/dist/types/src/hooks/useTopology.d.ts.map +1 -0
- package/dist/types/src/hooks/useTour.d.ts +3 -2
- package/dist/types/src/hooks/useTour.d.ts.map +1 -1
- package/dist/types/src/hooks/useWheel.d.ts +24 -0
- package/dist/types/src/hooks/useWheel.d.ts.map +1 -0
- package/dist/types/src/index.d.ts +0 -2
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +6 -6
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/util/animation.d.ts +16 -0
- package/dist/types/src/util/animation.d.ts.map +1 -0
- package/dist/types/src/util/debug.d.ts.map +1 -1
- package/dist/types/src/util/index.d.ts +2 -0
- package/dist/types/src/util/index.d.ts.map +1 -1
- package/dist/types/src/util/inertia.d.ts.map +1 -1
- package/dist/types/src/util/path.d.ts.map +1 -1
- package/dist/types/src/util/render.d.ts +25 -1
- package/dist/types/src/util/render.d.ts.map +1 -1
- package/dist/types/src/util/styles.d.ts +4 -0
- package/dist/types/src/util/styles.d.ts.map +1 -0
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +41 -35
- package/src/components/Globe/Globe.stories.tsx +141 -65
- package/src/components/Globe/Globe.tsx +262 -119
- package/src/components/Map/Map.stories.tsx +59 -12
- package/src/components/Map/Map.tsx +325 -82
- package/src/components/Toolbar/Controls.tsx +5 -5
- package/src/data.ts +19 -2
- package/src/hooks/context.tsx +46 -31
- package/src/hooks/index.ts +3 -0
- package/src/hooks/useDrag.ts +33 -5
- package/src/hooks/useGlobeZoomHandler.ts +2 -1
- package/src/hooks/useSimplifiedTopology.ts +81 -0
- package/src/hooks/useSpinner.ts +1 -2
- package/src/hooks/useTopology.ts +95 -0
- package/src/hooks/useTour.ts +70 -81
- package/src/hooks/useWheel.ts +83 -0
- package/src/index.ts +0 -2
- package/src/translations.ts +5 -5
- package/src/util/animation.ts +35 -0
- package/src/util/index.ts +2 -0
- package/src/util/inertia.ts +87 -4
- package/src/util/render.ts +105 -17
- package/src/util/styles.ts +62 -0
- package/dist/lib/browser/chunk-GMWLKTLN.mjs +0 -9
- package/dist/lib/browser/chunk-GMWLKTLN.mjs.map +0 -7
- package/dist/lib/browser/countries-110m-ZM3ZIEFS.mjs +0 -37859
- package/dist/lib/browser/countries-110m-ZM3ZIEFS.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-JODBF4CC.mjs +0 -11
- package/dist/lib/node-esm/chunk-JODBF4CC.mjs.map +0 -7
- package/dist/lib/node-esm/countries-110m-3SFASWVD.mjs +0 -37861
- package/dist/lib/node-esm/countries-110m-3SFASWVD.mjs.map +0 -7
package/src/translations.ts
CHANGED
|
@@ -4,16 +4,16 @@
|
|
|
4
4
|
|
|
5
5
|
import { type Resource } from '@dxos/react-ui';
|
|
6
6
|
|
|
7
|
-
export const translationKey = 'react-ui-geo';
|
|
7
|
+
export const translationKey = '@dxos/react-ui-geo';
|
|
8
8
|
|
|
9
9
|
export const translations = [
|
|
10
10
|
{
|
|
11
11
|
'en-US': {
|
|
12
12
|
[translationKey]: {
|
|
13
|
-
'zoom
|
|
14
|
-
'zoom
|
|
15
|
-
'start
|
|
16
|
-
'toggle
|
|
13
|
+
'zoom-in-icon.button': 'Zoom in',
|
|
14
|
+
'zoom-out-icon.button': 'Zoom out',
|
|
15
|
+
'start-icon.button': 'Start',
|
|
16
|
+
'toggle-icon.button': 'Toggle',
|
|
17
17
|
},
|
|
18
18
|
},
|
|
19
19
|
},
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type GeoProjection, geoDistance } from 'd3';
|
|
6
|
+
import versor from 'versor';
|
|
7
|
+
|
|
8
|
+
import { type Vector } from '../hooks/context';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Duration scaled by great-circle distance between two geo positions.
|
|
12
|
+
* Ensures long jumps animate longer than short ones while clamping to a
|
|
13
|
+
* minimum base. `scale` controls how steeply duration grows with distance
|
|
14
|
+
* (ms per radian of arc).
|
|
15
|
+
*/
|
|
16
|
+
export const flyDuration = (p1: [number, number], p2: [number, number], base: number, scale: number): number =>
|
|
17
|
+
Math.max(base, geoDistance(p1, p2) * scale);
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Per-frame tween that interpolates the projection's rotation between two
|
|
21
|
+
* Euler triples along the shortest great-circle arc using versors. Mutates
|
|
22
|
+
* the projection and pushes the normalised rotation through `setRotation`.
|
|
23
|
+
*/
|
|
24
|
+
export const createRotationTween = (
|
|
25
|
+
projection: GeoProjection,
|
|
26
|
+
setRotation: (rotation: Vector) => void,
|
|
27
|
+
r1: Vector,
|
|
28
|
+
r2: Vector,
|
|
29
|
+
): ((t: number) => void) => {
|
|
30
|
+
const iv = versor.interpolate(r1, r2);
|
|
31
|
+
return (t: number) => {
|
|
32
|
+
projection.rotate(iv(t));
|
|
33
|
+
setRotation(projection.rotate() as Vector);
|
|
34
|
+
};
|
|
35
|
+
};
|
package/src/util/index.ts
CHANGED
package/src/util/inertia.ts
CHANGED
|
@@ -28,14 +28,20 @@ export const geoInertiaDrag = (target, render, projection, options) => {
|
|
|
28
28
|
}
|
|
29
29
|
target = select(target);
|
|
30
30
|
|
|
31
|
-
//
|
|
32
|
-
|
|
31
|
+
// `linear` (default) constrains rotation so that mouse Δx maps to lambda
|
|
32
|
+
// (polar spin) and Δy maps to phi (tilt); gamma is held at 0.
|
|
33
|
+
// `versor` lets the dragged point follow the cursor exactly, at the cost of
|
|
34
|
+
// inducing some roll. In linear mode `lockTilt` keeps phi pinned but still
|
|
35
|
+
// allows lambda from Δx.
|
|
36
|
+
const linear = (options.mode ?? 'linear') === 'linear';
|
|
37
|
+
const axis = restrictAxis(options.lockTilt ? [true, false, false] : [true, true, true]);
|
|
38
|
+
const sharedHandlers = {
|
|
33
39
|
projection,
|
|
34
40
|
render: (rotation) => {
|
|
35
41
|
projection.rotate(rotation);
|
|
36
42
|
render && render();
|
|
37
43
|
},
|
|
38
|
-
axis
|
|
44
|
+
axis,
|
|
39
45
|
start: options.start,
|
|
40
46
|
move: options.move,
|
|
41
47
|
end: options.end,
|
|
@@ -43,7 +49,16 @@ export const geoInertiaDrag = (target, render, projection, options) => {
|
|
|
43
49
|
finish: options.finish,
|
|
44
50
|
time: options.time,
|
|
45
51
|
hold: options.hold,
|
|
46
|
-
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// Complete params: (projection, render, startDrag, dragging, endDrag).
|
|
55
|
+
const inertia = linear
|
|
56
|
+
? geoInertiaDragLinearHelper({
|
|
57
|
+
...sharedHandlers,
|
|
58
|
+
sensitivity: options.sensitivity,
|
|
59
|
+
getZoom: options.getZoom,
|
|
60
|
+
})
|
|
61
|
+
: geoInertiaDragHelper(sharedHandlers);
|
|
47
62
|
|
|
48
63
|
target.call(drag().on('start', inertia.start).on('drag', inertia.move).on('end', inertia.end));
|
|
49
64
|
return inertia;
|
|
@@ -112,6 +127,74 @@ const geoInertiaDragHelper = (opt) => {
|
|
|
112
127
|
return inertia;
|
|
113
128
|
};
|
|
114
129
|
|
|
130
|
+
/**
|
|
131
|
+
* Linear pixel-to-Euler drag: Δx → lambda, Δy → phi, gamma fixed at 0.
|
|
132
|
+
* Inertia spin-down reuses the same shared decay curve as the versor path.
|
|
133
|
+
*/
|
|
134
|
+
const DEFAULT_LINEAR_SENSITIVITY = 0.25;
|
|
135
|
+
|
|
136
|
+
const geoInertiaDragLinearHelper = (opt) => {
|
|
137
|
+
const projection = opt.projection;
|
|
138
|
+
const sensitivity = opt.sensitivity ?? DEFAULT_LINEAR_SENSITIVITY;
|
|
139
|
+
// Scale degrees-per-pixel by 1/zoom so the drag feels consistent at any zoom
|
|
140
|
+
// level (mirrors useWheel — a more zoomed-in globe needs smaller angular
|
|
141
|
+
// rotation per pixel of cursor travel). Clamped to a floor so very small
|
|
142
|
+
// zoom values don't blow up the gain.
|
|
143
|
+
const gain = () => sensitivity / Math.max(opt.getZoom?.() ?? 1, 0.1);
|
|
144
|
+
|
|
145
|
+
let r0; // Projection rotation as Euler angles at start of drag.
|
|
146
|
+
let p0; // Pointer pixel position at start of drag.
|
|
147
|
+
let kStart; // Gain captured at start of drag (held for the gesture + inertia).
|
|
148
|
+
let rEnd; // Projection rotation at end of drag.
|
|
149
|
+
let vEnd; // Pointer velocity (px/s) at end of drag.
|
|
150
|
+
|
|
151
|
+
const inertia = inertiaHelper({
|
|
152
|
+
axis: opt.axis,
|
|
153
|
+
|
|
154
|
+
start: () => {
|
|
155
|
+
r0 = projection.rotate();
|
|
156
|
+
p0 = [inertia.position[0], inertia.position[1]];
|
|
157
|
+
// Lock the gain at gesture start so a zoom change mid-gesture doesn't
|
|
158
|
+
// teleport the globe; inertia continues at the same gain.
|
|
159
|
+
kStart = gain();
|
|
160
|
+
opt.start && opt.start();
|
|
161
|
+
},
|
|
162
|
+
|
|
163
|
+
move: () => {
|
|
164
|
+
const dx = inertia.position[0] - p0[0];
|
|
165
|
+
const dy = inertia.position[1] - p0[1];
|
|
166
|
+
// Screen y grows downward; negate so dragging down rotates the globe to
|
|
167
|
+
// match the cursor (matches the feel of the versor-based path).
|
|
168
|
+
const r1 = [r0[0] + dx * kStart, r0[1] - dy * kStart, 0];
|
|
169
|
+
const r2 = opt.axis(r0, r1);
|
|
170
|
+
opt.render(r2);
|
|
171
|
+
opt.move && opt.move();
|
|
172
|
+
},
|
|
173
|
+
|
|
174
|
+
end: () => {
|
|
175
|
+
rEnd = projection.rotate();
|
|
176
|
+
vEnd = [inertia.velocity[0], inertia.velocity[1]];
|
|
177
|
+
opt.end && opt.end();
|
|
178
|
+
},
|
|
179
|
+
|
|
180
|
+
stop: opt.stop,
|
|
181
|
+
|
|
182
|
+
finish: opt.finish,
|
|
183
|
+
|
|
184
|
+
render: (t) => {
|
|
185
|
+
// t goes 0→1 along the decay curve; at t=1 we've added ~1s of velocity.
|
|
186
|
+
// dy sign flipped to match the move handler.
|
|
187
|
+
const r1 = [rEnd[0] + vEnd[0] * kStart * t, rEnd[1] - vEnd[1] * kStart * t, 0];
|
|
188
|
+
const r2 = opt.axis(rEnd, r1);
|
|
189
|
+
opt.render && opt.render(r2);
|
|
190
|
+
},
|
|
191
|
+
|
|
192
|
+
time: opt.time,
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
return inertia;
|
|
196
|
+
};
|
|
197
|
+
|
|
115
198
|
function inertiaHelper(opt) {
|
|
116
199
|
const A = opt.time || 5_000; // Reference time in ms.
|
|
117
200
|
const limit = 1.0001;
|
package/src/util/render.ts
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
// Copyright 2020 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { type GeoPath, type GeoPermissibleObjects, geoGraticule } from 'd3';
|
|
5
|
+
import { type GeoPath, type GeoPermissibleObjects, geoBounds, geoCentroid, geoDistance, geoGraticule } from 'd3';
|
|
6
|
+
import { type Feature, type Geometry } from 'geojson';
|
|
6
7
|
import { feature, mesh } from 'topojson-client';
|
|
7
8
|
import { type Topology } from 'topojson-specification';
|
|
8
9
|
|
|
9
10
|
import { type LatLngLiteral } from '../types';
|
|
10
|
-
|
|
11
11
|
import { geoLine, geoPoint } from './path';
|
|
12
12
|
|
|
13
13
|
export type Styles = Record<string, any>;
|
|
@@ -31,9 +31,57 @@ export type Features = {
|
|
|
31
31
|
lines?: { source: LatLngLiteral; target: LatLngLiteral }[];
|
|
32
32
|
};
|
|
33
33
|
|
|
34
|
+
/**
|
|
35
|
+
* Per-feature bounding circle used for view-frustum culling on an
|
|
36
|
+
* orthographic globe. `centroid` is in lon/lat degrees and `radius` is the
|
|
37
|
+
* angular distance (degrees) from the centroid to the farthest sampled
|
|
38
|
+
* vertex of the feature.
|
|
39
|
+
*/
|
|
40
|
+
export type FeatureBounds = {
|
|
41
|
+
geometry: Geometry;
|
|
42
|
+
centroid: [number, number];
|
|
43
|
+
radius: number;
|
|
44
|
+
};
|
|
45
|
+
|
|
34
46
|
export type Layer = {
|
|
35
47
|
styles: Styles;
|
|
36
48
|
path: GeoPermissibleObjects;
|
|
49
|
+
/**
|
|
50
|
+
* If present, this layer is treated as a `GeometryCollection` whose
|
|
51
|
+
* member geometries are filtered per-frame by `viewCenter` against each
|
|
52
|
+
* member's `FeatureBounds`. `path` becomes the *unculled* fallback used
|
|
53
|
+
* when no `viewCenter` is supplied (e.g. non-orthographic projections).
|
|
54
|
+
*/
|
|
55
|
+
cullable?: FeatureBounds[];
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const RAD_TO_DEG = 180 / Math.PI;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Compute a spherical bounding circle for a GeoJSON feature. We sample the
|
|
62
|
+
* geoBounds corners (cheap, sufficient for typical country shapes); for
|
|
63
|
+
* features crossing the antimeridian d3.geoBounds returns west > east, which
|
|
64
|
+
* geoDistance handles correctly when called on the actual centroid.
|
|
65
|
+
*/
|
|
66
|
+
const computeBounds = (geometry: Geometry): FeatureBounds => {
|
|
67
|
+
const feat: Feature = { type: 'Feature', geometry, properties: {} };
|
|
68
|
+
const centroid = geoCentroid(feat) as [number, number];
|
|
69
|
+
const [[w, s], [e, n]] = geoBounds(feat);
|
|
70
|
+
// Sample the four bbox corners; widest is the bounding radius.
|
|
71
|
+
const corners: Array<[number, number]> = [
|
|
72
|
+
[w, s],
|
|
73
|
+
[w, n],
|
|
74
|
+
[e, s],
|
|
75
|
+
[e, n],
|
|
76
|
+
];
|
|
77
|
+
let radius = 0;
|
|
78
|
+
for (const corner of corners) {
|
|
79
|
+
const d = geoDistance(centroid, corner) * RAD_TO_DEG;
|
|
80
|
+
if (d > radius) {
|
|
81
|
+
radius = d;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return { geometry, centroid, radius };
|
|
37
85
|
};
|
|
38
86
|
|
|
39
87
|
/**
|
|
@@ -63,11 +111,25 @@ export const createLayers = (topology: Topology, features: Features, styles: Sty
|
|
|
63
111
|
//
|
|
64
112
|
|
|
65
113
|
if (topology) {
|
|
66
|
-
if (
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
114
|
+
if (styles.land) {
|
|
115
|
+
// Prefer the `countries` GeometryCollection over the merged `land`
|
|
116
|
+
// multipolygon so each country can be culled independently. Visually
|
|
117
|
+
// identical (countries collectively tile the land surface).
|
|
118
|
+
if (topology.objects.countries) {
|
|
119
|
+
const fc = feature(topology, topology.objects.countries) as any;
|
|
120
|
+
const memberGeoms: Geometry[] = fc.features.map((f: Feature) => f.geometry);
|
|
121
|
+
const bounds = memberGeoms.map(computeBounds);
|
|
122
|
+
layers.push({
|
|
123
|
+
styles: styles.land,
|
|
124
|
+
path: { type: 'GeometryCollection', geometries: memberGeoms } as any,
|
|
125
|
+
cullable: bounds,
|
|
126
|
+
});
|
|
127
|
+
} else if (topology.objects.land) {
|
|
128
|
+
layers.push({
|
|
129
|
+
styles: styles.land,
|
|
130
|
+
path: feature(topology, topology.objects.land),
|
|
131
|
+
});
|
|
132
|
+
}
|
|
71
133
|
}
|
|
72
134
|
|
|
73
135
|
if (topology.objects.countries && styles.border) {
|
|
@@ -92,22 +154,24 @@ export const createLayers = (topology: Topology, features: Features, styles: Sty
|
|
|
92
154
|
if (features) {
|
|
93
155
|
const { points, lines } = features;
|
|
94
156
|
|
|
95
|
-
|
|
157
|
+
// Lines first so points (drawn after) sit on top — the route nodes should
|
|
158
|
+
// never be occluded by an arc that passes through them.
|
|
159
|
+
if (lines && styles.line) {
|
|
96
160
|
layers.push({
|
|
97
|
-
styles: styles.
|
|
161
|
+
styles: styles.line,
|
|
98
162
|
path: {
|
|
99
163
|
type: 'GeometryCollection',
|
|
100
|
-
geometries:
|
|
164
|
+
geometries: lines.map(({ source, target }) => geoLine(source, target)),
|
|
101
165
|
},
|
|
102
166
|
});
|
|
103
167
|
}
|
|
104
168
|
|
|
105
|
-
if (
|
|
169
|
+
if (points && styles.point) {
|
|
106
170
|
layers.push({
|
|
107
|
-
styles: styles.
|
|
171
|
+
styles: styles.point,
|
|
108
172
|
path: {
|
|
109
173
|
type: 'GeometryCollection',
|
|
110
|
-
geometries:
|
|
174
|
+
geometries: points.map((point) => geoPoint(point)),
|
|
111
175
|
},
|
|
112
176
|
});
|
|
113
177
|
}
|
|
@@ -118,8 +182,19 @@ export const createLayers = (topology: Topology, features: Features, styles: Sty
|
|
|
118
182
|
|
|
119
183
|
/**
|
|
120
184
|
* Render layers created above.
|
|
185
|
+
*
|
|
186
|
+
* When `viewCenter` is supplied (orthographic globe), layers with a
|
|
187
|
+
* `cullable` index are filtered to just the features whose bounding circle
|
|
188
|
+
* intersects the visible hemisphere — keeping the d3-geo walk proportional
|
|
189
|
+
* to what's actually on-screen.
|
|
121
190
|
*/
|
|
122
|
-
export const renderLayers = (
|
|
191
|
+
export const renderLayers = (
|
|
192
|
+
generator: GeoPath,
|
|
193
|
+
layers: Layer[] = [],
|
|
194
|
+
scale: number,
|
|
195
|
+
styles: StyleSet,
|
|
196
|
+
viewCenter?: [number, number],
|
|
197
|
+
) => {
|
|
123
198
|
const context: CanvasRenderingContext2D = generator.context();
|
|
124
199
|
const {
|
|
125
200
|
canvas: { width, height },
|
|
@@ -136,7 +211,8 @@ export const renderLayers = (generator: GeoPath, layers: Layer[] = [], scale: nu
|
|
|
136
211
|
|
|
137
212
|
// Render features.
|
|
138
213
|
// https://github.com/d3/d3-geo#_path
|
|
139
|
-
layers.forEach((
|
|
214
|
+
layers.forEach((layer) => {
|
|
215
|
+
const { path, styles, cullable } = layer;
|
|
140
216
|
context.save();
|
|
141
217
|
let fill = false;
|
|
142
218
|
let stroke = false;
|
|
@@ -152,9 +228,21 @@ export const renderLayers = (generator: GeoPath, layers: Layer[] = [], scale: nu
|
|
|
152
228
|
});
|
|
153
229
|
}
|
|
154
230
|
|
|
155
|
-
|
|
231
|
+
let renderPath = path;
|
|
232
|
+
if (cullable && viewCenter) {
|
|
233
|
+
const geometries: Geometry[] = [];
|
|
234
|
+
for (let index = 0; index < cullable.length; index++) {
|
|
235
|
+
const bounds = cullable[index];
|
|
236
|
+
const angularDistance = geoDistance(viewCenter, bounds.centroid) * RAD_TO_DEG;
|
|
237
|
+
if (angularDistance < 90 + bounds.radius) {
|
|
238
|
+
geometries.push(bounds.geometry);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
renderPath = { type: 'GeometryCollection', geometries } as any;
|
|
242
|
+
}
|
|
156
243
|
|
|
157
|
-
|
|
244
|
+
context.beginPath();
|
|
245
|
+
generator(renderPath);
|
|
158
246
|
fill && context.fill();
|
|
159
247
|
stroke && context.stroke();
|
|
160
248
|
context.restore();
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type ThemeMode } from '@dxos/react-ui';
|
|
6
|
+
|
|
7
|
+
import { type StyleSet } from './render';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Default style set for the Globe, theme-aware. Originated in plugin-map's
|
|
11
|
+
* GlobeControl; lifted here so other plugins (plugin-trip, etc.) get the same
|
|
12
|
+
* baseline without copying the palette.
|
|
13
|
+
*/
|
|
14
|
+
// Point colour; lines pick up the same colour at reduced alpha so the route
|
|
15
|
+
// reads as belonging to the same node set without competing with the nodes.
|
|
16
|
+
const POINT_COLOR = 'rgb(220, 38, 38)';
|
|
17
|
+
const LINE_COLOR = 'rgba(220, 38, 38, 0.5)';
|
|
18
|
+
|
|
19
|
+
export const globeStyles = (themeMode: ThemeMode): StyleSet =>
|
|
20
|
+
themeMode === 'dark'
|
|
21
|
+
? {
|
|
22
|
+
water: {
|
|
23
|
+
fillStyle: '#191919',
|
|
24
|
+
},
|
|
25
|
+
land: {
|
|
26
|
+
fillStyle: '#444',
|
|
27
|
+
strokeStyle: '#222',
|
|
28
|
+
},
|
|
29
|
+
border: {
|
|
30
|
+
strokeStyle: '#111',
|
|
31
|
+
},
|
|
32
|
+
graticule: {
|
|
33
|
+
strokeStyle: '#111',
|
|
34
|
+
},
|
|
35
|
+
line: {
|
|
36
|
+
lineWidth: 1.5,
|
|
37
|
+
lineDash: [4, 16],
|
|
38
|
+
strokeStyle: LINE_COLOR,
|
|
39
|
+
},
|
|
40
|
+
point: {
|
|
41
|
+
radius: 0.2,
|
|
42
|
+
fillStyle: POINT_COLOR,
|
|
43
|
+
},
|
|
44
|
+
}
|
|
45
|
+
: {
|
|
46
|
+
water: {
|
|
47
|
+
fillStyle: '#C0DAE4',
|
|
48
|
+
},
|
|
49
|
+
land: {
|
|
50
|
+
fillStyle: '#C2D8B4',
|
|
51
|
+
strokeStyle: '#A6C291',
|
|
52
|
+
},
|
|
53
|
+
line: {
|
|
54
|
+
lineWidth: 1.5,
|
|
55
|
+
lineDash: [4, 16],
|
|
56
|
+
strokeStyle: LINE_COLOR,
|
|
57
|
+
},
|
|
58
|
+
point: {
|
|
59
|
+
radius: 0.2,
|
|
60
|
+
fillStyle: POINT_COLOR,
|
|
61
|
+
},
|
|
62
|
+
};
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../../src/data.ts"],
|
|
4
|
-
"sourcesContent": ["//\n// Copyright 2024 DXOS.org\n//\n\nimport { type Topology } from 'topojson-specification';\n\nexport const loadTopology = async (): Promise<Topology> => {\n return (await import('../data/countries-110m.ts')).default;\n};\n"],
|
|
5
|
-
"mappings": ";AAMO,IAAMA,eAAe,YAAA;AAC1B,UAAQ,MAAM,OAAO,+BAAA,GAA8BC;AACrD;",
|
|
6
|
-
"names": ["loadTopology", "default"]
|
|
7
|
-
}
|