isard 1.15.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/CHANGELOG.md +70 -0
  2. package/README.md +4 -4
  3. package/dist/basemap/vectortiles/placed.js +7 -4
  4. package/dist/basemap/vectortiles/placed.js.map +1 -1
  5. package/dist/basemap/vectortiles/tiles.d.ts +1 -1
  6. package/dist/basemap/vectortiles/tiles.js.map +1 -1
  7. package/dist/basemap/workerpool.d.ts +1 -1
  8. package/dist/basemap/workerpool.js.map +1 -1
  9. package/dist/bind/layers.js +3 -1
  10. package/dist/bind/layers.js.map +1 -1
  11. package/dist/decoder.worker.js +4 -5
  12. package/dist/decoder.worker.js.map +6 -6
  13. package/dist/decoding/decoders.d.ts +1 -1
  14. package/dist/decoding/decoders.js.map +1 -1
  15. package/dist/decoding/loader.d.ts +6 -3
  16. package/dist/decoding/loader.js +7 -5
  17. package/dist/decoding/loader.js.map +1 -1
  18. package/dist/decoding/protocol.d.ts +3 -2
  19. package/dist/decoding/protocol.js.map +1 -1
  20. package/dist/engine-loader.d.ts +20 -18
  21. package/dist/engine-loader.js +45 -42
  22. package/dist/engine-loader.js.map +1 -1
  23. package/dist/index.d.ts +3 -3
  24. package/dist/index.js.map +1 -1
  25. package/dist/isard_tess_bg.wasm +0 -0
  26. package/dist/isard_tiles3d_bg.wasm +0 -0
  27. package/dist/isard_webgl2_bg.wasm +0 -0
  28. package/dist/isard_webgpu_bg.wasm +0 -0
  29. package/dist/layers/custom.d.ts +35 -29
  30. package/dist/layers/custom.js +60 -46
  31. package/dist/layers/custom.js.map +1 -1
  32. package/dist/layers/extrusions.js.map +1 -1
  33. package/dist/layers/features.js.map +1 -1
  34. package/dist/layers/flow.js.map +1 -1
  35. package/dist/layers/glyphs.d.ts +1 -1
  36. package/dist/layers/glyphs.js.map +1 -1
  37. package/dist/layers/ground.d.ts +20 -13
  38. package/dist/layers/ground.js +29 -8
  39. package/dist/layers/ground.js.map +1 -1
  40. package/dist/layers/icons.d.ts +1 -1
  41. package/dist/layers/icons.js.map +1 -1
  42. package/dist/layers/layers.d.ts +1 -2
  43. package/dist/layers/layers.js +2 -11
  44. package/dist/layers/layers.js.map +1 -1
  45. package/dist/layers/models.js +0 -4
  46. package/dist/layers/models.js.map +1 -1
  47. package/dist/layers/network.d.ts +1 -1
  48. package/dist/layers/network.js.map +1 -1
  49. package/dist/layers/panoramas.d.ts +1 -0
  50. package/dist/layers/panoramas.js +3 -2
  51. package/dist/layers/panoramas.js.map +1 -1
  52. package/dist/layers/pointclouds.d.ts +1 -1
  53. package/dist/layers/pointclouds.js +0 -4
  54. package/dist/layers/pointclouds.js.map +1 -1
  55. package/dist/layers/raster.js +2 -2
  56. package/dist/layers/raster.js.map +1 -1
  57. package/dist/layers/symbols.js.map +1 -1
  58. package/dist/layers/tiles3d.d.ts +6 -2
  59. package/dist/layers/tiles3d.js +23 -5
  60. package/dist/layers/tiles3d.js.map +1 -1
  61. package/dist/layers/vectors.d.ts +4 -5
  62. package/dist/layers/vectors.js +7 -15
  63. package/dist/layers/vectors.js.map +1 -1
  64. package/dist/map/engine-settings.d.ts +1 -1
  65. package/dist/map/engine-settings.js.map +1 -1
  66. package/dist/map/engines.d.ts +11 -26
  67. package/dist/map/engines.js +10 -48
  68. package/dist/map/engines.js.map +1 -1
  69. package/dist/map/frame-loop.d.ts +1 -3
  70. package/dist/map/frame-loop.js +3 -3
  71. package/dist/map/frame-loop.js.map +1 -1
  72. package/dist/map/hover-pick.d.ts +9 -3
  73. package/dist/map/hover-pick.js +29 -6
  74. package/dist/map/hover-pick.js.map +1 -1
  75. package/dist/map/layer-stack.d.ts +2 -2
  76. package/dist/map/layer-stack.js +1 -3
  77. package/dist/map/layer-stack.js.map +1 -1
  78. package/dist/map/symbol-atlases.d.ts +5 -4
  79. package/dist/map/symbol-atlases.js +8 -13
  80. package/dist/map/symbol-atlases.js.map +1 -1
  81. package/dist/map/viewshed.d.ts +1 -1
  82. package/dist/map/viewshed.js.map +1 -1
  83. package/dist/map.d.ts +26 -20
  84. package/dist/map.js +97 -82
  85. package/dist/map.js.map +1 -1
  86. package/dist/react/IsardMap.d.ts +1 -1
  87. package/dist/react/IsardMap.js.map +1 -1
  88. package/dist/react/index.d.ts +1 -1
  89. package/dist/react/index.js.map +1 -1
  90. package/dist/types.d.ts +49 -18
  91. package/dist/types.js.map +1 -1
  92. package/dist/ui/interaction.d.ts +1 -1
  93. package/dist/ui/interaction.js.map +1 -1
  94. package/dist/util/animation.d.ts +1 -1
  95. package/dist/util/animation.js.map +1 -1
  96. package/dist/wasm/isard_tiles3d.d.ts +181 -0
  97. package/dist/wasm/isard_tiles3d.js +387 -0
  98. package/dist/wasm/{isard_core.d.ts → isard_webgl2.d.ts} +375 -184
  99. package/dist/wasm/{isard_core.js → isard_webgl2.js} +2167 -415
  100. package/dist/wasm/{isard.d.ts → isard_webgpu.d.ts} +183 -355
  101. package/dist/wasm/{isard.js → isard_webgpu.js} +1374 -1097
  102. package/package.json +4 -3
  103. package/vite.js +5 -4
  104. package/dist/isard_bg.wasm +0 -0
  105. package/dist/isard_core_bg.wasm +0 -0
