isard 1.16.0 → 2.1.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.
- package/CHANGELOG.md +80 -0
- package/README.md +5 -5
- package/dist/basemap/vectortiles/placed.js +7 -4
- package/dist/basemap/vectortiles/placed.js.map +1 -1
- package/dist/basemap/vectortiles/tiles.d.ts +1 -1
- package/dist/basemap/vectortiles/tiles.js.map +1 -1
- package/dist/basemap/vectortiles/vectors.d.ts +3 -1
- package/dist/basemap/vectortiles/vectors.js +2 -0
- package/dist/basemap/vectortiles/vectors.js.map +1 -1
- package/dist/basemap/vectortiles.d.ts +1 -3
- package/dist/basemap/vectortiles.js +1 -3
- package/dist/basemap/vectortiles.js.map +1 -1
- package/dist/basemap/workerpool.d.ts +1 -1
- package/dist/basemap/workerpool.js.map +1 -1
- package/dist/bind/layers.js +2 -2
- package/dist/bind/layers.js.map +1 -1
- package/dist/decoder.worker.js +4 -5
- package/dist/decoder.worker.js.map +6 -6
- package/dist/decoding/decoders.d.ts +1 -1
- package/dist/decoding/decoders.js.map +1 -1
- package/dist/decoding/loader.d.ts +6 -3
- package/dist/decoding/loader.js +7 -5
- package/dist/decoding/loader.js.map +1 -1
- package/dist/decoding/protocol.d.ts +3 -2
- package/dist/decoding/protocol.js.map +1 -1
- package/dist/engine-loader.d.ts +20 -18
- package/dist/engine-loader.js +45 -42
- package/dist/engine-loader.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js.map +1 -1
- package/dist/isard_grid_bg.wasm +0 -0
- package/dist/isard_tess_bg.wasm +0 -0
- package/dist/isard_tiles3d_bg.wasm +0 -0
- package/dist/isard_webgl2_bg.wasm +0 -0
- package/dist/isard_webgpu_bg.wasm +0 -0
- package/dist/layers/custom.d.ts +35 -29
- package/dist/layers/custom.js +60 -46
- package/dist/layers/custom.js.map +1 -1
- package/dist/layers/extrusions.d.ts +6 -1
- package/dist/layers/extrusions.js +15 -4
- package/dist/layers/extrusions.js.map +1 -1
- package/dist/layers/features.js.map +1 -1
- package/dist/layers/flow.js.map +1 -1
- package/dist/layers/glyphs.d.ts +1 -1
- package/dist/layers/glyphs.js.map +1 -1
- package/dist/layers/ground.d.ts +21 -14
- package/dist/layers/ground.js +30 -9
- package/dist/layers/ground.js.map +1 -1
- package/dist/layers/icons.d.ts +1 -1
- package/dist/layers/icons.js.map +1 -1
- package/dist/layers/layers.d.ts +1 -2
- package/dist/layers/layers.js +3 -12
- package/dist/layers/layers.js.map +1 -1
- package/dist/layers/models.d.ts +7 -1
- package/dist/layers/models.js +19 -5
- package/dist/layers/models.js.map +1 -1
- package/dist/layers/network.d.ts +1 -1
- package/dist/layers/network.js.map +1 -1
- package/dist/layers/panoramas.d.ts +1 -0
- package/dist/layers/panoramas.js +3 -2
- package/dist/layers/panoramas.js.map +1 -1
- package/dist/layers/pointclouds.d.ts +3 -3
- package/dist/layers/pointclouds.js +3 -7
- package/dist/layers/pointclouds.js.map +1 -1
- package/dist/layers/raster.js +5 -0
- package/dist/layers/raster.js.map +1 -1
- package/dist/layers/symbols.js.map +1 -1
- package/dist/layers/tiles3d.d.ts +5 -7
- package/dist/layers/tiles3d.js +9 -18
- package/dist/layers/tiles3d.js.map +1 -1
- package/dist/layers/vectors.d.ts +10 -5
- package/dist/layers/vectors.js +25 -16
- package/dist/layers/vectors.js.map +1 -1
- package/dist/map/engine-settings.d.ts +5 -2
- package/dist/map/engine-settings.js +13 -2
- package/dist/map/engine-settings.js.map +1 -1
- package/dist/map/engines.d.ts +11 -26
- package/dist/map/engines.js +10 -48
- package/dist/map/engines.js.map +1 -1
- package/dist/map/frame-loop.d.ts +1 -3
- package/dist/map/frame-loop.js +3 -3
- package/dist/map/frame-loop.js.map +1 -1
- package/dist/map/hover-pick.d.ts +9 -3
- package/dist/map/hover-pick.js +29 -6
- package/dist/map/hover-pick.js.map +1 -1
- package/dist/map/layer-stack.d.ts +7 -8
- package/dist/map/layer-stack.js +6 -9
- package/dist/map/layer-stack.js.map +1 -1
- package/dist/map/symbol-atlases.d.ts +5 -4
- package/dist/map/symbol-atlases.js +8 -13
- package/dist/map/symbol-atlases.js.map +1 -1
- package/dist/map/viewshed.d.ts +1 -1
- package/dist/map/viewshed.js.map +1 -1
- package/dist/map.d.ts +45 -26
- package/dist/map.js +141 -90
- package/dist/map.js.map +1 -1
- package/dist/react/IsardMap.d.ts +1 -1
- package/dist/react/IsardMap.js.map +1 -1
- package/dist/react/VectorGeometry.d.ts +4 -1
- package/dist/react/VectorGeometry.js +5 -1
- package/dist/react/VectorGeometry.js.map +1 -1
- package/dist/react/index.d.ts +1 -1
- package/dist/react/index.js.map +1 -1
- package/dist/settings.d.ts +2 -0
- package/dist/settings.js +1 -1
- package/dist/settings.js.map +1 -1
- package/dist/types.d.ts +52 -22
- package/dist/types.js.map +1 -1
- package/dist/ui/interaction.d.ts +1 -1
- package/dist/ui/interaction.js.map +1 -1
- package/dist/util/animation.d.ts +1 -1
- package/dist/util/animation.js.map +1 -1
- package/dist/wasm/isard_tiles3d.d.ts +181 -0
- package/dist/wasm/isard_tiles3d.js +387 -0
- package/dist/wasm/{isard_core.d.ts → isard_webgl2.d.ts} +387 -182
- package/dist/wasm/{isard_core.js → isard_webgl2.js} +2087 -324
- package/dist/wasm/{isard.d.ts → isard_webgpu.d.ts} +195 -358
- package/dist/wasm/{isard.js → isard_webgpu.js} +1320 -1042
- package/package.json +4 -3
- package/vite.js +5 -4
- package/dist/isard_bg.wasm +0 -0
- package/dist/isard_core_bg.wasm +0 -0
package/dist/map.js
CHANGED
|
@@ -9,7 +9,7 @@ import { SEA } from "./basemap/basemaps.js";
|
|
|
9
9
|
import { pmtilesLayer } from "./basemap/pmtileslayer.js";
|
|
10
10
|
import { VectorTiles } from "./basemap/vectortiles.js";
|
|
11
11
|
import { defaultWorkerUrl } from "./basemap/workerpool.js";
|
|
12
|
-
import { destroyedEngine, engineUrl, gridUrl, loadEngine, tessUrl } from "./engine-loader.js";
|
|
12
|
+
import { destroyedEngine, engineUrl, gridUrl, loadEngine, rendererKind, tessUrl, } from "./engine-loader.js";
|
|
13
13
|
import { IGN_PRESETS } from "./geoplateforme.js";
|
|
14
14
|
import { CustomLayer } from "./layers/custom.js";
|
|
15
15
|
import { ExtrusionLayer, extrusionStyle } from "./layers/extrusions.js";
|
|
@@ -34,7 +34,7 @@ import { mapSettings, sendHeaders } from "./map/engine-settings.js";
|
|
|
34
34
|
import { MapEngines } from "./map/engines.js";
|
|
35
35
|
import { MapListeners } from "./map/events.js";
|
|
36
36
|
import { FrameLoop } from "./map/frame-loop.js";
|
|
37
|
-
import { HoverPick } from "./map/hover-pick.js";
|
|
37
|
+
import { HoverPick, pickOnce } from "./map/hover-pick.js";
|
|
38
38
|
import { addSpec } from "./map/layer-spec.js";
|
|
39
39
|
import { LayerStack } from "./map/layer-stack.js";
|
|
40
40
|
import { OverlayHost } from "./map/overlay-host.js";
|
|
@@ -67,12 +67,22 @@ const NETWORK_HALO_OPACITY = 0.11;
|
|
|
67
67
|
/** A layer whose picked features come back with their properties (lines, circles, fills, extrusions, 3D Tiles…) */
|
|
68
68
|
const hasFeatures = (layer) => 'featureAt' in layer && typeof layer.featureAt === 'function';
|
|
69
69
|
/** A map (see `GeoMap.create`); layers take it as their host */
|
|
70
|
+
/** The parts of a frame by their code in the engine's GPU timings (`Engine.gpuTimings`) */
|
|
71
|
+
const GPU_PARTS = ['backdrop', 'ground', 'layer', 'shadows', 'panorama', 'viewshed'];
|
|
72
|
+
/** Turns the GPU timing of an engine on or off: whether it times, false for an engine built without it (only a build to
|
|
73
|
+
* profile has `setGpuTiming`) */
|
|
74
|
+
const engineGpuTiming = (engine, on) => {
|
|
75
|
+
const set = Reflect.get(engine, 'setGpuTiming');
|
|
76
|
+
return typeof set === 'function' && Reflect.apply(set, engine, [on]) === true;
|
|
77
|
+
};
|
|
70
78
|
export class GeoMap {
|
|
71
79
|
canvas;
|
|
72
80
|
#engine;
|
|
81
|
+
/** The frames' parts timed on the GPU (`setGpuTiming`), again on a new engine */
|
|
82
|
+
gpuTiming = false;
|
|
73
83
|
/** The work the map waits on before it is idle (`track`) */
|
|
74
84
|
#working = 0;
|
|
75
|
-
/** The engine
|
|
85
|
+
/** The engine, made asynchronously, and the workers decoding its 3D Tiles */
|
|
76
86
|
engines;
|
|
77
87
|
/** The layers in drawing order and those kept on top */
|
|
78
88
|
stack = new LayerStack(() => this.#engine);
|
|
@@ -90,7 +100,7 @@ export class GeoMap {
|
|
|
90
100
|
tessUrl;
|
|
91
101
|
/** @internal Where the workers load the grid module from (`isard_grid_bg.wasm`) */
|
|
92
102
|
gridUrl;
|
|
93
|
-
/** The WebGL context
|
|
103
|
+
/** The GPU is lost (the WebGL context, the WebGPU device): nothing is drawn until a new engine is there */
|
|
94
104
|
lost = false;
|
|
95
105
|
gone = false;
|
|
96
106
|
listeners = new MapListeners();
|
|
@@ -124,20 +134,25 @@ export class GeoMap {
|
|
|
124
134
|
static async create(container, given = {}) {
|
|
125
135
|
// A preset under the options given
|
|
126
136
|
const options = given.preset ? { ...IGN_PRESETS[given.preset], ...given } : given;
|
|
127
|
-
// With 3D Tiles from the start when asked, or when only that engine's URL is given (as before the core engine)
|
|
128
|
-
const full = options.tiles3d === true || (options.wasmUrl !== undefined && options.coreWasmUrl === undefined);
|
|
129
137
|
// Before anything is created
|
|
130
138
|
if (options.basemap !== undefined)
|
|
131
139
|
checkBasemap(options.basemap);
|
|
132
|
-
|
|
140
|
+
// The engine on wgpu (#146): WebGPU, else wgpu's WebGL2 fallback
|
|
141
|
+
const kind = await rendererKind(options.renderer);
|
|
133
142
|
const bindings = await loadEngine(kind, engineUrl(kind, options));
|
|
134
|
-
const map = new GeoMap(container, options, bindings.Engine
|
|
135
|
-
|
|
136
|
-
|
|
143
|
+
const map = new GeoMap(container, options, bindings.Engine);
|
|
144
|
+
// Its device asked for: the map started on a placeholder meanwhile, its layers created on the engine once it is
|
|
145
|
+
// there (as after a context loss)
|
|
146
|
+
map.adopt(await map.engines.create(map.canvas, () => map.requestRender(), map.settings));
|
|
147
|
+
// A profiling script's page (`window.__isardProfile` set before isard loads): its maps, to time
|
|
148
|
+
if (Reflect.get(globalThis, '__isardProfile')) {
|
|
149
|
+
const maps = Reflect.get(globalThis, '__isardMaps');
|
|
150
|
+
Reflect.set(globalThis, '__isardMaps', [...(Array.isArray(maps) ? maps : []), map]);
|
|
151
|
+
}
|
|
137
152
|
return map;
|
|
138
153
|
}
|
|
139
|
-
constructor(container, options, engineClass
|
|
140
|
-
this.engines = new MapEngines(engineClass,
|
|
154
|
+
constructor(container, options, engineClass) {
|
|
155
|
+
this.engines = new MapEngines(engineClass, options);
|
|
141
156
|
const workerUrl = options.basemapWorkerUrl ?? defaultWorkerUrl(options.assetsUrl);
|
|
142
157
|
this.tessUrl = tessUrl(options);
|
|
143
158
|
this.gridUrl = gridUrl(options);
|
|
@@ -145,7 +160,8 @@ export class GeoMap {
|
|
|
145
160
|
this.basemaps = new BasemapLayers(this, this.stack, workerUrl, options.basemapTileSize);
|
|
146
161
|
this.container = container;
|
|
147
162
|
this.canvas = createCanvas();
|
|
148
|
-
|
|
163
|
+
// The engine takes the canvas for itself: it says how large a texture its device takes
|
|
164
|
+
this.atlases = new SymbolAtlases(() => this.#engine.maxTextureSize(), options.glyphs, () => this.#engine);
|
|
149
165
|
this.overlays = new OverlayHost(container, this.canvas, this);
|
|
150
166
|
this.earth = (options.body ?? 'earth') === 'earth';
|
|
151
167
|
this.body = options.body;
|
|
@@ -156,7 +172,6 @@ export class GeoMap {
|
|
|
156
172
|
lost: () => this.lost,
|
|
157
173
|
interacting: () => this.interaction?.active === true,
|
|
158
174
|
customLayers: () => this.layers.some((layer) => layer instanceof CustomLayer),
|
|
159
|
-
upgrading: () => this.engines.upgrading !== undefined,
|
|
160
175
|
working: () => this.#working > 0,
|
|
161
176
|
drawn: (hovered) => {
|
|
162
177
|
this.qualityWatch?.frame(performance.now(), this.movingNow);
|
|
@@ -167,15 +182,13 @@ export class GeoMap {
|
|
|
167
182
|
this.overlays.positionAll();
|
|
168
183
|
},
|
|
169
184
|
}, this.listeners);
|
|
170
|
-
|
|
185
|
+
// The engine is made asynchronously (`GeoMap.create` adopts it): a placeholder meanwhile
|
|
186
|
+
this.#engine = destroyedEngine(EARTH_START);
|
|
171
187
|
// Once it has an engine: a map whose creation failed before is not left following them
|
|
172
188
|
this.unwatchHeaders = onHeaderRules((rules) => sendHeaders(this.#engine, rules));
|
|
173
|
-
// The browser may take the GPU away (driver reset, background tab on mobile): the map waits, then rebuilds
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
this.frames.cancel();
|
|
177
|
-
this.dropLostEngine();
|
|
178
|
-
}, () => this.restore());
|
|
189
|
+
// The browser may take the GPU away (driver reset, background tab on mobile): the map waits, then rebuilds. A
|
|
190
|
+
// WebGPU device lost is watched with each engine (`adopt`)
|
|
191
|
+
this.unwatchContext = watchContext(this.canvas, () => this.gpuLost(), () => this.restore());
|
|
179
192
|
const start = this.earth ? EARTH_START : BODY_START;
|
|
180
193
|
this.setCamera({ ...start, ...options });
|
|
181
194
|
this.setBasemap(options.basemap ?? (this.earth ? 'osm-positron' : 'none'), options.hillshade ?? 0);
|
|
@@ -224,33 +237,26 @@ export class GeoMap {
|
|
|
224
237
|
get onTop() {
|
|
225
238
|
return this.stack.onTop;
|
|
226
239
|
}
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
upgrade() {
|
|
233
|
-
this.engines.upgrade(() => {
|
|
234
|
-
// A lost context: the engine is rebuilt on it once restored
|
|
235
|
-
if (!this.lost)
|
|
236
|
-
this.restore();
|
|
237
|
-
}, (error) => {
|
|
238
|
-
// The 3D Tiles layers stay empty; the next one added tries again
|
|
239
|
-
report(error);
|
|
240
|
-
this.requestRender();
|
|
241
|
-
});
|
|
240
|
+
/** The GPU is lost: nothing drawn, the engine dropped (`dropLostEngine`) */
|
|
241
|
+
gpuLost() {
|
|
242
|
+
this.lost = true;
|
|
243
|
+
this.frames.cancel();
|
|
244
|
+
this.dropLostEngine();
|
|
242
245
|
}
|
|
243
|
-
/**
|
|
244
|
-
*
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
246
|
+
/**
|
|
247
|
+
* Follows the loss of `engine`'s WebGPU device (a phone taking its memory back, a driver reset, the GPU process
|
|
248
|
+
* gone): while it is the map's, the map drops it and makes a new one at once, WebGPU having no event for the GPU
|
|
249
|
+
* given back. The device of an engine the map freed itself (replaced, the map destroyed) is lost too: not the map's
|
|
250
|
+
* any more, left be
|
|
251
|
+
*/
|
|
252
|
+
watchDevice(engine) {
|
|
253
|
+
void engine.gpuDevice()?.lost.then((info) => {
|
|
254
|
+
if (this.gone || this.#engine !== engine)
|
|
255
|
+
return;
|
|
256
|
+
console.warn(`isard: the GPU device was lost (${info.reason}: ${info.message}), the map made again`);
|
|
257
|
+
this.gpuLost();
|
|
258
|
+
this.restore();
|
|
259
|
+
});
|
|
254
260
|
}
|
|
255
261
|
/**
|
|
256
262
|
* The context is lost: its engine freed at once, the map's calls going to one that does nothing until `restore`
|
|
@@ -266,19 +272,35 @@ export class GeoMap {
|
|
|
266
272
|
// Glyph ranges and icons arriving meanwhile upload to it, not to the freed engine
|
|
267
273
|
this.atlases.attach(this.#engine);
|
|
268
274
|
}
|
|
269
|
-
/** After
|
|
275
|
+
/** After the GPU lost: a new engine with the same camera, settings and layers (same handles, same order); the engine
|
|
276
|
+
* made asynchronously, adopted once there */
|
|
270
277
|
restore() {
|
|
271
278
|
if (this.gone)
|
|
272
279
|
return;
|
|
280
|
+
this.engines
|
|
281
|
+
.create(this.canvas, () => this.requestRender(), this.settings)
|
|
282
|
+
.then((engine) => this.adopt(engine))
|
|
283
|
+
.catch(report);
|
|
284
|
+
}
|
|
285
|
+
/** A new engine in place of the map's (a placeholder, or a lost one): the same camera, settings and layers (same
|
|
286
|
+
* handles, same order) */
|
|
287
|
+
adopt(engine) {
|
|
288
|
+
if (this.gone) {
|
|
289
|
+
engine.free();
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
273
292
|
this.hoverPick.cancel();
|
|
274
293
|
const camera = this.getCamera();
|
|
275
294
|
this.#engine.free();
|
|
276
|
-
this.#engine =
|
|
295
|
+
this.#engine = engine;
|
|
277
296
|
this.lost = false;
|
|
297
|
+
this.watchDevice(engine);
|
|
298
|
+
// Still timed (profiling)
|
|
299
|
+
if (this.gpuTiming)
|
|
300
|
+
engineGpuTiming(engine, true);
|
|
278
301
|
this.setCamera(camera);
|
|
279
302
|
this.atlases.attach(this.#engine);
|
|
280
|
-
|
|
281
|
-
this.stack.reattach(this.#engine, (layer) => layer.needsFullEngine && !this.engines.tiles3d);
|
|
303
|
+
this.stack.reattach(this.#engine);
|
|
282
304
|
this.applySwipe();
|
|
283
305
|
// A street-level eye stays one in the new engine
|
|
284
306
|
if (this.#firstPerson)
|
|
@@ -293,6 +315,7 @@ export class GeoMap {
|
|
|
293
315
|
void set.show(set.current.index).catch(report);
|
|
294
316
|
});
|
|
295
317
|
this.resize();
|
|
318
|
+
this.requestRender();
|
|
296
319
|
}
|
|
297
320
|
/** The street-level eye, while there is one (`setFirstPerson`) */
|
|
298
321
|
#firstPerson;
|
|
@@ -354,20 +377,14 @@ export class GeoMap {
|
|
|
354
377
|
return !this.gone && this.stack.has(layer);
|
|
355
378
|
}
|
|
356
379
|
addLayer(layer) {
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
catch (error) {
|
|
366
|
-
// A context lost before the browser says so (its event comes later): nothing could be made on it, the layer is
|
|
367
|
-
// made with the others on the next engine (`restore`). Any other failure is the caller's
|
|
368
|
-
if (!this.contextLost())
|
|
369
|
-
throw error;
|
|
370
|
-
}
|
|
380
|
+
try {
|
|
381
|
+
layer.attach(this.engine);
|
|
382
|
+
}
|
|
383
|
+
catch (error) {
|
|
384
|
+
// A context lost before the browser says so (its event comes later): nothing could be made on it, the layer is
|
|
385
|
+
// made with the others on the next engine (`restore`). Any other failure is the caller's
|
|
386
|
+
if (!this.contextLost())
|
|
387
|
+
throw error;
|
|
371
388
|
}
|
|
372
389
|
// Under the layers kept on top (the basemap's labels)
|
|
373
390
|
this.stack.add(layer);
|
|
@@ -376,6 +393,7 @@ export class GeoMap {
|
|
|
376
393
|
}
|
|
377
394
|
/** Whether the canvas's WebGL context is lost, its event come or not */
|
|
378
395
|
contextLost() {
|
|
396
|
+
// On a WebGPU canvas, no WebGL context: null, nothing taken
|
|
379
397
|
return this.lost || this.canvas.getContext('webgl2')?.isContextLost() === true;
|
|
380
398
|
}
|
|
381
399
|
/** @internal Keeps a layer above the layers added after it (they go under it), as the labels of a basemap */
|
|
@@ -573,26 +591,64 @@ export class GeoMap {
|
|
|
573
591
|
this.engine.setBackground(...this.settings.background);
|
|
574
592
|
this.requestRender();
|
|
575
593
|
}
|
|
594
|
+
/** The GPU memory its layers hold (bytes): the tiles of the 3D Tiles and point clouds, kept within the budget
|
|
595
|
+
* (`GeoMapOptions.memory`), the rasters' pools */
|
|
596
|
+
memory() {
|
|
597
|
+
// Two numbers a layer, its id and its bytes, the relief's (id 0) last
|
|
598
|
+
const pairs = this.engine.memory();
|
|
599
|
+
const byId = new Map(Array.from({ length: pairs.length / 2 }, (_, i) => [pairs[2 * i], pairs[2 * i + 1] ?? 0]));
|
|
600
|
+
const layers = this.layers.flatMap((layer) => {
|
|
601
|
+
const bytes = layer.id >= 0 ? byId.get(layer.id) : undefined;
|
|
602
|
+
return bytes === undefined ? [] : [{ layer, bytes }];
|
|
603
|
+
});
|
|
604
|
+
return {
|
|
605
|
+
budget: this.settings.memory,
|
|
606
|
+
held: [...byId.values()].reduce((sum, bytes) => sum + bytes, 0),
|
|
607
|
+
layers,
|
|
608
|
+
};
|
|
609
|
+
}
|
|
576
610
|
/** Tile counters of the raster layers, for diagnostics */
|
|
577
611
|
stats() {
|
|
578
612
|
return this.engine.stats();
|
|
579
613
|
}
|
|
614
|
+
/** Times the parts of each frame on the GPU, for profiling (`gpuTimings`): timer queries, read a few frames later.
|
|
615
|
+
* An engine built to profile only (`PROFILE=1 bun run build`); false without one, or if the browser cannot
|
|
616
|
+
* (`EXT_disjoint_timer_query_webgl2`) */
|
|
617
|
+
setGpuTiming(on) {
|
|
618
|
+
this.gpuTiming = engineGpuTiming(this.engine, on);
|
|
619
|
+
return this.gpuTiming;
|
|
620
|
+
}
|
|
621
|
+
/** The GPU time of the last frame measured (`setGpuTiming`), in all and by part: the backdrop, the ground (the raster
|
|
622
|
+
* layers over the relief), each layer, the shadows, the panorama, the viewshed (ms) */
|
|
623
|
+
gpuTimings() {
|
|
624
|
+
// The frame's number, then each part's code, layer id (-1 for none) and milliseconds
|
|
625
|
+
const read = Reflect.get(this.engine, 'gpuTimings');
|
|
626
|
+
const flat = typeof read === 'function' ? Reflect.apply(read, this.engine, []) : undefined;
|
|
627
|
+
if (!(flat instanceof Float64Array) || flat.length === 0)
|
|
628
|
+
return undefined;
|
|
629
|
+
const parts = Array.from({ length: (flat.length - 1) / 3 }, (_, k) => {
|
|
630
|
+
const [code = 0, id = -1, ms = 0] = flat.subarray(1 + k * 3, 4 + k * 3);
|
|
631
|
+
const layer = id >= 0 ? this.layers.find((l) => l.id === id) : undefined;
|
|
632
|
+
return { part: GPU_PARTS[code] ?? 'layer', ms, ...(layer && { layer }) };
|
|
633
|
+
});
|
|
634
|
+
return { frame: flat[0] ?? 0, ms: parts.reduce((sum, p) => sum + p.ms, 0), parts };
|
|
635
|
+
}
|
|
580
636
|
/**
|
|
581
|
-
* The feature under a screen point (CSS pixels), undefined if none: its layer id and index. Reads
|
|
582
|
-
* offscreen picking pass, whatever the number of features,
|
|
583
|
-
*
|
|
637
|
+
* The feature under a screen point (CSS pixels), undefined if none: its layer id and index. Reads the pixels around
|
|
638
|
+
* the point of an offscreen picking pass, whatever the number of features, once the GPU is done (a frame or two
|
|
639
|
+
* later: the GPU is not waited for). On hover, listen to 'hoverfeature' instead.
|
|
584
640
|
*/
|
|
585
|
-
pick(point, { radius = 0, layers } = {}) {
|
|
641
|
+
async pick(point, { radius = 0, layers } = {}) {
|
|
586
642
|
const only = layers && Uint32Array.from(layers.map((l) => l.id));
|
|
587
|
-
return this.engine
|
|
643
|
+
return (await pickOnce(() => this.engine, point, radius, only))?.hit;
|
|
588
644
|
}
|
|
589
645
|
/**
|
|
590
646
|
* The feature under a screen point (CSS pixels), or the nearest one within `radius` pixels: its layer handle, its
|
|
591
647
|
* index there and, for line, circle and fill layers, the feature itself; undefined if none. `layers` restricts the
|
|
592
648
|
* search to some layers (the others hide nothing).
|
|
593
649
|
*/
|
|
594
|
-
pickFeature(point, options = {}) {
|
|
595
|
-
const hit = this.pick(point, options);
|
|
650
|
+
async pickFeature(point, options = {}) {
|
|
651
|
+
const hit = await this.pick(point, options);
|
|
596
652
|
return hit && this.featureHit(hit);
|
|
597
653
|
}
|
|
598
654
|
/** A hit with its layer handle and feature; undefined if the layer is gone */
|
|
@@ -617,13 +673,6 @@ export class GeoMap {
|
|
|
617
673
|
* and calibrations loaded, none shown until `show`, which projects one onto the scene around it
|
|
618
674
|
*/
|
|
619
675
|
async addPanoramas(options) {
|
|
620
|
-
// Drawn by the engine with 3D Tiles: loaded first if needed
|
|
621
|
-
if (!this.engines.tiles3d) {
|
|
622
|
-
this.upgrade();
|
|
623
|
-
await this.engines.upgrading;
|
|
624
|
-
if (!this.engines.tiles3d)
|
|
625
|
-
throw new Error('isard: panoramas need the engine with 3D Tiles, which failed to load');
|
|
626
|
-
}
|
|
627
676
|
const host = panoramaHost(this, (lon, lat) => this.engines.geoidHeight(lon, lat));
|
|
628
677
|
const panoramas = await loadPanoramas(host, options);
|
|
629
678
|
this.panoramaSets.add(panoramas);
|
|
@@ -664,15 +713,16 @@ export class GeoMap {
|
|
|
664
713
|
this.interaction.rotateAroundPointer = enabled;
|
|
665
714
|
}
|
|
666
715
|
/** What is drawn under a screen point (CSS pixels), on the relief or standing on it (a roof, a wall, a mesh): its
|
|
667
|
-
* position and its height above the relief, in metres; undefined where nothing was drawn.
|
|
668
|
-
*
|
|
716
|
+
* position and its height above the relief, in metres; undefined where nothing was drawn. Read from the last copy of
|
|
717
|
+
* the scene's depth (a frame or two old, a pixel every 4 CSS pixels): at once, without waiting for the GPU */
|
|
669
718
|
pickSurface(point) {
|
|
670
719
|
const hit = this.engine.surface(point[0], point[1]);
|
|
671
720
|
return hit && { lonLat: [hit[0], hit[1]], height: hit[2] };
|
|
672
721
|
}
|
|
673
722
|
/** The height above the relief, in metres, of the top of what stands at a position: a roof, a mesh, 0 on the bare
|
|
674
|
-
* ground; undefined where nothing of the last frame is there
|
|
675
|
-
* read back
|
|
723
|
+
* ground; undefined where nothing of the last frame is there, or until measured: the scene is drawn looking down at
|
|
724
|
+
* it after a frame and read back a frame or two later, the answer kept and measured again every 250 ms at most (ask
|
|
725
|
+
* again on the next `render` event) */
|
|
676
726
|
surfaceAt(lonLat) {
|
|
677
727
|
return this.engine.surfaceAt(lonLat[0], lonLat[1]);
|
|
678
728
|
}
|
|
@@ -802,7 +852,7 @@ export class GeoMap {
|
|
|
802
852
|
}
|
|
803
853
|
/** Polygons raised into blocks with depth (buildings), styled per feature; tiles of them come with `setTile` */
|
|
804
854
|
addExtrusions(options = {}) {
|
|
805
|
-
return this.place(new ExtrusionLayer(this, options.data, extrusionStyle(options)), options.before);
|
|
855
|
+
return this.place(new ExtrusionLayer(this, options.data, extrusionStyle(options), options.drape), options.before);
|
|
806
856
|
}
|
|
807
857
|
/** Text labels from GeoJSON (points, the middle of lines, the center of polygons), placed without overlaps */
|
|
808
858
|
addSymbols(options) {
|
|
@@ -879,7 +929,7 @@ export class GeoMap {
|
|
|
879
929
|
}
|
|
880
930
|
/**
|
|
881
931
|
* A point cloud (see `PointCloudOptions`): a COPC file read by range requests (IGN's LiDAR HD…), or 3D Tiles
|
|
882
|
-
* points; with depth
|
|
932
|
+
* points; with depth; under `before` if given
|
|
883
933
|
*/
|
|
884
934
|
addPointCloud(options) {
|
|
885
935
|
return this.place(new PointCloudLayer(this, options), options.before);
|
|
@@ -898,7 +948,7 @@ export class GeoMap {
|
|
|
898
948
|
addFlow(options) {
|
|
899
949
|
return this.place(new FlowLayer(this, options), options.before);
|
|
900
950
|
}
|
|
901
|
-
/** A glTF model drawn at positions (see `ModelOptions`), with depth
|
|
951
|
+
/** A glTF model drawn at positions (see `ModelOptions`), with depth */
|
|
902
952
|
addModels(options) {
|
|
903
953
|
return this.place(new ModelLayer(this, options), options.before);
|
|
904
954
|
}
|
|
@@ -913,11 +963,12 @@ export class GeoMap {
|
|
|
913
963
|
return addPhoto((o) => this.addModels(o), options);
|
|
914
964
|
}
|
|
915
965
|
/**
|
|
916
|
-
* The page's own
|
|
917
|
-
*
|
|
966
|
+
* The page's own WebGPU drawing at its place among the layers, in every frame (see `CustomLayerInterface`): raw
|
|
967
|
+
* WebGPU, three.js's WebGPU renderer… with the map's device and the matrices of the frame (`frame.matrixAt`); under
|
|
968
|
+
* `before` if given. Not drawn on the WebGL2 fallback
|
|
918
969
|
*/
|
|
919
970
|
addCustomLayer(layer, options = {}) {
|
|
920
|
-
const custom = new CustomLayer(this, layer, this
|
|
971
|
+
const custom = new CustomLayer(this, layer, this, (lon, lat, altitude) => this.engines.customMatrix(lon, lat, altitude), () => this.getCamera());
|
|
921
972
|
return this.place(custom, options.before);
|
|
922
973
|
}
|
|
923
974
|
// ── Frames ──
|
|
@@ -975,7 +1026,7 @@ export class GeoMap {
|
|
|
975
1026
|
this.resizeObserver.disconnect();
|
|
976
1027
|
this.interaction?.destroy();
|
|
977
1028
|
this.engines.destroy();
|
|
978
|
-
// Custom layers free their
|
|
1029
|
+
// Custom layers free their GPU objects, models their data, while the device is there
|
|
979
1030
|
this.stack.dispose();
|
|
980
1031
|
this.engine.free();
|
|
981
1032
|
// Work still under way (labels being shaped, tiles arriving, timers) may call the engine after this: it finds one
|