@trackunit/react-map-adapter-google 0.0.132 → 0.0.133
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/README.md +7 -1
- package/buildDeckGlHeatmapLayer.cjs.js +30 -0
- package/buildDeckGlHeatmapLayer.esm.js +28 -0
- package/createDeckGlGoogleMapsOverlayRuntime.cjs.js +26 -0
- package/createDeckGlGoogleMapsOverlayRuntime.esm.js +24 -0
- package/index.cjs.js +218 -4
- package/index.esm.js +218 -4
- package/package.json +11 -4
- package/src/GoogleMapsLayerPort.d.ts +3 -0
- package/src/deckGl/DeckGlGoogleMapsLifecycle.d.ts +38 -0
- package/src/deckGl/DeckGlGoogleMapsOverlayCoordinator.d.ts +33 -0
- package/src/deckGl/createDeckGlGoogleMapsOverlayRuntime.d.ts +17 -0
- package/src/deckGl/loadDeckGlGoogleMapsOverlayRuntime.d.ts +5 -0
- package/src/googleMapsAdapter.d.ts +6 -0
- package/src/heatmap/buildDeckGlHeatmapLayer.d.ts +8 -0
package/README.md
CHANGED
|
@@ -58,9 +58,15 @@ const irisAppManifest = {
|
|
|
58
58
|
|
|
59
59
|
Use the exported directives instead of copying host lists or using the Manager-only `defaultCspConfig()`. This keeps the CSP requirement versioned with the adapter.
|
|
60
60
|
|
|
61
|
+
## Heatmaps
|
|
62
|
+
|
|
63
|
+
- **Renderer:** lazy `@deck.gl/google-maps` overlay + `HeatmapLayer` (Google's own Heatmap Layer is decommissioned - see https://developers.google.com/maps/deprecations#heatmap-layer-js-deprecation).
|
|
64
|
+
- **Integration:** Heatmap layers from `@trackunit/react-map` (`layerType: "heatmap"` on the adapter snapshot) trigger lazy deck.gl inside this adapter — no deck.gl types or APIs are exported here.
|
|
65
|
+
- **Contract:** shared `HeatmapSourceConfig` from `@trackunit/react-map-adapter-shared`.
|
|
66
|
+
|
|
61
67
|
## Dependencies
|
|
62
68
|
|
|
63
|
-
Depends on `@trackunit/react-map-adapter-shared
|
|
69
|
+
Depends on `@trackunit/react-map-adapter-shared`, `@trackunit/react-map-color-utils`, `@trackunit/geo-json-utils`, Google Maps packages (e.g. `@vis.gl/react-google-maps`, `@googlemaps/markerclusterer`), and `@deck.gl/*` (heatmap on Google — **dynamic import** at runtime when a heatmap is active; still declared npm dependencies). It does **not** import `@trackunit/react-map` (adapters are consumed *by* the map package, not the reverse).
|
|
64
70
|
|
|
65
71
|
## Part of the `@trackunit/react-map` family
|
|
66
72
|
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var aggregationLayers = require('@deck.gl/aggregation-layers');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Maps {@link HeatmapSourceConfig} → deck.gl `HeatmapLayer` on Google Maps.
|
|
7
|
+
*
|
|
8
|
+
* Invoked from {@link DeckGlGoogleMapsLifecycle} after dynamic import.
|
|
9
|
+
*/
|
|
10
|
+
const buildDeckGlHeatmapLayer = (config) => {
|
|
11
|
+
return new aggregationLayers.HeatmapLayer({
|
|
12
|
+
id: `heatmap-${config.id}`,
|
|
13
|
+
data: config.features.features,
|
|
14
|
+
getPosition: feature => {
|
|
15
|
+
if (feature.geometry?.type !== "Point")
|
|
16
|
+
return [0, 0];
|
|
17
|
+
return [feature.geometry.coordinates[0], feature.geometry.coordinates[1]];
|
|
18
|
+
},
|
|
19
|
+
getWeight: feature => {
|
|
20
|
+
const weight = feature.properties?.weight;
|
|
21
|
+
return typeof weight === "number" ? weight : 1;
|
|
22
|
+
},
|
|
23
|
+
aggregation: "SUM",
|
|
24
|
+
radiusPixels: config.style.radiusPixels,
|
|
25
|
+
intensity: config.style.intensity,
|
|
26
|
+
opacity: config.style.opacity,
|
|
27
|
+
});
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
exports.buildDeckGlHeatmapLayer = buildDeckGlHeatmapLayer;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { HeatmapLayer } from '@deck.gl/aggregation-layers';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Maps {@link HeatmapSourceConfig} → deck.gl `HeatmapLayer` on Google Maps.
|
|
5
|
+
*
|
|
6
|
+
* Invoked from {@link DeckGlGoogleMapsLifecycle} after dynamic import.
|
|
7
|
+
*/
|
|
8
|
+
const buildDeckGlHeatmapLayer = (config) => {
|
|
9
|
+
return new HeatmapLayer({
|
|
10
|
+
id: `heatmap-${config.id}`,
|
|
11
|
+
data: config.features.features,
|
|
12
|
+
getPosition: feature => {
|
|
13
|
+
if (feature.geometry?.type !== "Point")
|
|
14
|
+
return [0, 0];
|
|
15
|
+
return [feature.geometry.coordinates[0], feature.geometry.coordinates[1]];
|
|
16
|
+
},
|
|
17
|
+
getWeight: feature => {
|
|
18
|
+
const weight = feature.properties?.weight;
|
|
19
|
+
return typeof weight === "number" ? weight : 1;
|
|
20
|
+
},
|
|
21
|
+
aggregation: "SUM",
|
|
22
|
+
radiusPixels: config.style.radiusPixels,
|
|
23
|
+
intensity: config.style.intensity,
|
|
24
|
+
opacity: config.style.opacity,
|
|
25
|
+
});
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export { buildDeckGlHeatmapLayer };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var googleMaps = require('@deck.gl/google-maps');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Creates one `GoogleMapsOverlay` per map.
|
|
7
|
+
*
|
|
8
|
+
* See https://deck.gl/docs/api-reference/google-maps/google-maps-overlay
|
|
9
|
+
*/
|
|
10
|
+
const createDeckGlGoogleMapsOverlayRuntime = () => ({
|
|
11
|
+
createOverlay: map => {
|
|
12
|
+
const overlay = new googleMaps.GoogleMapsOverlay({
|
|
13
|
+
layers: [],
|
|
14
|
+
});
|
|
15
|
+
overlay.setMap(map);
|
|
16
|
+
return {
|
|
17
|
+
setMap: nextMap => overlay.setMap(nextMap),
|
|
18
|
+
setLayers: layers => {
|
|
19
|
+
overlay.setProps({ layers: [...layers] });
|
|
20
|
+
},
|
|
21
|
+
finalize: () => overlay.finalize(),
|
|
22
|
+
};
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
exports.createDeckGlGoogleMapsOverlayRuntime = createDeckGlGoogleMapsOverlayRuntime;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { GoogleMapsOverlay } from '@deck.gl/google-maps';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Creates one `GoogleMapsOverlay` per map.
|
|
5
|
+
*
|
|
6
|
+
* See https://deck.gl/docs/api-reference/google-maps/google-maps-overlay
|
|
7
|
+
*/
|
|
8
|
+
const createDeckGlGoogleMapsOverlayRuntime = () => ({
|
|
9
|
+
createOverlay: map => {
|
|
10
|
+
const overlay = new GoogleMapsOverlay({
|
|
11
|
+
layers: [],
|
|
12
|
+
});
|
|
13
|
+
overlay.setMap(map);
|
|
14
|
+
return {
|
|
15
|
+
setMap: nextMap => overlay.setMap(nextMap),
|
|
16
|
+
setLayers: layers => {
|
|
17
|
+
overlay.setProps({ layers: [...layers] });
|
|
18
|
+
},
|
|
19
|
+
finalize: () => overlay.finalize(),
|
|
20
|
+
};
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
export { createDeckGlGoogleMapsOverlayRuntime };
|
package/index.cjs.js
CHANGED
|
@@ -55,6 +55,196 @@ const GoogleApiProvider = ({ apiKey, language, children }) => {
|
|
|
55
55
|
return (jsxRuntime.jsx(reactGoogleMaps.APIProvider, { apiKey: apiKey, language: language, libraries: libraries, version: GOOGLE_MAPS_API_VERSION, children: children }));
|
|
56
56
|
};
|
|
57
57
|
|
|
58
|
+
let runtimePromise = null;
|
|
59
|
+
/**
|
|
60
|
+
* Lazily dynamic-imports {@link createDeckGlGoogleMapsOverlayRuntime} and caches the promise.
|
|
61
|
+
*/
|
|
62
|
+
const loadDeckGlGoogleMapsOverlayRuntime = () => {
|
|
63
|
+
runtimePromise ?? (runtimePromise = Promise.resolve().then(function () { return require('./createDeckGlGoogleMapsOverlayRuntime.cjs.js'); })
|
|
64
|
+
.then(module => module.createDeckGlGoogleMapsOverlayRuntime())
|
|
65
|
+
.catch(error => {
|
|
66
|
+
runtimePromise = null;
|
|
67
|
+
throw error;
|
|
68
|
+
}));
|
|
69
|
+
return runtimePromise;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Owns a single deck.gl `GoogleMapsOverlay` on a Google map.
|
|
74
|
+
*
|
|
75
|
+
* Callers pass deck.gl `Layer` instances via {@link setLayers}; the coordinator merges them
|
|
76
|
+
* into one overlay. Additional adapter capabilities can contribute layers here later.
|
|
77
|
+
*/
|
|
78
|
+
class DeckGlGoogleMapsOverlayCoordinator {
|
|
79
|
+
constructor(loadOverlayRuntime = loadDeckGlGoogleMapsOverlayRuntime) {
|
|
80
|
+
this.loadOverlayRuntime = loadOverlayRuntime;
|
|
81
|
+
this.map = null;
|
|
82
|
+
this.overlay = null;
|
|
83
|
+
this.pendingLayers = [];
|
|
84
|
+
this.runtime = null;
|
|
85
|
+
this.loading = null;
|
|
86
|
+
this.overlayCreation = null;
|
|
87
|
+
}
|
|
88
|
+
/** Associates the coordinator with a map and applies any pending layers. */
|
|
89
|
+
connect(map) {
|
|
90
|
+
const isReconnection = this.map !== null && this.map !== map;
|
|
91
|
+
this.map = map;
|
|
92
|
+
if (isReconnection) {
|
|
93
|
+
this.overlay?.setMap(map);
|
|
94
|
+
}
|
|
95
|
+
void this.syncOverlay();
|
|
96
|
+
}
|
|
97
|
+
/** Starts loading the deck.gl overlay runtime without creating an overlay. */
|
|
98
|
+
prepare() {
|
|
99
|
+
return this.loadRuntime().then(() => undefined);
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Replaces all deck.gl layers on the shared overlay.
|
|
103
|
+
*
|
|
104
|
+
* An empty array tears down the overlay until layers are set again.
|
|
105
|
+
*/
|
|
106
|
+
setLayers(layers) {
|
|
107
|
+
this.pendingLayers = layers;
|
|
108
|
+
if (layers.length === 0) {
|
|
109
|
+
this.overlay?.finalize();
|
|
110
|
+
this.overlay = null;
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
void this.syncOverlay();
|
|
114
|
+
}
|
|
115
|
+
/** Finalizes the overlay and clears coordinator state. */
|
|
116
|
+
destroy() {
|
|
117
|
+
this.pendingLayers = [];
|
|
118
|
+
this.overlay?.finalize();
|
|
119
|
+
this.overlay = null;
|
|
120
|
+
this.map = null;
|
|
121
|
+
this.loading = null;
|
|
122
|
+
}
|
|
123
|
+
syncOverlay() {
|
|
124
|
+
if (this.map === null || this.pendingLayers.length === 0)
|
|
125
|
+
return;
|
|
126
|
+
if (this.overlay !== null) {
|
|
127
|
+
this.overlay.setLayers(this.pendingLayers);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
if (this.overlayCreation !== null)
|
|
131
|
+
return;
|
|
132
|
+
this.overlayCreation = this.loadRuntime()
|
|
133
|
+
.then(runtime => {
|
|
134
|
+
if (this.map === null || this.pendingLayers.length === 0)
|
|
135
|
+
return;
|
|
136
|
+
this.overlay = runtime.createOverlay(this.map);
|
|
137
|
+
this.overlay.setLayers(this.pendingLayers);
|
|
138
|
+
})
|
|
139
|
+
.catch(() => undefined)
|
|
140
|
+
.finally(() => {
|
|
141
|
+
this.overlayCreation = null;
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
loadRuntime() {
|
|
145
|
+
if (this.runtime !== null)
|
|
146
|
+
return Promise.resolve(this.runtime);
|
|
147
|
+
if (this.loading !== null)
|
|
148
|
+
return this.loading;
|
|
149
|
+
this.loading = this.loadOverlayRuntime().then(runtime => {
|
|
150
|
+
this.runtime = runtime;
|
|
151
|
+
this.loading = null;
|
|
152
|
+
return runtime;
|
|
153
|
+
}, error => {
|
|
154
|
+
this.loading = null;
|
|
155
|
+
throw error;
|
|
156
|
+
});
|
|
157
|
+
return this.loading;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Internal deck.gl lifecycle for the Google adapter (not a {@link LayerPort}).
|
|
163
|
+
*
|
|
164
|
+
* Reads deck.gl-relevant layers from a {@link LayerSnapshot}, builds deck.gl `Layer`
|
|
165
|
+
* instances, and pushes them through {@link DeckGlGoogleMapsOverlayCoordinator}.
|
|
166
|
+
* Heatmap is supported today; additional layer types can extend `buildDeckGlLayers`.
|
|
167
|
+
*/
|
|
168
|
+
class DeckGlGoogleMapsLifecycle {
|
|
169
|
+
constructor(overlayCoordinator = new DeckGlGoogleMapsOverlayCoordinator(), loadHeatmapLayerBuilder = () => Promise.resolve().then(function () { return require('./buildDeckGlHeatmapLayer.cjs.js'); })) {
|
|
170
|
+
this.overlayCoordinator = overlayCoordinator;
|
|
171
|
+
this.loadHeatmapLayerBuilder = loadHeatmapLayerBuilder;
|
|
172
|
+
this.map = null;
|
|
173
|
+
this.heatmapConfigs = [];
|
|
174
|
+
this.heatmapLayers = new Map();
|
|
175
|
+
this.heatmapLayerBuilderModule = null;
|
|
176
|
+
this.syncing = null;
|
|
177
|
+
}
|
|
178
|
+
/** Binds to the map and syncs if snapshot state is already held. */
|
|
179
|
+
connect(map) {
|
|
180
|
+
this.map = map;
|
|
181
|
+
this.overlayCoordinator.connect(map);
|
|
182
|
+
void this.sync();
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Applies deck.gl layers derived from the snapshot.
|
|
186
|
+
*
|
|
187
|
+
* Only layers rendered via deck.gl are considered; native Google layers stay on
|
|
188
|
+
* {@link GoogleMapsLayerPort}.
|
|
189
|
+
*/
|
|
190
|
+
applySnapshot(snapshot) {
|
|
191
|
+
this.heatmapConfigs = snapshot.layers.filter(layer => layer.layerType === "heatmap");
|
|
192
|
+
void this.sync();
|
|
193
|
+
}
|
|
194
|
+
/** Detaches from the map and releases the shared deck.gl overlay. */
|
|
195
|
+
destroy() {
|
|
196
|
+
this.map = null;
|
|
197
|
+
this.heatmapConfigs = [];
|
|
198
|
+
this.heatmapLayers.clear();
|
|
199
|
+
this.syncing = null;
|
|
200
|
+
this.overlayCoordinator.destroy();
|
|
201
|
+
}
|
|
202
|
+
sync() {
|
|
203
|
+
if (this.map === null)
|
|
204
|
+
return;
|
|
205
|
+
if (this.heatmapConfigs.length === 0) {
|
|
206
|
+
this.heatmapLayers.clear();
|
|
207
|
+
this.overlayCoordinator.setLayers([]);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
if (this.heatmapLayerBuilderModule !== null) {
|
|
211
|
+
this.applyCurrentLayers(this.heatmapLayerBuilderModule);
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
if (this.syncing !== null)
|
|
215
|
+
return;
|
|
216
|
+
this.syncing = Promise.all([this.loadHeatmapLayerBuilder(), this.overlayCoordinator.prepare()])
|
|
217
|
+
.then(([module]) => {
|
|
218
|
+
this.heatmapLayerBuilderModule = module;
|
|
219
|
+
this.syncing = null;
|
|
220
|
+
if (this.map === null)
|
|
221
|
+
return;
|
|
222
|
+
this.applyCurrentLayers(module);
|
|
223
|
+
})
|
|
224
|
+
.catch(() => {
|
|
225
|
+
this.syncing = null;
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
applyCurrentLayers(module) {
|
|
229
|
+
const currentIds = new Set(this.heatmapConfigs.map(config => config.id));
|
|
230
|
+
const layers = this.heatmapConfigs.map(config => {
|
|
231
|
+
const tracked = this.heatmapLayers.get(config.id);
|
|
232
|
+
if (tracked !== undefined && esToolkit.isEqual(tracked.config, config)) {
|
|
233
|
+
return tracked.layer;
|
|
234
|
+
}
|
|
235
|
+
const layer = module.buildDeckGlHeatmapLayer(config);
|
|
236
|
+
this.heatmapLayers.set(config.id, { config, layer });
|
|
237
|
+
return layer;
|
|
238
|
+
});
|
|
239
|
+
for (const id of this.heatmapLayers.keys()) {
|
|
240
|
+
if (!currentIds.has(id)) {
|
|
241
|
+
this.heatmapLayers.delete(id);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
this.overlayCoordinator.setLayers(layers);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
58
248
|
/**
|
|
59
249
|
* Zod schema for Google Maps LatLngLiteral
|
|
60
250
|
* A plain object with lat and lng number properties
|
|
@@ -163,7 +353,7 @@ const DOM_MARKER_Z_INDEX = 1000;
|
|
|
163
353
|
* any sources can be rendered. Sources set before connect are queued.
|
|
164
354
|
*/
|
|
165
355
|
class GoogleMapsLayerPort {
|
|
166
|
-
constructor() {
|
|
356
|
+
constructor(deckGlGoogleMapsLifecycle = new DeckGlGoogleMapsLifecycle()) {
|
|
167
357
|
/**
|
|
168
358
|
* Marker portal subscription compatible with `useSyncExternalStore`.
|
|
169
359
|
* `<Layers>` routes descriptors from this store to `markerRender`. Includes
|
|
@@ -269,6 +459,7 @@ class GoogleMapsLayerPort {
|
|
|
269
459
|
this.mapDblClickListenerCleanup = null;
|
|
270
460
|
// Consumer-injected shape style defaults (set via setShapeStyleDefaults before first snapshot)
|
|
271
461
|
this._shapeStyleDefaults = undefined;
|
|
462
|
+
this.deckGlGoogleMapsLifecycle = deckGlGoogleMapsLifecycle;
|
|
272
463
|
}
|
|
273
464
|
get shapeStyleDefaults() {
|
|
274
465
|
if (this._shapeStyleDefaults === undefined) {
|
|
@@ -290,6 +481,7 @@ class GoogleMapsLayerPort {
|
|
|
290
481
|
// re-render all existing sources on the new map
|
|
291
482
|
const isReconnection = this.map !== null && this.map !== map;
|
|
292
483
|
this.map = map;
|
|
484
|
+
this.deckGlGoogleMapsLifecycle.connect(map);
|
|
293
485
|
this.setupMapContainerDblClickListener(map);
|
|
294
486
|
this.setupMapClickListener(map);
|
|
295
487
|
if (isReconnection) {
|
|
@@ -389,6 +581,7 @@ class GoogleMapsLayerPort {
|
|
|
389
581
|
for (const id of [...this.overlaySources.keys()]) {
|
|
390
582
|
this.removeImageOverlayInternal(id);
|
|
391
583
|
}
|
|
584
|
+
this.deckGlGoogleMapsLifecycle.destroy();
|
|
392
585
|
this.interactionHandlers.clear();
|
|
393
586
|
this.backgroundClickHandlers.clear();
|
|
394
587
|
this.markerPortalDescriptors = [];
|
|
@@ -413,6 +606,7 @@ class GoogleMapsLayerPort {
|
|
|
413
606
|
const shapeIds = new Set(snapshot.layers.filter(l => l.layerType === "shapes").map(l => l.id));
|
|
414
607
|
const routeIds = new Set(snapshot.layers.filter(l => l.layerType === "route").map(l => l.id));
|
|
415
608
|
const overlayIds = new Set(snapshot.layers.filter(l => l.layerType === "image-overlay").map(l => l.id));
|
|
609
|
+
this.deckGlGoogleMapsLifecycle.applySnapshot(snapshot);
|
|
416
610
|
for (const layer of snapshot.layers) {
|
|
417
611
|
switch (layer.layerType) {
|
|
418
612
|
case "markers":
|
|
@@ -427,6 +621,9 @@ class GoogleMapsLayerPort {
|
|
|
427
621
|
case "image-overlay":
|
|
428
622
|
this.syncImageOverlay(layer);
|
|
429
623
|
break;
|
|
624
|
+
case "heatmap":
|
|
625
|
+
// Rendered by DeckGlGoogleMapsLifecycle (applySnapshot above); ADR-0029.
|
|
626
|
+
break;
|
|
430
627
|
default: {
|
|
431
628
|
const exhaustiveCheck = layer;
|
|
432
629
|
throw new Error(`Unknown layer type: ${String(exhaustiveCheck)}`);
|
|
@@ -2589,7 +2786,18 @@ class GoogleMapsAdapterInstance {
|
|
|
2589
2786
|
* (ADR 0028: attaching it to a vector map silently degrades the renderer).
|
|
2590
2787
|
*/
|
|
2591
2788
|
this.connectedRenderingType = "VECTOR";
|
|
2592
|
-
|
|
2789
|
+
// Seed the full appearance from config so a persisted preference (via
|
|
2790
|
+
// usePersistedAppearanceSeed in @trackunit/react-map) shapes the FIRST map:
|
|
2791
|
+
// the renderer reads theme and showTraffic from this initial state to pick
|
|
2792
|
+
// mapId and renderingType, so seeding here avoids the construct-then-recreate
|
|
2793
|
+
// blink (and double Dynamic Maps load) on mount.
|
|
2794
|
+
this.appearance = {
|
|
2795
|
+
theme: config.theme ?? reactMapAdapterShared.DEFAULT_MAP_APPEARANCE.theme,
|
|
2796
|
+
mapType: config.mapType ?? reactMapAdapterShared.DEFAULT_MAP_APPEARANCE.mapType,
|
|
2797
|
+
showRoads: config.showRoads ?? reactMapAdapterShared.DEFAULT_MAP_APPEARANCE.showRoads,
|
|
2798
|
+
// Absent = off; the core default keeps the field optional for external compatibility
|
|
2799
|
+
showTraffic: config.showTraffic ?? false,
|
|
2800
|
+
};
|
|
2593
2801
|
this.layers = new GoogleMapsLayerPort();
|
|
2594
2802
|
this.layers.setTheme(this.appearance.theme);
|
|
2595
2803
|
this.state = reactMapAdapterShared.computeInitialState(this.appearance, config.initialViewport);
|
|
@@ -2649,11 +2857,17 @@ class GoogleMapsAdapterInstance {
|
|
|
2649
2857
|
this.layers.setPointerMoveReporter(position => {
|
|
2650
2858
|
this.reportPointerMove(position);
|
|
2651
2859
|
});
|
|
2652
|
-
// Restore previous viewport
|
|
2860
|
+
// Restore previous viewport after map recreation
|
|
2653
2861
|
if (previousState !== null) {
|
|
2654
2862
|
map.setCenter(positionToLatLng(previousState.center));
|
|
2655
2863
|
map.setZoom(previousState.zoom);
|
|
2656
|
-
|
|
2864
|
+
}
|
|
2865
|
+
// Apply the effective map type on every connect: vis.gl creates each map with
|
|
2866
|
+
// Google's default (roadmap), so a seeded satellite/hybrid appearance must be
|
|
2867
|
+
// applied on first connect too, not only restored after recreations.
|
|
2868
|
+
const effectiveMapTypeId = this.mapTypeToGoogleMapTypeId(this.appearance.mapType, this.appearance.showRoads);
|
|
2869
|
+
if (map.getMapTypeId() !== effectiveMapTypeId) {
|
|
2870
|
+
map.setMapTypeId(effectiveMapTypeId);
|
|
2657
2871
|
}
|
|
2658
2872
|
if (!this.state.isReady || this.state.initializationFailed) {
|
|
2659
2873
|
// Successful connect clears a prior init failure so recovery is possible (e.g. API load failed then
|
package/index.esm.js
CHANGED
|
@@ -54,6 +54,196 @@ const GoogleApiProvider = ({ apiKey, language, children }) => {
|
|
|
54
54
|
return (jsx(APIProvider, { apiKey: apiKey, language: language, libraries: libraries, version: GOOGLE_MAPS_API_VERSION, children: children }));
|
|
55
55
|
};
|
|
56
56
|
|
|
57
|
+
let runtimePromise = null;
|
|
58
|
+
/**
|
|
59
|
+
* Lazily dynamic-imports {@link createDeckGlGoogleMapsOverlayRuntime} and caches the promise.
|
|
60
|
+
*/
|
|
61
|
+
const loadDeckGlGoogleMapsOverlayRuntime = () => {
|
|
62
|
+
runtimePromise ?? (runtimePromise = import('./createDeckGlGoogleMapsOverlayRuntime.esm.js')
|
|
63
|
+
.then(module => module.createDeckGlGoogleMapsOverlayRuntime())
|
|
64
|
+
.catch(error => {
|
|
65
|
+
runtimePromise = null;
|
|
66
|
+
throw error;
|
|
67
|
+
}));
|
|
68
|
+
return runtimePromise;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Owns a single deck.gl `GoogleMapsOverlay` on a Google map.
|
|
73
|
+
*
|
|
74
|
+
* Callers pass deck.gl `Layer` instances via {@link setLayers}; the coordinator merges them
|
|
75
|
+
* into one overlay. Additional adapter capabilities can contribute layers here later.
|
|
76
|
+
*/
|
|
77
|
+
class DeckGlGoogleMapsOverlayCoordinator {
|
|
78
|
+
constructor(loadOverlayRuntime = loadDeckGlGoogleMapsOverlayRuntime) {
|
|
79
|
+
this.loadOverlayRuntime = loadOverlayRuntime;
|
|
80
|
+
this.map = null;
|
|
81
|
+
this.overlay = null;
|
|
82
|
+
this.pendingLayers = [];
|
|
83
|
+
this.runtime = null;
|
|
84
|
+
this.loading = null;
|
|
85
|
+
this.overlayCreation = null;
|
|
86
|
+
}
|
|
87
|
+
/** Associates the coordinator with a map and applies any pending layers. */
|
|
88
|
+
connect(map) {
|
|
89
|
+
const isReconnection = this.map !== null && this.map !== map;
|
|
90
|
+
this.map = map;
|
|
91
|
+
if (isReconnection) {
|
|
92
|
+
this.overlay?.setMap(map);
|
|
93
|
+
}
|
|
94
|
+
void this.syncOverlay();
|
|
95
|
+
}
|
|
96
|
+
/** Starts loading the deck.gl overlay runtime without creating an overlay. */
|
|
97
|
+
prepare() {
|
|
98
|
+
return this.loadRuntime().then(() => undefined);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Replaces all deck.gl layers on the shared overlay.
|
|
102
|
+
*
|
|
103
|
+
* An empty array tears down the overlay until layers are set again.
|
|
104
|
+
*/
|
|
105
|
+
setLayers(layers) {
|
|
106
|
+
this.pendingLayers = layers;
|
|
107
|
+
if (layers.length === 0) {
|
|
108
|
+
this.overlay?.finalize();
|
|
109
|
+
this.overlay = null;
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
void this.syncOverlay();
|
|
113
|
+
}
|
|
114
|
+
/** Finalizes the overlay and clears coordinator state. */
|
|
115
|
+
destroy() {
|
|
116
|
+
this.pendingLayers = [];
|
|
117
|
+
this.overlay?.finalize();
|
|
118
|
+
this.overlay = null;
|
|
119
|
+
this.map = null;
|
|
120
|
+
this.loading = null;
|
|
121
|
+
}
|
|
122
|
+
syncOverlay() {
|
|
123
|
+
if (this.map === null || this.pendingLayers.length === 0)
|
|
124
|
+
return;
|
|
125
|
+
if (this.overlay !== null) {
|
|
126
|
+
this.overlay.setLayers(this.pendingLayers);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
if (this.overlayCreation !== null)
|
|
130
|
+
return;
|
|
131
|
+
this.overlayCreation = this.loadRuntime()
|
|
132
|
+
.then(runtime => {
|
|
133
|
+
if (this.map === null || this.pendingLayers.length === 0)
|
|
134
|
+
return;
|
|
135
|
+
this.overlay = runtime.createOverlay(this.map);
|
|
136
|
+
this.overlay.setLayers(this.pendingLayers);
|
|
137
|
+
})
|
|
138
|
+
.catch(() => undefined)
|
|
139
|
+
.finally(() => {
|
|
140
|
+
this.overlayCreation = null;
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
loadRuntime() {
|
|
144
|
+
if (this.runtime !== null)
|
|
145
|
+
return Promise.resolve(this.runtime);
|
|
146
|
+
if (this.loading !== null)
|
|
147
|
+
return this.loading;
|
|
148
|
+
this.loading = this.loadOverlayRuntime().then(runtime => {
|
|
149
|
+
this.runtime = runtime;
|
|
150
|
+
this.loading = null;
|
|
151
|
+
return runtime;
|
|
152
|
+
}, error => {
|
|
153
|
+
this.loading = null;
|
|
154
|
+
throw error;
|
|
155
|
+
});
|
|
156
|
+
return this.loading;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Internal deck.gl lifecycle for the Google adapter (not a {@link LayerPort}).
|
|
162
|
+
*
|
|
163
|
+
* Reads deck.gl-relevant layers from a {@link LayerSnapshot}, builds deck.gl `Layer`
|
|
164
|
+
* instances, and pushes them through {@link DeckGlGoogleMapsOverlayCoordinator}.
|
|
165
|
+
* Heatmap is supported today; additional layer types can extend `buildDeckGlLayers`.
|
|
166
|
+
*/
|
|
167
|
+
class DeckGlGoogleMapsLifecycle {
|
|
168
|
+
constructor(overlayCoordinator = new DeckGlGoogleMapsOverlayCoordinator(), loadHeatmapLayerBuilder = () => import('./buildDeckGlHeatmapLayer.esm.js')) {
|
|
169
|
+
this.overlayCoordinator = overlayCoordinator;
|
|
170
|
+
this.loadHeatmapLayerBuilder = loadHeatmapLayerBuilder;
|
|
171
|
+
this.map = null;
|
|
172
|
+
this.heatmapConfigs = [];
|
|
173
|
+
this.heatmapLayers = new Map();
|
|
174
|
+
this.heatmapLayerBuilderModule = null;
|
|
175
|
+
this.syncing = null;
|
|
176
|
+
}
|
|
177
|
+
/** Binds to the map and syncs if snapshot state is already held. */
|
|
178
|
+
connect(map) {
|
|
179
|
+
this.map = map;
|
|
180
|
+
this.overlayCoordinator.connect(map);
|
|
181
|
+
void this.sync();
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Applies deck.gl layers derived from the snapshot.
|
|
185
|
+
*
|
|
186
|
+
* Only layers rendered via deck.gl are considered; native Google layers stay on
|
|
187
|
+
* {@link GoogleMapsLayerPort}.
|
|
188
|
+
*/
|
|
189
|
+
applySnapshot(snapshot) {
|
|
190
|
+
this.heatmapConfigs = snapshot.layers.filter(layer => layer.layerType === "heatmap");
|
|
191
|
+
void this.sync();
|
|
192
|
+
}
|
|
193
|
+
/** Detaches from the map and releases the shared deck.gl overlay. */
|
|
194
|
+
destroy() {
|
|
195
|
+
this.map = null;
|
|
196
|
+
this.heatmapConfigs = [];
|
|
197
|
+
this.heatmapLayers.clear();
|
|
198
|
+
this.syncing = null;
|
|
199
|
+
this.overlayCoordinator.destroy();
|
|
200
|
+
}
|
|
201
|
+
sync() {
|
|
202
|
+
if (this.map === null)
|
|
203
|
+
return;
|
|
204
|
+
if (this.heatmapConfigs.length === 0) {
|
|
205
|
+
this.heatmapLayers.clear();
|
|
206
|
+
this.overlayCoordinator.setLayers([]);
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
if (this.heatmapLayerBuilderModule !== null) {
|
|
210
|
+
this.applyCurrentLayers(this.heatmapLayerBuilderModule);
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
if (this.syncing !== null)
|
|
214
|
+
return;
|
|
215
|
+
this.syncing = Promise.all([this.loadHeatmapLayerBuilder(), this.overlayCoordinator.prepare()])
|
|
216
|
+
.then(([module]) => {
|
|
217
|
+
this.heatmapLayerBuilderModule = module;
|
|
218
|
+
this.syncing = null;
|
|
219
|
+
if (this.map === null)
|
|
220
|
+
return;
|
|
221
|
+
this.applyCurrentLayers(module);
|
|
222
|
+
})
|
|
223
|
+
.catch(() => {
|
|
224
|
+
this.syncing = null;
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
applyCurrentLayers(module) {
|
|
228
|
+
const currentIds = new Set(this.heatmapConfigs.map(config => config.id));
|
|
229
|
+
const layers = this.heatmapConfigs.map(config => {
|
|
230
|
+
const tracked = this.heatmapLayers.get(config.id);
|
|
231
|
+
if (tracked !== undefined && isEqual(tracked.config, config)) {
|
|
232
|
+
return tracked.layer;
|
|
233
|
+
}
|
|
234
|
+
const layer = module.buildDeckGlHeatmapLayer(config);
|
|
235
|
+
this.heatmapLayers.set(config.id, { config, layer });
|
|
236
|
+
return layer;
|
|
237
|
+
});
|
|
238
|
+
for (const id of this.heatmapLayers.keys()) {
|
|
239
|
+
if (!currentIds.has(id)) {
|
|
240
|
+
this.heatmapLayers.delete(id);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
this.overlayCoordinator.setLayers(layers);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
57
247
|
/**
|
|
58
248
|
* Zod schema for Google Maps LatLngLiteral
|
|
59
249
|
* A plain object with lat and lng number properties
|
|
@@ -162,7 +352,7 @@ const DOM_MARKER_Z_INDEX = 1000;
|
|
|
162
352
|
* any sources can be rendered. Sources set before connect are queued.
|
|
163
353
|
*/
|
|
164
354
|
class GoogleMapsLayerPort {
|
|
165
|
-
constructor() {
|
|
355
|
+
constructor(deckGlGoogleMapsLifecycle = new DeckGlGoogleMapsLifecycle()) {
|
|
166
356
|
/**
|
|
167
357
|
* Marker portal subscription compatible with `useSyncExternalStore`.
|
|
168
358
|
* `<Layers>` routes descriptors from this store to `markerRender`. Includes
|
|
@@ -268,6 +458,7 @@ class GoogleMapsLayerPort {
|
|
|
268
458
|
this.mapDblClickListenerCleanup = null;
|
|
269
459
|
// Consumer-injected shape style defaults (set via setShapeStyleDefaults before first snapshot)
|
|
270
460
|
this._shapeStyleDefaults = undefined;
|
|
461
|
+
this.deckGlGoogleMapsLifecycle = deckGlGoogleMapsLifecycle;
|
|
271
462
|
}
|
|
272
463
|
get shapeStyleDefaults() {
|
|
273
464
|
if (this._shapeStyleDefaults === undefined) {
|
|
@@ -289,6 +480,7 @@ class GoogleMapsLayerPort {
|
|
|
289
480
|
// re-render all existing sources on the new map
|
|
290
481
|
const isReconnection = this.map !== null && this.map !== map;
|
|
291
482
|
this.map = map;
|
|
483
|
+
this.deckGlGoogleMapsLifecycle.connect(map);
|
|
292
484
|
this.setupMapContainerDblClickListener(map);
|
|
293
485
|
this.setupMapClickListener(map);
|
|
294
486
|
if (isReconnection) {
|
|
@@ -388,6 +580,7 @@ class GoogleMapsLayerPort {
|
|
|
388
580
|
for (const id of [...this.overlaySources.keys()]) {
|
|
389
581
|
this.removeImageOverlayInternal(id);
|
|
390
582
|
}
|
|
583
|
+
this.deckGlGoogleMapsLifecycle.destroy();
|
|
391
584
|
this.interactionHandlers.clear();
|
|
392
585
|
this.backgroundClickHandlers.clear();
|
|
393
586
|
this.markerPortalDescriptors = [];
|
|
@@ -412,6 +605,7 @@ class GoogleMapsLayerPort {
|
|
|
412
605
|
const shapeIds = new Set(snapshot.layers.filter(l => l.layerType === "shapes").map(l => l.id));
|
|
413
606
|
const routeIds = new Set(snapshot.layers.filter(l => l.layerType === "route").map(l => l.id));
|
|
414
607
|
const overlayIds = new Set(snapshot.layers.filter(l => l.layerType === "image-overlay").map(l => l.id));
|
|
608
|
+
this.deckGlGoogleMapsLifecycle.applySnapshot(snapshot);
|
|
415
609
|
for (const layer of snapshot.layers) {
|
|
416
610
|
switch (layer.layerType) {
|
|
417
611
|
case "markers":
|
|
@@ -426,6 +620,9 @@ class GoogleMapsLayerPort {
|
|
|
426
620
|
case "image-overlay":
|
|
427
621
|
this.syncImageOverlay(layer);
|
|
428
622
|
break;
|
|
623
|
+
case "heatmap":
|
|
624
|
+
// Rendered by DeckGlGoogleMapsLifecycle (applySnapshot above); ADR-0029.
|
|
625
|
+
break;
|
|
429
626
|
default: {
|
|
430
627
|
const exhaustiveCheck = layer;
|
|
431
628
|
throw new Error(`Unknown layer type: ${String(exhaustiveCheck)}`);
|
|
@@ -2588,7 +2785,18 @@ class GoogleMapsAdapterInstance {
|
|
|
2588
2785
|
* (ADR 0028: attaching it to a vector map silently degrades the renderer).
|
|
2589
2786
|
*/
|
|
2590
2787
|
this.connectedRenderingType = "VECTOR";
|
|
2591
|
-
|
|
2788
|
+
// Seed the full appearance from config so a persisted preference (via
|
|
2789
|
+
// usePersistedAppearanceSeed in @trackunit/react-map) shapes the FIRST map:
|
|
2790
|
+
// the renderer reads theme and showTraffic from this initial state to pick
|
|
2791
|
+
// mapId and renderingType, so seeding here avoids the construct-then-recreate
|
|
2792
|
+
// blink (and double Dynamic Maps load) on mount.
|
|
2793
|
+
this.appearance = {
|
|
2794
|
+
theme: config.theme ?? DEFAULT_MAP_APPEARANCE.theme,
|
|
2795
|
+
mapType: config.mapType ?? DEFAULT_MAP_APPEARANCE.mapType,
|
|
2796
|
+
showRoads: config.showRoads ?? DEFAULT_MAP_APPEARANCE.showRoads,
|
|
2797
|
+
// Absent = off; the core default keeps the field optional for external compatibility
|
|
2798
|
+
showTraffic: config.showTraffic ?? false,
|
|
2799
|
+
};
|
|
2592
2800
|
this.layers = new GoogleMapsLayerPort();
|
|
2593
2801
|
this.layers.setTheme(this.appearance.theme);
|
|
2594
2802
|
this.state = computeInitialState(this.appearance, config.initialViewport);
|
|
@@ -2648,11 +2856,17 @@ class GoogleMapsAdapterInstance {
|
|
|
2648
2856
|
this.layers.setPointerMoveReporter(position => {
|
|
2649
2857
|
this.reportPointerMove(position);
|
|
2650
2858
|
});
|
|
2651
|
-
// Restore previous viewport
|
|
2859
|
+
// Restore previous viewport after map recreation
|
|
2652
2860
|
if (previousState !== null) {
|
|
2653
2861
|
map.setCenter(positionToLatLng(previousState.center));
|
|
2654
2862
|
map.setZoom(previousState.zoom);
|
|
2655
|
-
|
|
2863
|
+
}
|
|
2864
|
+
// Apply the effective map type on every connect: vis.gl creates each map with
|
|
2865
|
+
// Google's default (roadmap), so a seeded satellite/hybrid appearance must be
|
|
2866
|
+
// applied on first connect too, not only restored after recreations.
|
|
2867
|
+
const effectiveMapTypeId = this.mapTypeToGoogleMapTypeId(this.appearance.mapType, this.appearance.showRoads);
|
|
2868
|
+
if (map.getMapTypeId() !== effectiveMapTypeId) {
|
|
2869
|
+
map.setMapTypeId(effectiveMapTypeId);
|
|
2656
2870
|
}
|
|
2657
2871
|
if (!this.state.isReady || this.state.initializationFailed) {
|
|
2658
2872
|
// Successful connect clears a prior init failure so recovery is possible (e.g. API load failed then
|
package/package.json
CHANGED
|
@@ -1,15 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trackunit/react-map-adapter-google",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.133",
|
|
4
4
|
"repository": "https://github.com/Trackunit/manager",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE.txt",
|
|
6
6
|
"engines": {
|
|
7
7
|
"node": ">=24.x"
|
|
8
8
|
},
|
|
9
9
|
"dependencies": {
|
|
10
|
-
"@
|
|
11
|
-
"@
|
|
12
|
-
"@
|
|
10
|
+
"@deck.gl/aggregation-layers": "^9.4.0",
|
|
11
|
+
"@deck.gl/core": "^9.4.0",
|
|
12
|
+
"@deck.gl/google-maps": "^9.4.0",
|
|
13
|
+
"@deck.gl/layers": "^9.4.0",
|
|
14
|
+
"@loaders.gl/core": "~4.5.2",
|
|
15
|
+
"@luma.gl/core": "^9.4.1",
|
|
16
|
+
"@luma.gl/engine": "^9.4.1",
|
|
17
|
+
"@trackunit/react-map-adapter-shared": "0.0.144",
|
|
18
|
+
"@trackunit/react-map-color-utils": "0.0.119",
|
|
19
|
+
"@trackunit/geo-json-utils": "1.16.3",
|
|
13
20
|
"@googlemaps/markerclusterer": "^2.6.2",
|
|
14
21
|
"@vis.gl/react-google-maps": "^1.7.1",
|
|
15
22
|
"es-toolkit": "^1.39.10",
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type GeoJsonPosition } from "@trackunit/geo-json-utils";
|
|
2
2
|
import { type DomPortalStore, type EntityInteractionHandler, type LayerPort, type LayerSnapshot, type MapTheme, type ShapeStyleDefaults } from "@trackunit/react-map-adapter-shared";
|
|
3
|
+
import { DeckGlGoogleMapsLifecycle } from "./deckGl/DeckGlGoogleMapsLifecycle";
|
|
3
4
|
/**
|
|
4
5
|
* Google Maps implementation of the LayerPort interface.
|
|
5
6
|
*
|
|
@@ -77,6 +78,8 @@ export declare class GoogleMapsLayerPort implements LayerPort {
|
|
|
77
78
|
private holdNativeZoomTimer;
|
|
78
79
|
private mapDblClickListenerCleanup;
|
|
79
80
|
private _shapeStyleDefaults;
|
|
81
|
+
private readonly deckGlGoogleMapsLifecycle;
|
|
82
|
+
constructor(deckGlGoogleMapsLifecycle?: DeckGlGoogleMapsLifecycle);
|
|
80
83
|
private get shapeStyleDefaults();
|
|
81
84
|
setShapeStyleDefaults(defaults: ShapeStyleDefaults): void;
|
|
82
85
|
/**
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { Layer } from "@deck.gl/core";
|
|
2
|
+
import type { HeatmapSourceConfig, LayerSnapshot } from "@trackunit/react-map-adapter-shared";
|
|
3
|
+
import { DeckGlGoogleMapsOverlayCoordinator } from "./DeckGlGoogleMapsOverlayCoordinator";
|
|
4
|
+
type HeatmapLayerBuilderModule = Readonly<{
|
|
5
|
+
buildDeckGlHeatmapLayer: (config: HeatmapSourceConfig) => Layer;
|
|
6
|
+
}>;
|
|
7
|
+
type LoadHeatmapLayerBuilder = () => Promise<HeatmapLayerBuilderModule>;
|
|
8
|
+
/**
|
|
9
|
+
* Internal deck.gl lifecycle for the Google adapter (not a {@link LayerPort}).
|
|
10
|
+
*
|
|
11
|
+
* Reads deck.gl-relevant layers from a {@link LayerSnapshot}, builds deck.gl `Layer`
|
|
12
|
+
* instances, and pushes them through {@link DeckGlGoogleMapsOverlayCoordinator}.
|
|
13
|
+
* Heatmap is supported today; additional layer types can extend `buildDeckGlLayers`.
|
|
14
|
+
*/
|
|
15
|
+
export declare class DeckGlGoogleMapsLifecycle {
|
|
16
|
+
private readonly overlayCoordinator;
|
|
17
|
+
private readonly loadHeatmapLayerBuilder;
|
|
18
|
+
private map;
|
|
19
|
+
private heatmapConfigs;
|
|
20
|
+
private readonly heatmapLayers;
|
|
21
|
+
private heatmapLayerBuilderModule;
|
|
22
|
+
private syncing;
|
|
23
|
+
constructor(overlayCoordinator?: DeckGlGoogleMapsOverlayCoordinator, loadHeatmapLayerBuilder?: LoadHeatmapLayerBuilder);
|
|
24
|
+
/** Binds to the map and syncs if snapshot state is already held. */
|
|
25
|
+
connect(map: google.maps.Map): void;
|
|
26
|
+
/**
|
|
27
|
+
* Applies deck.gl layers derived from the snapshot.
|
|
28
|
+
*
|
|
29
|
+
* Only layers rendered via deck.gl are considered; native Google layers stay on
|
|
30
|
+
* {@link GoogleMapsLayerPort}.
|
|
31
|
+
*/
|
|
32
|
+
applySnapshot(snapshot: LayerSnapshot): void;
|
|
33
|
+
/** Detaches from the map and releases the shared deck.gl overlay. */
|
|
34
|
+
destroy(): void;
|
|
35
|
+
private sync;
|
|
36
|
+
private applyCurrentLayers;
|
|
37
|
+
}
|
|
38
|
+
export {};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { Layer } from "@deck.gl/core";
|
|
2
|
+
import type { DeckGlGoogleMapsOverlayRuntime } from "./createDeckGlGoogleMapsOverlayRuntime";
|
|
3
|
+
export type LoadDeckGlGoogleMapsOverlayRuntime = () => Promise<DeckGlGoogleMapsOverlayRuntime>;
|
|
4
|
+
/**
|
|
5
|
+
* Owns a single deck.gl `GoogleMapsOverlay` on a Google map.
|
|
6
|
+
*
|
|
7
|
+
* Callers pass deck.gl `Layer` instances via {@link setLayers}; the coordinator merges them
|
|
8
|
+
* into one overlay. Additional adapter capabilities can contribute layers here later.
|
|
9
|
+
*/
|
|
10
|
+
export declare class DeckGlGoogleMapsOverlayCoordinator {
|
|
11
|
+
private readonly loadOverlayRuntime;
|
|
12
|
+
private map;
|
|
13
|
+
private overlay;
|
|
14
|
+
private pendingLayers;
|
|
15
|
+
private runtime;
|
|
16
|
+
private loading;
|
|
17
|
+
private overlayCreation;
|
|
18
|
+
constructor(loadOverlayRuntime?: LoadDeckGlGoogleMapsOverlayRuntime);
|
|
19
|
+
/** Associates the coordinator with a map and applies any pending layers. */
|
|
20
|
+
connect(map: google.maps.Map): void;
|
|
21
|
+
/** Starts loading the deck.gl overlay runtime without creating an overlay. */
|
|
22
|
+
prepare(): Promise<void>;
|
|
23
|
+
/**
|
|
24
|
+
* Replaces all deck.gl layers on the shared overlay.
|
|
25
|
+
*
|
|
26
|
+
* An empty array tears down the overlay until layers are set again.
|
|
27
|
+
*/
|
|
28
|
+
setLayers(layers: ReadonlyArray<Layer>): void;
|
|
29
|
+
/** Finalizes the overlay and clears coordinator state. */
|
|
30
|
+
destroy(): void;
|
|
31
|
+
private syncOverlay;
|
|
32
|
+
private loadRuntime;
|
|
33
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Layer } from "@deck.gl/core";
|
|
2
|
+
/** Mutable deck.gl overlay bound to one {@link google.maps.Map}. */
|
|
3
|
+
export type DeckGlGoogleMapsOverlayHandle = Readonly<{
|
|
4
|
+
setMap: (map: google.maps.Map) => void;
|
|
5
|
+
setLayers: (layers: ReadonlyArray<Layer>) => void;
|
|
6
|
+
finalize: () => void;
|
|
7
|
+
}>;
|
|
8
|
+
/** Factory after lazy-loading `@deck.gl/google-maps`. */
|
|
9
|
+
export type DeckGlGoogleMapsOverlayRuntime = Readonly<{
|
|
10
|
+
createOverlay: (map: google.maps.Map) => DeckGlGoogleMapsOverlayHandle;
|
|
11
|
+
}>;
|
|
12
|
+
/**
|
|
13
|
+
* Creates one `GoogleMapsOverlay` per map.
|
|
14
|
+
*
|
|
15
|
+
* See https://deck.gl/docs/api-reference/google-maps/google-maps-overlay
|
|
16
|
+
*/
|
|
17
|
+
export declare const createDeckGlGoogleMapsOverlayRuntime: () => DeckGlGoogleMapsOverlayRuntime;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { DeckGlGoogleMapsOverlayRuntime } from "./createDeckGlGoogleMapsOverlayRuntime";
|
|
2
|
+
/**
|
|
3
|
+
* Lazily dynamic-imports {@link createDeckGlGoogleMapsOverlayRuntime} and caches the promise.
|
|
4
|
+
*/
|
|
5
|
+
export declare const loadDeckGlGoogleMapsOverlayRuntime: () => Promise<DeckGlGoogleMapsOverlayRuntime>;
|
|
@@ -21,6 +21,9 @@ export declare const googleMapsAdapter: (config: Readonly<Readonly<{
|
|
|
21
21
|
language?: string;
|
|
22
22
|
region?: string;
|
|
23
23
|
theme?: import("@trackunit/react-map-adapter-shared").MapTheme;
|
|
24
|
+
mapType?: import("@trackunit/react-map-adapter-shared").MapType;
|
|
25
|
+
showRoads?: boolean;
|
|
26
|
+
showTraffic?: boolean;
|
|
24
27
|
initialViewport?: import("@trackunit/react-map-adapter-shared").InitialViewport;
|
|
25
28
|
restrictBounds?: import("@trackunit/geo-json-utils").GeoJsonBbox | null;
|
|
26
29
|
}> & {
|
|
@@ -30,6 +33,9 @@ export declare const googleMapsAdapter: (config: Readonly<Readonly<{
|
|
|
30
33
|
language?: string;
|
|
31
34
|
region?: string;
|
|
32
35
|
theme?: import("@trackunit/react-map-adapter-shared").MapTheme;
|
|
36
|
+
mapType?: import("@trackunit/react-map-adapter-shared").MapType;
|
|
37
|
+
showRoads?: boolean;
|
|
38
|
+
showTraffic?: boolean;
|
|
33
39
|
initialViewport?: import("@trackunit/react-map-adapter-shared").InitialViewport;
|
|
34
40
|
restrictBounds?: import("@trackunit/geo-json-utils").GeoJsonBbox | null;
|
|
35
41
|
}> & {
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { HeatmapLayer } from "@deck.gl/aggregation-layers";
|
|
2
|
+
import type { GeoJsonFeature, HeatmapSourceConfig } from "@trackunit/react-map-adapter-shared";
|
|
3
|
+
/**
|
|
4
|
+
* Maps {@link HeatmapSourceConfig} → deck.gl `HeatmapLayer` on Google Maps.
|
|
5
|
+
*
|
|
6
|
+
* Invoked from {@link DeckGlGoogleMapsLifecycle} after dynamic import.
|
|
7
|
+
*/
|
|
8
|
+
export declare const buildDeckGlHeatmapLayer: (config: HeatmapSourceConfig) => HeatmapLayer<GeoJsonFeature>;
|