@@ -1 +1 @@
1
- {"version":3,"file":"viewshed.js","sourceRoot":"","sources":["../../src/map/viewshed.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAE5E,OAAO,EAAE,GAAG,EAAE,MAAM,gBAAgB,CAAC;AAIrC,gEAAgE;AAChE,MAAM,WAAW,GAAG,CAAC,OAAiE,EAA8B,EAAE;IACpH,IAAI,CAAC,OAAO;QAAE,OAAO,EAAE,CAAC;IAExB,OAAO,UAAU,IAAI,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;AACrD,CAAC,CAAC;AAEF,4GAA4G;AAC5G,MAAM,CAAC,MAAM,aAAa,GAAG,CAC3B,MAAc,EACd,OAAiE,EACxD,EAAE;IACX,MAAM,SAAS,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC;IACvC,MAAM,CAAC,KAAK,CAAC,GAAG,SAAS,CAAC;IAE1B,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,CAAC,aAAa,EAAE,CAAC;QAEvB,OAAO,KAAK,CAAC;IACf,CAAC;IACD,MAAM,CAAC,WAAW,CAChB,IAAI,YAAY,CACd,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;QACvB,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC;QACb,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC;QACb,CAAC,CAAC,MAAM,IAAI,GAAG;QACf,CAAC,CAAC,KAAK,IAAI,IAAI;QACf,GAAG,CAAC,CAAC,CAAC,SAAS,KAAK,SAAS;YAC3B,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;YACvB,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,IAAI,IAAI,CAAC,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,EAAE,CAAC,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC;KAClE,CAAC,CACH,EACD,IAAI,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC,YAAY,IAAI,SAAS,CAAC,CAAC,EACtD,IAAI,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC,WAAW,IAAI,SAAS,CAAC,CAAC,EACrD,KAAK,CAAC,OAAO,IAAI,GAAG,EACpB,IAAI,YAAY,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAC9D,CAAC;IAEF,OAAO,IAAI,CAAC;AACd,CAAC,CAAC","sourcesContent":["/** The viewshed's observers given to the engine, in the arrays it reads. */\n\nimport { rgb } from '../settings.ts';\nimport type { ViewshedOptions } from '../types.ts';\nimport type { Engine } from '../wasm/isard.js';\n\n/** The observers of a viewshed option: none, one, or several */\nconst observersOf = (options: ViewshedOptions | readonly ViewshedOptions[] | undefined): readonly ViewshedOptions[] => {\n if (!options) return [];\n\n return 'position' in options ? [options] : options;\n};\n\n/** Sets the viewshed of the engine, or clears it without observers; true if one is set (a frame to draw) */\nexport const applyViewshed = (\n engine: Engine,\n options: ViewshedOptions | readonly ViewshedOptions[] | undefined,\n): boolean => {\n const observers = observersOf(options);\n const [first] = observers;\n\n if (!first) {\n engine.clearViewshed();\n\n return false;\n }\n engine.setViewshed(\n new Float64Array(\n observers.flatMap((o) => [\n o.position[0],\n o.position[1],\n o.height ?? 1.7,\n o.range ?? 1000,\n ...(o.direction === undefined\n ? [Number.NaN, 0, 0, 0]\n : [o.direction, o.tilt ?? 0, o.fov ?? 60, o.verticalFov ?? 40]),\n ]),\n ),\n new Float32Array(rgb(first.visibleColor ?? '#2f9e44')),\n new Float32Array(rgb(first.hiddenColor ?? '#e03131')),\n first.opacity ?? 0.7,\n new Float32Array(first.coneColor ? rgb(first.coneColor) : []),\n );\n\n return true;\n};\n"]}
1
+ {"version":3,"file":"viewshed.js","sourceRoot":"","sources":["../../src/map/viewshed.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAE5E,OAAO,EAAE,GAAG,EAAE,MAAM,gBAAgB,CAAC;AAIrC,gEAAgE;AAChE,MAAM,WAAW,GAAG,CAAC,OAAiE,EAA8B,EAAE;IACpH,IAAI,CAAC,OAAO;QAAE,OAAO,EAAE,CAAC;IAExB,OAAO,UAAU,IAAI,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;AACrD,CAAC,CAAC;AAEF,4GAA4G;AAC5G,MAAM,CAAC,MAAM,aAAa,GAAG,CAC3B,MAAc,EACd,OAAiE,EACxD,EAAE;IACX,MAAM,SAAS,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC;IACvC,MAAM,CAAC,KAAK,CAAC,GAAG,SAAS,CAAC;IAE1B,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,CAAC,aAAa,EAAE,CAAC;QAEvB,OAAO,KAAK,CAAC;IACf,CAAC;IACD,MAAM,CAAC,WAAW,CAChB,IAAI,YAAY,CACd,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;QACvB,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC;QACb,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC;QACb,CAAC,CAAC,MAAM,IAAI,GAAG;QACf,CAAC,CAAC,KAAK,IAAI,IAAI;QACf,GAAG,CAAC,CAAC,CAAC,SAAS,KAAK,SAAS;YAC3B,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;YACvB,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,IAAI,IAAI,CAAC,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,EAAE,CAAC,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC;KAClE,CAAC,CACH,EACD,IAAI,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC,YAAY,IAAI,SAAS,CAAC,CAAC,EACtD,IAAI,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC,WAAW,IAAI,SAAS,CAAC,CAAC,EACrD,KAAK,CAAC,OAAO,IAAI,GAAG,EACpB,IAAI,YAAY,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAC9D,CAAC;IAEF,OAAO,IAAI,CAAC;AACd,CAAC,CAAC","sourcesContent":["/** The viewshed's observers given to the engine, in the arrays it reads. */\n\nimport { rgb } from '../settings.ts';\nimport type { ViewshedOptions } from '../types.ts';\nimport type { Engine } from '../wasm/isard_webgpu.js';\n\n/** The observers of a viewshed option: none, one, or several */\nconst observersOf = (options: ViewshedOptions | readonly ViewshedOptions[] | undefined): readonly ViewshedOptions[] => {\n if (!options) return [];\n\n return 'position' in options ? [options] : options;\n};\n\n/** Sets the viewshed of the engine, or clears it without observers; true if one is set (a frame to draw) */\nexport const applyViewshed = (\n engine: Engine,\n options: ViewshedOptions | readonly ViewshedOptions[] | undefined,\n): boolean => {\n const observers = observersOf(options);\n const [first] = observers;\n\n if (!first) {\n engine.clearViewshed();\n\n return false;\n }\n engine.setViewshed(\n new Float64Array(\n observers.flatMap((o) => [\n o.position[0],\n o.position[1],\n o.height ?? 1.7,\n o.range ?? 1000,\n ...(o.direction === undefined\n ? [Number.NaN, 0, 0, 0]\n : [o.direction, o.tilt ?? 0, o.fov ?? 60, o.verticalFov ?? 40]),\n ]),\n ),\n new Float32Array(rgb(first.visibleColor ?? '#2f9e44')),\n new Float32Array(rgb(first.hiddenColor ?? '#e03131')),\n first.opacity ?? 0.7,\n new Float32Array(first.coneColor ? rgb(first.coneColor) : []),\n );\n\n return true;\n};\n"]}
package/dist/map.d.ts CHANGED
@@ -26,14 +26,15 @@ import { Tiles3dLayer } from './layers/tiles3d.ts';
26
26
  import { type TreeOptions } from './layers/trees.ts';
27
27
  import { type HoverPicking } from './map/hover-pick.ts';
28
28
  import { type LayerSpec } from './map/layer-spec.ts';
29
- import type { AnimationOptions, AtmosphereOptions, Basemap, FeatureHit, FirstPersonView, FlyToOptions, GeoMapOptions, LoadingMode, LonLat, MapEvents, NetworkOptions, PickOptions, Point, Projection, Quality, RasterOptions, SunOptions, SwipeOptions, TerrainModel, TerrainOptions, Tiles3dOptions, ViewshedOptions } from './types.ts';
29
+ import type { AnimationOptions, AtmosphereOptions, Basemap, FeatureHit, FirstPersonView, FlyToOptions, GeoMapOptions, LoadingMode, LonLat, MapEvents, MapGpuTimings, NetworkOptions, PickOptions, Point, Projection, Quality, RasterOptions, SunOptions, SwipeOptions, TerrainModel, TerrainOptions, Tiles3dOptions, ViewshedOptions } from './types.ts';
30
30
  import { type Control, type ControlPosition } from './ui/controls.ts';
31
- import type { Camera, PickHit, RasterLayerStats } from './wasm/isard.js';
31
+ import type { Camera, PickHit, RasterLayerStats } from './wasm/isard_webgpu.js';
32
32
  export type { LayerSpec } from './map/layer-spec.ts';
33
- /** A map (see `GeoMap.create`); layers take it as their host */
34
33
  export declare class GeoMap {
35
34
  #private;
36
35
  readonly canvas: HTMLCanvasElement;
36
+ /** The frames' parts timed on the GPU (`setGpuTiming`), again on a new engine */
37
+ private gpuTiming;
37
38
  /** The engine class (core, or with 3D Tiles) and the engine with 3D Tiles loaded when needed */
38
39
  private readonly engines;
39
40
  /** The layers in drawing order and those kept on top */
@@ -84,13 +85,6 @@ export declare class GeoMap {
84
85
  private get layers();
85
86
  /** Layers new layers go under */
86
87
  private get onTop();
87
- private createEngine;
88
- /** A 3D Tiles layer or a model on the core engine: loads the engine with 3D Tiles, then rebuilds the map on it
89
- * (camera, settings and layers kept, as after a context loss); the layer is created then */
90
- private upgrade;
91
- /** Downloads and compiles the engine with 3D Tiles once the map is idle (`tiles3d: 'preload'`), without running
92
- * it: the first 3D Tiles layer then only instantiates it */
93
- private preloadTiles3d;
94
88
  /**
95
89
  * The context is lost: its engine freed at once, the map's calls going to one that does nothing until `restore`
96
90
  * builds the next. The old engine's GPU objects went with the context: deleted now, it is silent (on the context
@@ -98,8 +92,12 @@ export declare class GeoMap {
98
92
  * would fail on the lost context, whose limits read 0
99
93
  */
100
94
  private dropLostEngine;
101
- /** After a context loss: a new engine with the same camera, settings and layers (same handles, same order) */
95
+ /** After a context loss: a new engine with the same camera, settings and layers (same handles, same order); the
96
+ * engine made asynchronously, adopted once there */
102
97
  private restore;
98
+ /** A new engine in place of the map's (a placeholder, or a lost one): the same camera, settings and layers (same
99
+ * handles, same order) */
100
+ private adopt;
103
101
  /** The street-level eye, if the camera is one */
104
102
  get firstPerson(): Readonly<Required<FirstPersonView>> | undefined;
105
103
  /**
@@ -182,18 +180,25 @@ export declare class GeoMap {
182
180
  setBackground(color: string): void;
183
181
  /** Tile counters of the raster layers, for diagnostics */
184
182
  stats(): RasterLayerStats[];
183
+ /** Times the parts of each frame on the GPU, for profiling (`gpuTimings`): timer queries, read a few frames later.
184
+ * An engine built to profile only (`PROFILE=1 bun run build`); false without one, or if the browser cannot
185
+ * (`EXT_disjoint_timer_query_webgl2`) */
186
+ setGpuTiming(on: boolean): boolean;
187
+ /** The GPU time of the last frame measured (`setGpuTiming`), in all and by part: the backdrop, the ground (the raster
188
+ * layers over the relief), each layer, the shadows, the panorama, the viewshed (ms) */
189
+ gpuTimings(): MapGpuTimings | undefined;
185
190
  /**
186
- * The feature under a screen point (CSS pixels), undefined if none: its layer id and index. Reads one pixel of an
187
- * offscreen picking pass, whatever the number of features, but waits for the GPU to finish the frame (a stall): for
188
- * clicks and one-off queries. On hover, listen to 'hoverfeature' instead, which picks without waiting.
191
+ * The feature under a screen point (CSS pixels), undefined if none: its layer id and index. Reads the pixels around
192
+ * the point of an offscreen picking pass, whatever the number of features, once the GPU is done (a frame or two
193
+ * later: the GPU is not waited for). On hover, listen to 'hoverfeature' instead.
189
194
  */
190
- pick(point: Point, { radius, layers }?: PickOptions): PickHit | undefined;
195
+ pick(point: Point, { radius, layers }?: PickOptions): Promise<PickHit | undefined>;
191
196
  /**
192
197
  * The feature under a screen point (CSS pixels), or the nearest one within `radius` pixels: its layer handle, its
193
198
  * index there and, for line, circle and fill layers, the feature itself; undefined if none. `layers` restricts the
194
199
  * search to some layers (the others hide nothing).
195
200
  */
196
- pickFeature(point: Point, options?: PickOptions): FeatureHit | undefined;
201
+ pickFeature(point: Point, options?: PickOptions): Promise<FeatureHit | undefined>;
197
202
  /** A hit with its layer handle and feature; undefined if the layer is gone */
198
203
  private featureHit;
199
204
  /** Screen point (CSS pixels) of a position on the ground (the relief), or `height` metres above it, undefined
@@ -220,15 +225,16 @@ export declare class GeoMap {
220
225
  * where it starts, a roof or a mesh as the relief (on by default), or around the screen center */
221
226
  setRotateAroundPointer(enabled: boolean): void;
222
227
  /** What is drawn under a screen point (CSS pixels), on the relief or standing on it (a roof, a wall, a mesh): its
223
- * position and its height above the relief, in metres; undefined where nothing was drawn. The last frame's scene is
224
- * drawn again and read back, waiting for the GPU (a stall): for clicks and drags */
228
+ * position and its height above the relief, in metres; undefined where nothing was drawn. Read from the last copy of
229
+ * the scene's depth (a frame or two old, a pixel every 4 CSS pixels): at once, without waiting for the GPU */
225
230
  pickSurface(point: Point): {
226
231
  lonLat: LonLat;
227
232
  height: number;
228
233
  } | undefined;
229
234
  /** The height above the relief, in metres, of the top of what stands at a position: a roof, a mesh, 0 on the bare
230
- * ground; undefined where nothing of the last frame is there. The scene is drawn from above into a single pixel and
231
- * read back: cheap, but it waits for the GPU */
235
+ * ground; undefined where nothing of the last frame is there, or until measured: the scene is drawn looking down at
236
+ * it after a frame and read back a frame or two later, the answer kept and measured again every 250 ms at most (ask
237
+ * again on the next `render` event) */
232
238
  surfaceAt(lonLat: LonLat): number | undefined;
233
239
  /** Adds a control in a corner of the map; controls stack from the corner inward */
234
240
  addControl(control: Control, position?: ControlPosition): this;
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,9 +67,19 @@ 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
85
  /** The engine class (core, or with 3D Tiles) and the engine with 3D Tiles loaded when needed */
@@ -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
- const kind = full ? 'full' : 'core';
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, full ? bindings.module : undefined);
135
- if (options.tiles3d === 'preload' && !full)
136
- map.preloadTiles3d();
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, tiles3dModule) {
140
- this.engines = new MapEngines(engineClass, tiles3dModule, options, () => this.gone);
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
- this.atlases = new SymbolAtlases(this.canvas, options.glyphs, () => this.#engine);
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,7 +182,8 @@ export class GeoMap {
167
182
  this.overlays.positionAll();
168
183
  },
169
184
  }, this.listeners);
170
- this.#engine = this.createEngine();
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
189
  // The browser may take the GPU away (driver reset, background tab on mobile): the map waits, then rebuilds
@@ -224,34 +240,6 @@ export class GeoMap {
224
240
  get onTop() {
225
241
  return this.stack.onTop;
226
242
  }
227
- createEngine() {
228
- return this.engines.create(this.canvas, () => this.requestRender(), this.settings);
229
- }
230
- /** A 3D Tiles layer or a model on the core engine: loads the engine with 3D Tiles, then rebuilds the map on it
231
- * (camera, settings and layers kept, as after a context loss); the layer is created then */
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
- });
242
- }
243
- /** Downloads and compiles the engine with 3D Tiles once the map is idle (`tiles3d: 'preload'`), without running
244
- * it: the first 3D Tiles layer then only instantiates it */
245
- preloadTiles3d() {
246
- if (this.frames.settled)
247
- this.engines.preload();
248
- else {
249
- const off = this.on('idle', () => {
250
- off();
251
- this.engines.preload();
252
- });
253
- }
254
- }
255
243
  /**
256
244
  * The context is lost: its engine freed at once, the map's calls going to one that does nothing until `restore`
257
245
  * builds the next. The old engine's GPU objects went with the context: deleted now, it is silent (on the context
@@ -266,19 +254,34 @@ export class GeoMap {
266
254
  // Glyph ranges and icons arriving meanwhile upload to it, not to the freed engine
267
255
  this.atlases.attach(this.#engine);
268
256
  }
269
- /** After a context loss: a new engine with the same camera, settings and layers (same handles, same order) */
257
+ /** After a context loss: a new engine with the same camera, settings and layers (same handles, same order); the
258
+ * engine made asynchronously, adopted once there */
270
259
  restore() {
271
260
  if (this.gone)
272
261
  return;
262
+ this.engines
263
+ .create(this.canvas, () => this.requestRender(), this.settings)
264
+ .then((engine) => this.adopt(engine))
265
+ .catch(report);
266
+ }
267
+ /** A new engine in place of the map's (a placeholder, or a lost one): the same camera, settings and layers (same
268
+ * handles, same order) */
269
+ adopt(engine) {
270
+ if (this.gone) {
271
+ engine.free();
272
+ return;
273
+ }
273
274
  this.hoverPick.cancel();
274
275
  const camera = this.getCamera();
275
276
  this.#engine.free();
276
- this.#engine = this.createEngine();
277
+ this.#engine = engine;
277
278
  this.lost = false;
279
+ // Still timed (profiling)
280
+ if (this.gpuTiming)
281
+ engineGpuTiming(engine, true);
278
282
  this.setCamera(camera);
279
283
  this.atlases.attach(this.#engine);
280
- // Those still waiting for the engine with 3D Tiles (loading, or failed) are created with it
281
- this.stack.reattach(this.#engine, (layer) => layer.needsFullEngine && !this.engines.tiles3d);
284
+ this.stack.reattach(this.#engine);
282
285
  this.applySwipe();
283
286
  // A street-level eye stays one in the new engine
284
287
  if (this.#firstPerson)
@@ -293,6 +296,7 @@ export class GeoMap {
293
296
  void set.show(set.current.index).catch(report);
294
297
  });
295
298
  this.resize();
299
+ this.requestRender();
296
300
  }
297
301
  /** The street-level eye, while there is one (`setFirstPerson`) */
298
302
  #firstPerson;
@@ -354,20 +358,14 @@ export class GeoMap {
354
358
  return !this.gone && this.stack.has(layer);
355
359
  }
356
360
  addLayer(layer) {
357
- // On the core engine, a 3D Tiles layer or a model waits for the engine with 3D Tiles: it is created with the
358
- // others on it
359
- if (layer.needsFullEngine && !this.engines.tiles3d)
360
- this.upgrade();
361
- else {
362
- try {
363
- layer.attach(this.engine);
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
- }
361
+ try {
362
+ layer.attach(this.engine);
363
+ }
364
+ catch (error) {
365
+ // A context lost before the browser says so (its event comes later): nothing could be made on it, the layer is
366
+ // made with the others on the next engine (`restore`). Any other failure is the caller's
367
+ if (!this.contextLost())
368
+ throw error;
371
369
  }
372
370
  // Under the layers kept on top (the basemap's labels)
373
371
  this.stack.add(layer);
@@ -376,6 +374,7 @@ export class GeoMap {
376
374
  }
377
375
  /** Whether the canvas's WebGL context is lost, its event come or not */
378
376
  contextLost() {
377
+ // On a WebGPU canvas, no WebGL context: null, nothing taken
379
378
  return this.lost || this.canvas.getContext('webgl2')?.isContextLost() === true;
380
379
  }
381
380
  /** @internal Keeps a layer above the layers added after it (they go under it), as the labels of a basemap */
@@ -577,22 +576,44 @@ export class GeoMap {
577
576
  stats() {
578
577
  return this.engine.stats();
579
578
  }
579
+ /** Times the parts of each frame on the GPU, for profiling (`gpuTimings`): timer queries, read a few frames later.
580
+ * An engine built to profile only (`PROFILE=1 bun run build`); false without one, or if the browser cannot
581
+ * (`EXT_disjoint_timer_query_webgl2`) */
582
+ setGpuTiming(on) {
583
+ this.gpuTiming = engineGpuTiming(this.engine, on);
584
+ return this.gpuTiming;
585
+ }
586
+ /** The GPU time of the last frame measured (`setGpuTiming`), in all and by part: the backdrop, the ground (the raster
587
+ * layers over the relief), each layer, the shadows, the panorama, the viewshed (ms) */
588
+ gpuTimings() {
589
+ // The frame's number, then each part's code, layer id (-1 for none) and milliseconds
590
+ const read = Reflect.get(this.engine, 'gpuTimings');
591
+ const flat = typeof read === 'function' ? Reflect.apply(read, this.engine, []) : undefined;
592
+ if (!(flat instanceof Float64Array) || flat.length === 0)
593
+ return undefined;
594
+ const parts = Array.from({ length: (flat.length - 1) / 3 }, (_, k) => {
595
+ const [code = 0, id = -1, ms = 0] = flat.subarray(1 + k * 3, 4 + k * 3);
596
+ const layer = id >= 0 ? this.layers.find((l) => l.id === id) : undefined;
597
+ return { part: GPU_PARTS[code] ?? 'layer', ms, ...(layer && { layer }) };
598
+ });
599
+ return { frame: flat[0] ?? 0, ms: parts.reduce((sum, p) => sum + p.ms, 0), parts };
600
+ }
580
601
  /**
581
- * The feature under a screen point (CSS pixels), undefined if none: its layer id and index. Reads one pixel of an
582
- * offscreen picking pass, whatever the number of features, but waits for the GPU to finish the frame (a stall): for
583
- * clicks and one-off queries. On hover, listen to 'hoverfeature' instead, which picks without waiting.
602
+ * The feature under a screen point (CSS pixels), undefined if none: its layer id and index. Reads the pixels around
603
+ * the point of an offscreen picking pass, whatever the number of features, once the GPU is done (a frame or two
604
+ * later: the GPU is not waited for). On hover, listen to 'hoverfeature' instead.
584
605
  */
585
- pick(point, { radius = 0, layers } = {}) {
606
+ async pick(point, { radius = 0, layers } = {}) {
586
607
  const only = layers && Uint32Array.from(layers.map((l) => l.id));
587
- return this.engine.pick(point[0], point[1], radius, only);
608
+ return (await pickOnce(() => this.engine, point, radius, only))?.hit;
588
609
  }
589
610
  /**
590
611
  * The feature under a screen point (CSS pixels), or the nearest one within `radius` pixels: its layer handle, its
591
612
  * index there and, for line, circle and fill layers, the feature itself; undefined if none. `layers` restricts the
592
613
  * search to some layers (the others hide nothing).
593
614
  */
594
- pickFeature(point, options = {}) {
595
- const hit = this.pick(point, options);
615
+ async pickFeature(point, options = {}) {
616
+ const hit = await this.pick(point, options);
596
617
  return hit && this.featureHit(hit);
597
618
  }
598
619
  /** A hit with its layer handle and feature; undefined if the layer is gone */
@@ -617,13 +638,6 @@ export class GeoMap {
617
638
  * and calibrations loaded, none shown until `show`, which projects one onto the scene around it
618
639
  */
619
640
  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
641
  const host = panoramaHost(this, (lon, lat) => this.engines.geoidHeight(lon, lat));
628
642
  const panoramas = await loadPanoramas(host, options);
629
643
  this.panoramaSets.add(panoramas);
@@ -664,15 +678,16 @@ export class GeoMap {
664
678
  this.interaction.rotateAroundPointer = enabled;
665
679
  }
666
680
  /** 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. The last frame's scene is
668
- * drawn again and read back, waiting for the GPU (a stall): for clicks and drags */
681
+ * position and its height above the relief, in metres; undefined where nothing was drawn. Read from the last copy of
682
+ * the scene's depth (a frame or two old, a pixel every 4 CSS pixels): at once, without waiting for the GPU */
669
683
  pickSurface(point) {
670
684
  const hit = this.engine.surface(point[0], point[1]);
671
685
  return hit && { lonLat: [hit[0], hit[1]], height: hit[2] };
672
686
  }
673
687
  /** 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. The scene is drawn from above into a single pixel and
675
- * read back: cheap, but it waits for the GPU */
688
+ * ground; undefined where nothing of the last frame is there, or until measured: the scene is drawn looking down at
689
+ * it after a frame and read back a frame or two later, the answer kept and measured again every 250 ms at most (ask
690
+ * again on the next `render` event) */
676
691
  surfaceAt(lonLat) {
677
692
  return this.engine.surfaceAt(lonLat[0], lonLat[1]);
678
693
  }
@@ -714,9 +729,9 @@ export class GeoMap {
714
729
  /** Raster tiles ({z}, {x}, {y} URL; Web Mercator, or reprojected from another `grid`: longitudes and latitudes,
715
730
  * Lambert-93, UTM…), over the layers already added */
716
731
  addRaster(options) {
717
- const { url, opacity = 1, saturation = 1, size, before } = options;
732
+ const { url, opacity = 1, saturation = 1, size, before, ground = true } = options;
718
733
  const { loader, minZoom, maxZoom } = rasterSource(options, this.workers.remoteReprojection);
719
- const layer = new RasterLayer(this, { url, minZoom, maxZoom, opacity, saturation, size, loader });
734
+ const layer = new RasterLayer(this, { url, minZoom, maxZoom, opacity, saturation, size, loader, ground });
720
735
  this.place(layer, before);
721
736
  if (options.attribution)
722
737
  this.credits.set(layer, options.attribution);
@@ -869,13 +884,13 @@ export class GeoMap {
869
884
  }
870
885
  /** Textured meshes in 3D Tiles (photogrammetry, buildings), drawn with depth over the basemap */
871
886
  addTiles3d(options) {
872
- const { url, maxScreenSpaceError = 16, opacity = 1, pointSize, color, geoid = false, facades = false } = options;
887
+ const { url, maxScreenSpaceError = 16, opacity = 1, pointSize, color, geoid = false, facades = false, drape, } = options;
873
888
  return this.place(new Tiles3dLayer(this, url, geoid, {
874
889
  maxScreenSpaceError,
875
890
  opacity,
876
891
  ...(pointSize !== undefined ? { pointSize } : {}),
877
892
  ...(color !== undefined ? { color } : {}),
878
- }, facades), options.before);
893
+ }, facades, drape), options.before);
879
894
  }
880
895
  /**
881
896
  * A point cloud (see `PointCloudOptions`): a COPC file read by range requests (IGN's LiDAR HD…), or 3D Tiles
@@ -917,7 +932,7 @@ export class GeoMap {
917
932
  * WebGL, three.js… with the matrices of the frame (`frame.matrixAt`); under `before` if given
918
933
  */
919
934
  addCustomLayer(layer, options = {}) {
920
- const custom = new CustomLayer(this, layer, this.canvas, this, (lon, lat, altitude) => this.engines.customMatrix(lon, lat, altitude), () => this.getCamera());
935
+ const custom = new CustomLayer(this, layer, this, (lon, lat, altitude) => this.engines.customMatrix(lon, lat, altitude), () => this.getCamera());
921
936
  return this.place(custom, options.before);
922
937
  }
923
938
  // ── Frames ──