isard 1.0.0 → 1.2.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 (268) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/dist/{basemaps.d.ts → basemap/basemaps.d.ts} +3 -3
  3. package/dist/{basemaps.js → basemap/basemaps.js} +3 -3
  4. package/dist/basemap/basemaps.js.map +1 -0
  5. package/dist/{grid.d.ts → basemap/grid.d.ts} +6 -6
  6. package/dist/{grid.js → basemap/grid.js} +61 -67
  7. package/dist/basemap/grid.js.map +1 -0
  8. package/dist/{mvt.d.ts → basemap/mvt.d.ts} +1 -1
  9. package/dist/{mvt.js → basemap/mvt.js} +6 -6
  10. package/dist/basemap/mvt.js.map +1 -0
  11. package/dist/{pmtiles.d.ts → basemap/pmtiles.d.ts} +16 -10
  12. package/dist/{pmtiles.js → basemap/pmtiles.js} +41 -33
  13. package/dist/basemap/pmtiles.js.map +1 -0
  14. package/dist/{resample.d.ts → basemap/resample.d.ts} +11 -2
  15. package/dist/{resample.js → basemap/resample.js} +18 -16
  16. package/dist/basemap/resample.js.map +1 -0
  17. package/dist/{terrain.d.ts → basemap/terrain.d.ts} +3 -3
  18. package/dist/{terrain.js → basemap/terrain.js} +7 -7
  19. package/dist/basemap/terrain.js.map +1 -0
  20. package/dist/{vectorstyle.d.ts → basemap/vectorstyle.d.ts} +12 -10
  21. package/dist/{vectorstyle.js → basemap/vectorstyle.js} +129 -111
  22. package/dist/basemap/vectorstyle.js.map +1 -0
  23. package/dist/{vectortiles.d.ts → basemap/vectortiles.d.ts} +18 -18
  24. package/dist/{vectortiles.js → basemap/vectortiles.js} +157 -109
  25. package/dist/basemap/vectortiles.js.map +1 -0
  26. package/dist/{wmts.d.ts → basemap/wmts.d.ts} +3 -3
  27. package/dist/{wmts.js → basemap/wmts.js} +19 -14
  28. package/dist/basemap/wmts.js.map +1 -0
  29. package/dist/basemap.worker.js +2 -2
  30. package/dist/basemap.worker.js.map +14 -13
  31. package/dist/bind/layers.d.ts +46 -0
  32. package/dist/bind/layers.js +154 -0
  33. package/dist/bind/layers.js.map +1 -0
  34. package/dist/{crs.d.ts → data/crs.d.ts} +11 -11
  35. package/dist/{crs.js → data/crs.js} +38 -34
  36. package/dist/data/crs.js.map +1 -0
  37. package/dist/{gpx.d.ts → data/gpx.d.ts} +2 -2
  38. package/dist/{gpx.js → data/gpx.js} +20 -20
  39. package/dist/data/gpx.js.map +1 -0
  40. package/dist/{kml.d.ts → data/kml.d.ts} +2 -2
  41. package/dist/{kml.js → data/kml.js} +42 -39
  42. package/dist/data/kml.js.map +1 -0
  43. package/dist/{topojson.d.ts → data/topojson.d.ts} +2 -2
  44. package/dist/{topojson.js → data/topojson.js} +15 -10
  45. package/dist/data/topojson.js.map +1 -0
  46. package/dist/{xml.d.ts → data/xml.d.ts} +5 -5
  47. package/dist/{xml.js → data/xml.js} +19 -18
  48. package/dist/data/xml.js.map +1 -0
  49. package/dist/decoder.worker.js +4 -4
  50. package/dist/decoder.worker.js.map +7 -7
  51. package/dist/{decoders.d.ts → engine/decoders.d.ts} +3 -3
  52. package/dist/{decoders.js → engine/decoders.js} +28 -25
  53. package/dist/engine/decoders.js.map +1 -0
  54. package/dist/{loader.d.ts → engine/loader.d.ts} +1 -1
  55. package/dist/{loader.js → engine/loader.js} +17 -14
  56. package/dist/engine/loader.js.map +1 -0
  57. package/dist/{protocol.d.ts → engine/protocol.d.ts} +1 -1
  58. package/dist/engine/protocol.js +3 -0
  59. package/dist/engine/protocol.js.map +1 -0
  60. package/dist/{requests.d.ts → engine/requests.d.ts} +1 -1
  61. package/dist/{requests.js → engine/requests.js} +19 -16
  62. package/dist/engine/requests.js.map +1 -0
  63. package/dist/engine-loader.d.ts +13 -9
  64. package/dist/engine-loader.js +30 -30
  65. package/dist/engine-loader.js.map +1 -1
  66. package/dist/formats.d.ts +3 -3
  67. package/dist/formats.js +3 -3
  68. package/dist/formats.js.map +1 -1
  69. package/dist/geoplateforme.d.ts +13 -13
  70. package/dist/geoplateforme.js +23 -21
  71. package/dist/geoplateforme.js.map +1 -1
  72. package/dist/index.d.ts +30 -29
  73. package/dist/index.js +18 -18
  74. package/dist/index.js.map +1 -1
  75. package/dist/isard_bg.wasm +0 -0
  76. package/dist/isard_core_bg.wasm +0 -0
  77. package/dist/isard_points_bg.wasm +0 -0
  78. package/dist/{extrusions.d.ts → layers/extrusions.d.ts} +1 -1
  79. package/dist/{extrusions.js → layers/extrusions.js} +10 -5
  80. package/dist/layers/extrusions.js.map +1 -0
  81. package/dist/{features.d.ts → layers/features.d.ts} +10 -10
  82. package/dist/{features.js → layers/features.js} +44 -45
  83. package/dist/layers/features.js.map +1 -0
  84. package/dist/{glyphs.d.ts → layers/glyphs.d.ts} +6 -6
  85. package/dist/{glyphs.js → layers/glyphs.js} +45 -41
  86. package/dist/layers/glyphs.js.map +1 -0
  87. package/dist/{icons.d.ts → layers/icons.d.ts} +10 -10
  88. package/dist/{icons.js → layers/icons.js} +84 -69
  89. package/dist/layers/icons.js.map +1 -0
  90. package/dist/{layers.d.ts → layers/layers.d.ts} +9 -4
  91. package/dist/{layers.js → layers/layers.js} +35 -31
  92. package/dist/layers/layers.js.map +1 -0
  93. package/dist/{panoramas.d.ts → layers/panoramas.d.ts} +3 -3
  94. package/dist/{panoramas.js → layers/panoramas.js} +54 -43
  95. package/dist/layers/panoramas.js.map +1 -0
  96. package/dist/{pointclouds.d.ts → layers/pointclouds.d.ts} +5 -5
  97. package/dist/{pointclouds.js → layers/pointclouds.js} +26 -34
  98. package/dist/layers/pointclouds.js.map +1 -0
  99. package/dist/{symbols.d.ts → layers/symbols.d.ts} +3 -3
  100. package/dist/{symbols.js → layers/symbols.js} +69 -72
  101. package/dist/layers/symbols.js.map +1 -0
  102. package/dist/map/basemap-layers.d.ts +26 -0
  103. package/dist/map/basemap-layers.js +61 -0
  104. package/dist/map/basemap-layers.js.map +1 -0
  105. package/dist/map/canvas.d.ts +17 -0
  106. package/dist/map/canvas.js +56 -0
  107. package/dist/map/canvas.js.map +1 -0
  108. package/dist/map/checks.d.ts +11 -0
  109. package/dist/map/checks.js +39 -0
  110. package/dist/map/checks.js.map +1 -0
  111. package/dist/map/credits.d.ts +17 -0
  112. package/dist/map/credits.js +34 -0
  113. package/dist/map/credits.js.map +1 -0
  114. package/dist/map/engine-settings.d.ts +15 -0
  115. package/dist/map/engine-settings.js +45 -0
  116. package/dist/map/engine-settings.js.map +1 -0
  117. package/dist/map/engines.d.ts +41 -0
  118. package/dist/map/engines.js +77 -0
  119. package/dist/map/engines.js.map +1 -0
  120. package/dist/map/events.d.ts +14 -0
  121. package/dist/map/events.js +46 -0
  122. package/dist/map/events.js.map +1 -0
  123. package/dist/map/frame-loop.d.ts +61 -0
  124. package/dist/map/frame-loop.js +139 -0
  125. package/dist/map/frame-loop.js.map +1 -0
  126. package/dist/map/hover-pick.d.ts +30 -0
  127. package/dist/map/hover-pick.js +56 -0
  128. package/dist/map/hover-pick.js.map +1 -0
  129. package/dist/map/layer-spec.d.ts +42 -0
  130. package/dist/map/layer-spec.js +34 -0
  131. package/dist/map/layer-spec.js.map +1 -0
  132. package/dist/map/layer-stack.d.ts +36 -0
  133. package/dist/map/layer-stack.js +101 -0
  134. package/dist/map/layer-stack.js.map +1 -0
  135. package/dist/map/overlay-host.d.ts +36 -0
  136. package/dist/map/overlay-host.js +101 -0
  137. package/dist/map/overlay-host.js.map +1 -0
  138. package/dist/map/panorama-host.d.ts +5 -0
  139. package/dist/map/panorama-host.js +32 -0
  140. package/dist/map/panorama-host.js.map +1 -0
  141. package/dist/map/raster-source.d.ts +11 -0
  142. package/dist/map/raster-source.js +26 -0
  143. package/dist/map/raster-source.js.map +1 -0
  144. package/dist/map/symbol-atlases.d.ts +26 -0
  145. package/dist/map/symbol-atlases.js +59 -0
  146. package/dist/map/symbol-atlases.js.map +1 -0
  147. package/dist/map/viewshed.d.ts +5 -0
  148. package/dist/map/viewshed.js +28 -0
  149. package/dist/map/viewshed.js.map +1 -0
  150. package/dist/map/workers.d.ts +46 -0
  151. package/dist/map/workers.js +73 -0
  152. package/dist/map/workers.js.map +1 -0
  153. package/dist/map.d.ts +46 -127
  154. package/dist/map.js +183 -698
  155. package/dist/map.js.map +1 -1
  156. package/dist/projections.d.ts +3 -3
  157. package/dist/projections.js +2 -2
  158. package/dist/projections.js.map +1 -1
  159. package/dist/react/Control.d.ts +11 -0
  160. package/dist/react/Control.js +18 -0
  161. package/dist/react/Control.js.map +1 -0
  162. package/dist/react/CustomLayer.d.ts +11 -0
  163. package/dist/react/CustomLayer.js +30 -0
  164. package/dist/react/CustomLayer.js.map +1 -0
  165. package/dist/react/IsardMap.d.ts +43 -0
  166. package/dist/react/IsardMap.js +146 -0
  167. package/dist/react/IsardMap.js.map +1 -0
  168. package/dist/react/Layer.d.ts +42 -0
  169. package/dist/react/Layer.js +74 -0
  170. package/dist/react/Layer.js.map +1 -0
  171. package/dist/react/Marker.d.ts +24 -0
  172. package/dist/react/Marker.js +53 -0
  173. package/dist/react/Marker.js.map +1 -0
  174. package/dist/react/Panel.d.ts +11 -0
  175. package/dist/react/Panel.js +37 -0
  176. package/dist/react/Panel.js.map +1 -0
  177. package/dist/react/Popup.d.ts +18 -0
  178. package/dist/react/Popup.js +60 -0
  179. package/dist/react/Popup.js.map +1 -0
  180. package/dist/react/Swipe.d.ts +9 -0
  181. package/dist/react/Swipe.js +41 -0
  182. package/dist/react/Swipe.js.map +1 -0
  183. package/dist/react/Viewshed.d.ts +5 -0
  184. package/dist/react/Viewshed.js +28 -0
  185. package/dist/react/Viewshed.js.map +1 -0
  186. package/dist/react/context.d.ts +32 -0
  187. package/dist/react/context.js +20 -0
  188. package/dist/react/context.js.map +1 -0
  189. package/dist/react/index.d.ts +26 -0
  190. package/dist/react/index.js +22 -0
  191. package/dist/react/index.js.map +1 -0
  192. package/dist/settings.d.ts +6 -10
  193. package/dist/settings.js +19 -17
  194. package/dist/settings.js.map +1 -1
  195. package/dist/types.d.ts +8 -8
  196. package/dist/types.js +1 -0
  197. package/dist/types.js.map +1 -1
  198. package/dist/{controls.d.ts → ui/controls.d.ts} +1 -1
  199. package/dist/{controls.js → ui/controls.js} +6 -4
  200. package/dist/ui/controls.js.map +1 -0
  201. package/dist/{interaction.d.ts → ui/interaction.d.ts} +9 -5
  202. package/dist/{interaction.js → ui/interaction.js} +9 -8
  203. package/dist/ui/interaction.js.map +1 -0
  204. package/dist/{overlay.d.ts → ui/overlay.d.ts} +2 -2
  205. package/dist/{overlay.js → ui/overlay.js} +18 -20
  206. package/dist/ui/overlay.js.map +1 -0
  207. package/dist/{style.d.ts → ui/style.d.ts} +1 -1
  208. package/dist/{style.js → ui/style.js} +2 -2
  209. package/dist/ui/style.js.map +1 -0
  210. package/dist/{animation.d.ts → util/animation.d.ts} +9 -5
  211. package/dist/{animation.js → util/animation.js} +6 -6
  212. package/dist/util/animation.js.map +1 -0
  213. package/dist/{color.d.ts → util/color.d.ts} +1 -1
  214. package/dist/{color.js → util/color.js} +14 -16
  215. package/dist/util/color.js.map +1 -0
  216. package/dist/{geo.d.ts → util/geo.d.ts} +6 -6
  217. package/dist/{geo.js → util/geo.js} +12 -12
  218. package/dist/util/geo.js.map +1 -0
  219. package/dist/util/guards.d.ts +7 -0
  220. package/dist/util/guards.js +8 -0
  221. package/dist/util/guards.js.map +1 -0
  222. package/dist/util/lru.js.map +1 -0
  223. package/dist/wasm/isard.d.ts +168 -0
  224. package/dist/wasm/isard.js +146 -0
  225. package/dist/wasm/isard_core.d.ts +144 -0
  226. package/dist/wasm/isard_core.js +122 -0
  227. package/dist/{basemap.worker.d.ts → workers/basemap.worker.d.ts} +17 -7
  228. package/dist/workers/basemap.worker.js +658 -0
  229. package/dist/workers/basemap.worker.js.map +1 -0
  230. package/package.json +24 -1
  231. package/vite.js +1 -1
  232. package/dist/animation.js.map +0 -1
  233. package/dist/basemaps.js.map +0 -1
  234. package/dist/color.js.map +0 -1
  235. package/dist/controls.js.map +0 -1
  236. package/dist/crs.js.map +0 -1
  237. package/dist/decoders.js.map +0 -1
  238. package/dist/extrusions.js.map +0 -1
  239. package/dist/features.js.map +0 -1
  240. package/dist/geo.js.map +0 -1
  241. package/dist/glyphs.js.map +0 -1
  242. package/dist/gpx.js.map +0 -1
  243. package/dist/grid.js.map +0 -1
  244. package/dist/icons.js.map +0 -1
  245. package/dist/interaction.js.map +0 -1
  246. package/dist/kml.js.map +0 -1
  247. package/dist/layers.js.map +0 -1
  248. package/dist/loader.js.map +0 -1
  249. package/dist/lru.js.map +0 -1
  250. package/dist/mvt.js.map +0 -1
  251. package/dist/overlay.js.map +0 -1
  252. package/dist/panoramas.js.map +0 -1
  253. package/dist/pmtiles.js.map +0 -1
  254. package/dist/pointclouds.js.map +0 -1
  255. package/dist/protocol.js +0 -2
  256. package/dist/protocol.js.map +0 -1
  257. package/dist/requests.js.map +0 -1
  258. package/dist/resample.js.map +0 -1
  259. package/dist/style.js.map +0 -1
  260. package/dist/symbols.js.map +0 -1
  261. package/dist/terrain.js.map +0 -1
  262. package/dist/topojson.js.map +0 -1
  263. package/dist/vectorstyle.js.map +0 -1
  264. package/dist/vectortiles.js.map +0 -1
  265. package/dist/wmts.js.map +0 -1
  266. package/dist/xml.js.map +0 -1
  267. /package/dist/{lru.d.ts → util/lru.d.ts} +0 -0
  268. /package/dist/{lru.js → util/lru.js} +0 -0
package/dist/map.js CHANGED
@@ -2,154 +2,91 @@
2
2
  * The map: canvas, camera, layers, events, overlays, credits and the frame loop. Frames are drawn on demand only: an
3
3
  * idle map costs nothing. Hover events are coalesced to one per frame, so the values computed for a mouse position are
4
4
  * drawn in the same frame. The engine's loading is in engine-loader.ts, its settings from the options in settings.ts,
5
- * the camera animations in animation.ts.
5
+ * the camera animations in animation.ts; the parts of the map with a state of their own (the drawing order of the
6
+ * layers, the overlays and controls, the frame loop, the hover picking, the workers…) in map/.
6
7
  */
7
- import { easeTo, flyTo } from "./animation.js";
8
- import { BASEMAPS, HILLSHADE, RENAMED_BASEMAPS, SEA } from "./basemaps.js";
9
- import { AttributionControl } from "./controls.js";
10
- import { destroyedEngine, engineUrl, loadEngine, loadFullEngineLater, preloadFullEngine, startTileLoader, } from "./engine-loader.js";
11
- import { ExtrusionLayer, extrusionStyle } from "./extrusions.js";
12
- import { CircleLayer, circleStyle, FillLayer, fillStyle, LineLayer, lineStyle, } from "./features.js";
8
+ import { SEA } from "./basemap/basemaps.js";
9
+ import { defaultWorkerUrl, pmtilesLayer, VectorTiles, } from "./basemap/vectortiles.js";
10
+ import { onHeaderRules } from "./engine/requests.js";
11
+ import { destroyedEngine, engineUrl, loadEngine } from "./engine-loader.js";
13
12
  import { IGN_PRESETS } from "./geoplateforme.js";
14
- import { Glyphs } from "./glyphs.js";
15
- import { equirectangularGrid, gridLoader, gridZooms, resolveGrid } from "./grid.js";
16
- import { Icons, readPixels } from "./icons.js";
17
- import { Interaction } from "./interaction.js";
18
- import { CustomLayer, GroundLayer, ModelLayer, NetworkLayer, RasterLayer, Tiles3dLayer, } from "./layers.js";
19
- import { loadPanoramas } from "./panoramas.js";
20
- import { PointCloudLayer } from "./pointclouds.js";
21
- import { headerRules, onHeaderRules } from "./requests.js";
22
- import { atmosphereSettings, bodySettings, haloSettings, rgb, terrainSettings, } from "./settings.js";
23
- import { injectStyle } from "./style.js";
24
- import { SymbolLayer, symbolStyle } from "./symbols.js";
25
- import { encodedTerrainLoader } from "./terrain.js";
26
- import { BasemapWorkers, defaultWorkerCount, defaultWorkerUrl, pmtilesLayer, pmtilesRasterLoader, VECTOR_BASEMAPS, VectorTiles, } from "./vectortiles.js";
27
- /** Reports an error without throwing it here: the console shows it, as an uncaught one */
28
- function report(error) {
29
- if (typeof reportError === 'function')
30
- reportError(error);
31
- else
32
- queueMicrotask(() => {
33
- throw error;
34
- });
35
- }
36
- /** Checks a network's arrays before the engine reads them: node index pairs, indices of existing nodes */
37
- function validateNetwork(nodes, edges) {
38
- if (nodes.length % 2 !== 0)
39
- throw new Error(`isard: network nodes must be lon, lat pairs (length ${nodes.length})`);
40
- if (edges.length % 2 !== 0)
41
- throw new Error(`isard: network edges must be node index pairs (length ${edges.length})`);
42
- const count = nodes.length / 2;
43
- for (let i = 0; i < edges.length; i += 1) {
44
- const node = edges[i];
45
- if (node >= count)
46
- throw new Error(`isard: network edge ${i >> 1} refers to node ${node}, of ${count} nodes`);
47
- }
48
- }
49
- const CORNERS = ['top-left', 'top-right', 'bottom-left', 'bottom-right'];
50
- /** Frames the hover picking waits for its answer at most (usually one) */
51
- const MAX_POLLS = 30;
52
- /** The name of a basemap, checked: an unknown one throws */
53
- function checkBasemap(basemap) {
54
- const renamed = RENAMED_BASEMAPS[basemap];
55
- if (renamed)
56
- throw new Error(`isard: the basemap '${basemap}' is now named '${renamed}'`);
57
- if (basemap !== 'none' && !(basemap in VECTOR_BASEMAPS) && !(basemap in BASEMAPS)) {
58
- throw new Error(`isard: unknown basemap '${basemap}'`);
59
- }
60
- return basemap;
61
- }
13
+ import { ExtrusionLayer, extrusionStyle } from "./layers/extrusions.js";
14
+ import { CircleLayer, circleStyle, FillLayer, fillStyle, LineLayer, lineStyle, } from "./layers/features.js";
15
+ import { CustomLayer, GroundLayer, ModelLayer, NetworkLayer, RasterLayer, Tiles3dLayer, } from "./layers/layers.js";
16
+ import { loadPanoramas } from "./layers/panoramas.js";
17
+ import { PointCloudLayer } from "./layers/pointclouds.js";
18
+ import { SymbolLayer, symbolStyle } from "./layers/symbols.js";
19
+ import { BasemapLayers } from "./map/basemap-layers.js";
20
+ import { createCanvas, labelCanvas, watchContext, watchPixelRatio } from "./map/canvas.js";
21
+ import { checkBasemap, report, validateNetwork } from "./map/checks.js";
22
+ import { Credits } from "./map/credits.js";
23
+ import { mapSettings, sendHeaders } from "./map/engine-settings.js";
24
+ import { MapEngines } from "./map/engines.js";
25
+ import { MapListeners } from "./map/events.js";
26
+ import { FrameLoop } from "./map/frame-loop.js";
27
+ import { HoverPick } from "./map/hover-pick.js";
28
+ import { addSpec } from "./map/layer-spec.js";
29
+ import { LayerStack } from "./map/layer-stack.js";
30
+ import { OverlayHost } from "./map/overlay-host.js";
31
+ import { panoramaHost } from "./map/panorama-host.js";
32
+ import { rasterSource } from "./map/raster-source.js";
33
+ import { SymbolAtlases } from "./map/symbol-atlases.js";
34
+ import { applyViewshed } from "./map/viewshed.js";
35
+ import { MapWorkers } from "./map/workers.js";
36
+ import { atmosphereSettings, rgb, terrainSettings } from "./settings.js";
37
+ import { AttributionControl } from "./ui/controls.js";
38
+ import { Interaction } from "./ui/interaction.js";
39
+ import { easeTo, flyTo } from "./util/animation.js";
40
+ /** A layer whose picked features come back with their properties (lines, circles, fills, extrusions, 3D Tiles…) */
41
+ const hasFeatures = (layer) => 'featureAt' in layer && typeof layer.featureAt === 'function';
62
42
  /** A map (see `GeoMap.create`); layers take it as their host */
63
43
  export class GeoMap {
64
44
  canvas;
65
- #glyphs;
66
- #icons;
67
- /** Layers new layers go under */
68
- onTop = new Set();
69
- /** The vector basemap, if any: its tiles' workers and its labels */
70
- vector;
71
- glyphsUrl;
72
- basemapWorkerUrl;
73
- /** Texture size of the vector basemap's tiles */
74
- basemapTileSize;
75
- container;
76
- /** Markers and popups, over the canvas */
77
- overlay;
78
- corners;
79
- controls = new Map();
80
- positioned = new Set();
81
- /** Canvas size in CSS pixels, kept on resize so placing overlays never reads the layout */
82
- size = [0, 0];
83
45
  #engine;
84
- /** Layers in drawing order (bottom first), to recreate them after a context loss */
85
- layers = [];
46
+ /** The engine class (core, or with 3D Tiles) and the engine with 3D Tiles loaded when needed */
47
+ engines;
48
+ /** The layers in drawing order and those kept on top */
49
+ stack = new LayerStack(() => this.#engine);
50
+ /** The basemap and the hillshade */
51
+ basemaps;
52
+ /** The glyphs and icons of the symbol layers */
53
+ atlases;
54
+ /** The workers of the vector tiles, the relief and the reprojected rasters */
55
+ workers;
56
+ container;
57
+ /** The canvas in the container, the markers and popups over it, the controls in its corners */
58
+ overlays;
86
59
  settings;
87
60
  /** The WebGL context is lost: nothing is drawn until it is restored */
88
61
  lost = false;
89
- listeners = {
90
- movestart: new Set(),
91
- move: new Set(),
92
- moveend: new Set(),
93
- resize: new Set(),
94
- hover: new Set(),
95
- hoverfeature: new Set(),
96
- click: new Set(),
97
- render: new Set(),
98
- idle: new Set(),
99
- leave: new Set(),
100
- };
62
+ gone = false;
63
+ listeners = new MapListeners();
64
+ /** Frames drawn on demand, with their events */
65
+ frames;
101
66
  resizeObserver;
102
67
  interaction;
103
- loader;
104
- /** The class of the engine: without 3D Tiles until a 3D Tiles layer is added, unless asked for from the start */
105
- engineClass;
106
- /** The module of the engine with 3D Tiles once it runs this map (the decoding workers run it too) */
107
- tiles3dModule;
108
- /** The engine with 3D Tiles is loading, for the 3D Tiles layers added on the core engine */
109
- upgrading;
110
- /** What loading the engine with 3D Tiles and its workers needs */
111
- tiles3dOptions;
112
- gone = false;
113
68
  /** The map is of the Earth: a basemap by default */
114
69
  earth;
115
70
  /** The body, as the option named it */
116
71
  body;
117
- basemap;
118
- hillshade;
119
- /** Called when the credits of the layers or relief change (the attribution controls) */
120
- attributionListeners = new Set();
121
- /** Credits of the raster layers that have some (basemaps, hillshade, the Géoplateforme's rasters…) */
122
- rasterCredits = new Map();
123
- animation;
124
- hoverPoint;
125
- /** Frame polling the engine for the answer of the hover picking, 0 if none */
126
- hoverPoll = 0;
127
- /** Polls of the hover picking under way: given up after `MAX_POLLS` (an answer that never comes) */
128
- hoverPolls = 0;
72
+ /** Credits of the raster layers that have some, and the attribution controls told when they change */
73
+ credits = new Credits();
74
+ /** The picking for 'hoverfeature', its answer emitted on a later frame */
75
+ hoverPick = new HoverPick(() => this.engine, () => this.lost, (result) => this.emit('hoverfeature', {
76
+ point: [result.x, result.y],
77
+ lonLat: result.lonLat,
78
+ hit: result.hit && this.featureHit(result.hit),
79
+ }));
129
80
  /** What 'hoverfeature' looks for: within `radius` CSS pixels, in `layers` only if given */
130
81
  hoverPicking = {};
131
- frameRequest = 0;
132
- lastCamera = '';
133
- /** The camera moved since the last 'moveend' */
134
- moving = false;
135
- /** The last frame found everything loaded ('idle' sent) */
136
- settled = false;
137
- inFrame = false;
138
- /** The camera of the frame being drawn: what `getCamera` gives the custom layers while the engine is busy */
139
- frameCamera;
140
82
  /** Vector tiles made by `vectorTiles()`, destroyed with the map */
141
83
  vectorSets = new Set();
142
- /** The workers drawing vector tiles, shared by every `VectorTiles` of the map; created on first use */
143
- basemapPool;
144
84
  /** Stops watching the device pixel ratio */
145
85
  unwatchRatio;
146
86
  /** Stops following the page's request headers (`setRequestHeaders`) */
147
87
  unwatchHeaders = () => undefined;
148
88
  /** Stops listening to the canvas's context loss and restoration */
149
89
  unwatchContext;
150
- /** The loaders of the relief by encoding, URL and floor: the same loader for the same tiles (the engine keeps its
151
- * tiles when only the exaggeration changes) */
152
- terrainLoaders = new Map();
153
90
  /** Loads the engine (once per page) and creates a map filling `container` */
154
91
  static async create(container, given = {}) {
155
92
  // A preset under the options given
@@ -167,66 +104,40 @@ export class GeoMap {
167
104
  return map;
168
105
  }
169
106
  constructor(container, options, engineClass, tiles3dModule) {
170
- this.engineClass = engineClass;
171
- this.tiles3dModule = tiles3dModule;
172
- this.tiles3dOptions = { ...options };
173
- this.glyphsUrl = options.glyphs;
174
- this.basemapWorkerUrl = options.basemapWorkerUrl ?? defaultWorkerUrl(options.assetsUrl);
175
- this.basemapTileSize = options.basemapTileSize;
107
+ this.engines = new MapEngines(engineClass, tiles3dModule, options, () => this.gone);
108
+ const workerUrl = options.basemapWorkerUrl ?? defaultWorkerUrl(options.assetsUrl);
109
+ this.workers = new MapWorkers(workerUrl, options.workers, () => this.gone, this);
110
+ this.basemaps = new BasemapLayers(this, this.stack, workerUrl, options.basemapTileSize);
176
111
  this.container = container;
177
- this.canvas = document.createElement('canvas');
178
- Object.assign(this.canvas.style, { display: 'block', width: '100%', height: '100%', touchAction: 'none' });
179
- // Overlays and controls are positioned against the container
180
- injectStyle(container.ownerDocument);
181
- if (getComputedStyle(container).position === 'static')
182
- container.style.position = 'relative';
183
- container.classList.add('isard-map');
184
- this.overlay = document.createElement('div');
185
- this.overlay.className = 'isard-overlay';
186
- const corner = (position) => {
187
- const element = document.createElement('div');
188
- element.className = `isard-ctrl-${position}`;
189
- return element;
190
- };
191
- this.corners = {
192
- 'top-left': corner('top-left'),
193
- 'top-right': corner('top-right'),
194
- 'bottom-left': corner('bottom-left'),
195
- 'bottom-right': corner('bottom-right'),
196
- };
197
- container.append(this.canvas, this.overlay, ...CORNERS.map((position) => this.corners[position]));
112
+ this.canvas = createCanvas();
113
+ this.atlases = new SymbolAtlases(this.canvas, options.glyphs, () => this.#engine);
114
+ this.overlays = new OverlayHost(container, this.canvas, this);
198
115
  this.earth = (options.body ?? 'earth') === 'earth';
199
116
  this.body = options.body;
200
- const body = bodySettings(options.body);
201
- this.settings = {
202
- body,
203
- zoomRange: [options.minZoom ?? 0, options.maxZoom ?? 22],
204
- maxPitch: options.maxPitch ?? 70,
205
- globe: (options.projection ?? 'globe') === 'globe',
206
- sky: options.sky ?? true,
207
- atmosphere: atmosphereSettings(options.atmosphere),
208
- terrain: terrainSettings(options.terrain, options.body, body.radius, this.terrainLoader),
209
- loading: options.loading ?? 'immediate',
210
- background: rgb(options.background ?? (this.earth ? '#f2f2f0' : '#000000')),
211
- globeColor: this.earth ? SEA['osm-positron'] : rgb(body.color),
212
- };
117
+ this.settings = mapSettings(options, this.earth, this.workers.terrainLoader);
118
+ this.frames = new FrameLoop({
119
+ engine: () => this.engine,
120
+ camera: () => this.getCamera(),
121
+ lost: () => this.lost,
122
+ interacting: () => this.interaction?.active === true,
123
+ customLayers: () => this.layers.some((layer) => layer instanceof CustomLayer),
124
+ upgrading: () => this.engines.upgrading !== undefined,
125
+ drawn: (hovered) => {
126
+ // The picking pass sees the frame just drawn; its answer comes on a later frame
127
+ if (hovered && this.listeners.has('hoverfeature'))
128
+ this.hoverPick.request(hovered, this.hoverPicking);
129
+ // Overlays follow the frame just drawn
130
+ this.overlays.positionAll();
131
+ },
132
+ }, this.listeners);
213
133
  this.#engine = this.createEngine();
214
134
  // Once it has an engine: a map whose creation failed before is not left following them
215
135
  this.unwatchHeaders = onHeaderRules((rules) => sendHeaders(this.#engine, rules));
216
136
  // The browser may take the GPU away (driver reset, background tab on mobile): the map waits, then rebuilds
217
- const lost = (event) => {
218
- event.preventDefault();
137
+ this.unwatchContext = watchContext(this.canvas, () => {
219
138
  this.lost = true;
220
- cancelAnimationFrame(this.frameRequest);
221
- this.frameRequest = 0;
222
- };
223
- const restored = () => this.restore();
224
- this.canvas.addEventListener('webglcontextlost', lost);
225
- this.canvas.addEventListener('webglcontextrestored', restored);
226
- this.unwatchContext = () => {
227
- this.canvas.removeEventListener('webglcontextlost', lost);
228
- this.canvas.removeEventListener('webglcontextrestored', restored);
229
- };
139
+ this.frames.cancel();
140
+ }, () => this.restore());
230
141
  const start = this.earth
231
142
  ? { center: [2.4246, 48.8457], zoom: 13, pitch: 0, bearing: 0 }
232
143
  : { center: [0, 0], zoom: 2, pitch: 0, bearing: 0 };
@@ -234,18 +145,12 @@ export class GeoMap {
234
145
  this.setBasemap(options.basemap ?? (this.earth ? 'osm-positron' : 'none'), options.hillshade ?? 0);
235
146
  this.resizeObserver = new ResizeObserver(() => this.resize());
236
147
  this.resizeObserver.observe(container);
237
- this.watchRatio();
148
+ this.unwatchRatio = watchPixelRatio(() => this.resize());
238
149
  this.resize();
239
150
  const interactive = options.interactive ?? true;
240
151
  const keyboard = interactive && (options.keyboard ?? true);
241
152
  this.interaction = interactive ? new Interaction(this, { keyboard }) : undefined;
242
- // Reached with Tab when the keys move it; named for screen readers either way
243
- this.canvas.classList.add('isard-canvas');
244
- this.canvas.setAttribute('role', keyboard ? 'application' : 'region');
245
- this.canvas.setAttribute('aria-roledescription', 'map');
246
- this.canvas.setAttribute('aria-label', options.ariaLabel ?? 'Map');
247
- if (keyboard)
248
- this.canvas.tabIndex = 0;
153
+ labelCanvas(this.canvas, keyboard, options.ariaLabel ?? 'Map');
249
154
  const attribution = options.attributionControl ?? true;
250
155
  if (attribution !== false) {
251
156
  this.addControl(new AttributionControl(attribution === true ? {} : attribution), 'bottom-right');
@@ -262,48 +167,26 @@ export class GeoMap {
262
167
  get engine() {
263
168
  return this.#engine;
264
169
  }
170
+ /** Layers in drawing order (bottom first) */
171
+ get layers() {
172
+ return this.stack.layers;
173
+ }
174
+ /** Layers new layers go under */
175
+ get onTop() {
176
+ return this.stack.onTop;
177
+ }
265
178
  createEngine() {
266
- const engine = new this.engineClass(this.canvas, () => this.requestRender());
267
- // The body first: the layers' shaders may take its size
268
- const { radius, flattening, atmosphere: air, relief } = this.settings.body;
269
- engine.setBody(radius, flattening, haloSettings(air), ...relief);
270
- sendHeaders(engine, headerRules());
271
- const { zoomRange, maxPitch, globe, sky, atmosphere, terrain, loading, background, globeColor } = this.settings;
272
- engine.setZoomRange(...zoomRange);
273
- engine.setMaxPitch(maxPitch);
274
- engine.setGlobe(globe);
275
- engine.setSky(sky);
276
- engine.setAtmosphere(atmosphere);
277
- engine.setTerrain(terrain?.url, terrain?.exaggeration ?? 1, terrain?.maxZoom, terrain?.geoid, terrain?.loader);
278
- engine.setImmediateLoading(loading === 'immediate');
279
- engine.setBackground(...background);
280
- engine.setGlobeColor(...globeColor);
281
- this.loader ??= this.startLoader(engine);
282
- if (this.loader)
283
- engine.setTileLoader(this.loader);
284
- return engine;
285
- }
286
- /** The decoding workers of 3D Tiles, with the engine that has them (they run its module) and unless turned off */
287
- startLoader(engine) {
288
- return this.tiles3dModule && startTileLoader(this.tiles3dModule, this.tiles3dOptions, engine);
179
+ return this.engines.create(this.canvas, () => this.requestRender(), this.settings);
289
180
  }
290
181
  /** A 3D Tiles layer or a model on the core engine: loads the engine with 3D Tiles, then rebuilds the map on it
291
182
  * (camera, settings and layers kept, as after a context loss); the layer is created then */
292
183
  upgrade() {
293
- this.upgrading ??= loadFullEngineLater(engineUrl('full', this.tiles3dOptions)).then(({ module, Engine }) => {
294
- this.upgrading = undefined;
295
- if (this.gone)
296
- return;
297
- this.engineClass = Engine;
298
- this.tiles3dModule = module;
184
+ this.engines.upgrade(() => {
299
185
  // A lost context: the engine is rebuilt on it once restored
300
186
  if (!this.lost)
301
187
  this.restore();
302
188
  }, (error) => {
303
189
  // The 3D Tiles layers stay empty; the next one added tries again
304
- this.upgrading = undefined;
305
- if (this.gone)
306
- return;
307
190
  report(error);
308
191
  this.requestRender();
309
192
  });
@@ -311,14 +194,12 @@ export class GeoMap {
311
194
  /** Downloads and compiles the engine with 3D Tiles once the map is idle (`tiles3d: 'preload'`), without running
312
195
  * it: the first 3D Tiles layer then only instantiates it */
313
196
  preloadTiles3d() {
314
- const url = engineUrl('full', this.tiles3dOptions);
315
- const later = () => preloadFullEngine(url, () => !this.gone && !this.tiles3dModule);
316
- if (this.settled)
317
- later();
197
+ if (this.frames.settled)
198
+ this.engines.preload();
318
199
  else {
319
200
  const off = this.on('idle', () => {
320
201
  off();
321
- later();
202
+ this.engines.preload();
322
203
  });
323
204
  }
324
205
  }
@@ -326,41 +207,15 @@ export class GeoMap {
326
207
  restore() {
327
208
  if (this.gone)
328
209
  return;
329
- this.cancelHoverPick();
210
+ this.hoverPick.cancel();
330
211
  const camera = this.getCamera();
331
212
  this.#engine.free();
332
213
  this.#engine = this.createEngine();
333
214
  this.lost = false;
334
215
  this.setCamera(camera);
335
- this.#glyphs?.attach(this.#engine);
336
- this.#icons?.attach(this.#engine);
337
- const engine = this.#engine;
338
- // No layer refers to an id of the old engine while the others are created
339
- for (const layer of this.layers)
340
- layer.forgetEngine();
341
- for (const layer of this.layers) {
342
- // Still waiting for the engine with 3D Tiles (loading, or failed): created with it
343
- if (layer.needsFullEngine && !this.tiles3dModule)
344
- continue;
345
- // A layer that fails is reported and left out; the others are created
346
- try {
347
- layer.attach(engine);
348
- }
349
- catch (error) {
350
- report(error);
351
- }
352
- }
353
- // Links between layers, once every layer has its new id (buildings colored by a network above them)
354
- for (const layer of this.layers) {
355
- if (layer.id < 0)
356
- continue;
357
- try {
358
- layer.relink(engine);
359
- }
360
- catch (error) {
361
- report(error);
362
- }
363
- }
216
+ this.atlases.attach(this.#engine);
217
+ // Those still waiting for the engine with 3D Tiles (loading, or failed) are created with it
218
+ this.stack.reattach(this.#engine, (layer) => layer.needsFullEngine && !this.engines.tiles3d);
364
219
  this.applySwipe();
365
220
  // A street-level eye stays one in the new engine
366
221
  if (this.#firstPerson)
@@ -368,9 +223,10 @@ export class GeoMap {
368
223
  // What the old engine drew over the scene: the viewshed, the panoramas shown
369
224
  if (this.viewshed)
370
225
  this.setViewshed(this.viewshed);
371
- for (const set of this.panoramaSets)
226
+ this.panoramaSets.forEach((set) => {
372
227
  if (set.current)
373
228
  void set.show(set.current.index).catch(report);
229
+ });
374
230
  this.resize();
375
231
  }
376
232
  /** The street-level eye, while there is one (`setFirstPerson`) */
@@ -392,13 +248,8 @@ export class GeoMap {
392
248
  this.setCamera({ center: was.lonLat, zoom: 18, pitch: 60, bearing: was.bearing });
393
249
  return;
394
250
  }
395
- const eye = {
396
- height: 1.7,
397
- pitch: 90,
398
- fov: 75,
399
- ...view,
400
- lonLat: [view.lonLat[0], view.lonLat[1]],
401
- };
251
+ const lonLat = [view.lonLat[0], view.lonLat[1]];
252
+ const eye = { height: 1.7, pitch: 90, fov: 75, ...view, lonLat };
402
253
  eye.pitch = Math.max(1, Math.min(179, eye.pitch));
403
254
  eye.fov = Math.max(10, Math.min(140, eye.fov));
404
255
  eye.bearing = ((eye.bearing % 360) + 360) % 360;
@@ -425,33 +276,22 @@ export class GeoMap {
425
276
  }
426
277
  /** @internal Forgets a removed layer */
427
278
  detach(layer) {
428
- this.unlist(layer);
429
- this.onTop.delete(layer);
430
- if (this.rasterCredits.delete(layer))
431
- this.attributionChanged();
279
+ this.stack.forget(layer);
280
+ this.credits.forget(layer);
432
281
  }
433
282
  /** @internal The layer is on the map */
434
283
  hasLayer(layer) {
435
- return !this.gone && this.layers.includes(layer);
436
- }
437
- /** Takes a layer out of the drawing order (to put it back elsewhere, or for good) */
438
- unlist(layer) {
439
- const index = this.layers.indexOf(layer);
440
- if (index >= 0)
441
- this.layers.splice(index, 1);
284
+ return !this.gone && this.stack.has(layer);
442
285
  }
443
286
  addLayer(layer) {
444
287
  // On the core engine, a 3D Tiles layer or a model waits for the engine with 3D Tiles: it is created with the
445
288
  // others on it
446
- if (layer.needsFullEngine && !this.tiles3dModule)
289
+ if (layer.needsFullEngine && !this.engines.tiles3d)
447
290
  this.upgrade();
448
291
  else
449
292
  layer.attach(this.engine);
450
- this.layers.push(layer);
451
293
  // Under the layers kept on top (the basemap's labels)
452
- const top = this.layers.find((l) => this.onTop.has(l) && l !== layer);
453
- if (top)
454
- this.moveLayer(layer, top);
294
+ this.stack.add(layer);
455
295
  this.requestRender();
456
296
  return layer;
457
297
  }
@@ -461,54 +301,21 @@ export class GeoMap {
461
301
  }
462
302
  /** @internal Moves a layer under `before`, or on top of all */
463
303
  moveLayer(layer, before) {
464
- const handle = layer;
465
- this.unlist(handle);
466
- const index = before ? this.layers.indexOf(before) : -1;
467
- const at = index < 0 ? this.layers.length : index;
468
- this.layers.splice(at, 0, handle);
469
- if (handle.id >= 0)
470
- this.engine.moveLayer(handle.id, this.engineIndex(at));
304
+ this.stack.move(layer, before);
471
305
  this.requestRender();
472
306
  }
473
307
  /** @internal See `LayerHost.replaceLayer` */
474
308
  replaceLayer(layer, old) {
475
- this.engine.removeLayer(old);
476
- const index = this.layers.indexOf(layer);
477
- if (index >= 0)
478
- this.engine.moveLayer(layer.id, this.engineIndex(index));
479
- }
480
- /** Index in the engine of the layer at `index`: the layers waiting for the engine with 3D Tiles are not in it yet */
481
- engineIndex(index) {
482
- let count = 0;
483
- for (let i = 0; i < index; i += 1)
484
- if ((this.layers[i]?.id ?? -1) >= 0)
485
- count += 1;
486
- return count;
487
- }
488
- /** Moves a layer under all the others */
489
- toBottom(layer) {
490
- this.engine.moveLayer(layer.id, 0);
491
- this.unlist(layer);
492
- this.layers.unshift(layer);
309
+ this.stack.replace(layer, old);
493
310
  }
494
311
  // ── Events ──
495
312
  /** Listens to an event; returns the function that stops listening */
496
313
  on(type, listener) {
497
- const listeners = this.listeners[type];
498
- listeners.add(listener);
499
- return () => listeners.delete(listener);
314
+ return this.listeners.on(type, listener);
500
315
  }
501
316
  /** @internal Calls the listeners; one that throws is reported and does not stop the others nor the frame */
502
317
  emit(type, event) {
503
- const listeners = this.listeners[type];
504
- for (const listener of listeners) {
505
- try {
506
- listener(event);
507
- }
508
- catch (error) {
509
- report(error);
510
- }
511
- }
318
+ this.listeners.emit(type, event);
512
319
  }
513
320
  // ── Camera ──
514
321
  /**
@@ -543,7 +350,7 @@ export class GeoMap {
543
350
  swipe: (options) => this.setSwipe(options),
544
351
  };
545
352
  getCamera() {
546
- return this.frameCamera ?? this.engine.camera();
353
+ return this.frames.camera ?? this.engine.camera();
547
354
  }
548
355
  setCamera(camera) {
549
356
  const c = { ...this.getCamera(), ...camera };
@@ -614,57 +421,34 @@ export class GeoMap {
614
421
  this.engine.setSky(enabled);
615
422
  this.requestRender();
616
423
  }
424
+ /** The viewshed set, set again in a new engine */
425
+ viewshed;
617
426
  /** Tints what can be seen from a point and what is hidden from it, within a range, all around or within a cone
618
427
  * (`direction`): the relief, extrusions, 3D Tiles and models hide what lies behind them (only what the map has
619
428
  * loaded for its view). Several observers (16 at most): what any of them sees is seen, what is hidden from all those
620
429
  * that reach it is hidden, in the colors and opacity of the first. Undefined or none removes it */
621
- /** The viewshed set, set again in a new engine */
622
- viewshed;
623
430
  setViewshed(options) {
624
431
  this.viewshed = options;
625
- let observers = [];
626
- if (options)
627
- observers = 'position' in options ? [options] : options;
628
- const [first] = observers;
629
- if (!first) {
630
- this.engine.clearViewshed();
631
- return;
632
- }
633
- this.engine.setViewshed(new Float64Array(observers.flatMap((o) => [
634
- o.position[0],
635
- o.position[1],
636
- o.height ?? 1.7,
637
- o.range ?? 1000,
638
- ...(o.direction === undefined
639
- ? [Number.NaN, 0, 0, 0]
640
- : [o.direction, o.tilt ?? 0, o.fov ?? 60, o.verticalFov ?? 40]),
641
- ])), new Float32Array(rgb(first.visibleColor ?? '#2f9e44')), new Float32Array(rgb(first.hiddenColor ?? '#e03131')), first.opacity ?? 0.7, new Float32Array(first.coneColor ? rgb(first.coneColor) : []));
642
- this.requestRender();
432
+ if (applyViewshed(this.engine, options))
433
+ this.requestRender();
643
434
  }
644
435
  /** Relief under the basemap: true for the body's terrain model (on the Earth the worldwide Terrain Tiles, NASA's
645
436
  * on the Moon and Mars), one of the Earth's models by name (`'ign'`), its options, or false; on another body, its
646
437
  * options with a `url` */
647
438
  setTerrain(terrain) {
648
- this.settings.terrain = terrainSettings(terrain, this.body, this.settings.body.radius, this.terrainLoader);
439
+ this.settings.terrain = terrainSettings(terrain, this.body, this.settings.body.radius, this.workers.terrainLoader);
649
440
  const t = this.settings.terrain;
650
441
  this.engine.setTerrain(t?.url, t?.exaggeration ?? 1, t?.maxZoom, t?.geoid, t?.loader);
651
- this.attributionChanged();
442
+ this.credits.changed();
652
443
  this.requestRender();
653
444
  }
654
445
  /** @internal The credits of the raster layers (the basemap first) and of the relief in use, each once */
655
446
  attributions() {
656
- const layers = this.layers.map((layer) => this.rasterCredits.get(layer));
657
- const credits = [...layers, this.settings.terrain?.attribution];
658
- return [...new Set(credits.filter((c) => c !== undefined))];
447
+ return this.credits.of(this.layers, this.settings.terrain?.attribution);
659
448
  }
660
449
  /** @internal Calls `listener` when the credits change; returns what stops it */
661
450
  onAttributionChange(listener) {
662
- this.attributionListeners.add(listener);
663
- return () => this.attributionListeners.delete(listener);
664
- }
665
- attributionChanged() {
666
- for (const listener of this.attributionListeners)
667
- listener();
451
+ return this.credits.onChange(listener);
668
452
  }
669
453
  /** Color behind the map, a CSS color (its alpha ignored) */
670
454
  setBackground(color) {
@@ -699,47 +483,9 @@ export class GeoMap {
699
483
  const layer = this.layers.find((l) => l.id === hit.layer);
700
484
  if (!layer)
701
485
  return undefined;
702
- const feature = 'featureAt' in layer && typeof layer.featureAt === 'function'
703
- ? layer.featureAt(hit.index)
704
- : undefined;
486
+ const feature = hasFeatures(layer) ? layer.featureAt(hit.index) : undefined;
705
487
  return { layer, index: hit.index, feature };
706
488
  }
707
- /** Asks the engine what is under the pointer; its answer, read without waiting for the GPU, comes on a later frame */
708
- requestHoverPick(point) {
709
- const { radius = 0, layers } = this.hoverPicking;
710
- const only = layers && Uint32Array.from(layers.map((l) => l.id));
711
- // The engine cannot pick (no request in flight): the poll under way would wait for nothing
712
- if (!this.engine.requestPick(point[0], point[1], radius, only)) {
713
- this.cancelHoverPick();
714
- return;
715
- }
716
- // A new request replaces the one in flight (in the engine too): one poll loop at most
717
- this.hoverPolls = 0;
718
- if (!this.hoverPoll)
719
- this.hoverPoll = requestAnimationFrame(this.pollHoverPick);
720
- }
721
- pollHoverPick = () => {
722
- this.hoverPoll = 0;
723
- if (this.lost)
724
- return;
725
- const result = this.engine.pollPick();
726
- if (!result) {
727
- // The GPU is not done yet (usually a frame): asked again next frame, without drawing; given up at last
728
- this.hoverPolls += 1;
729
- if (this.hoverPolls < MAX_POLLS)
730
- this.hoverPoll = requestAnimationFrame(this.pollHoverPick);
731
- return;
732
- }
733
- this.emit('hoverfeature', {
734
- point: [result.x, result.y],
735
- lonLat: result.lonLat,
736
- hit: result.hit && this.featureHit(result.hit),
737
- });
738
- };
739
- cancelHoverPick() {
740
- cancelAnimationFrame(this.hoverPoll);
741
- this.hoverPoll = 0;
742
- }
743
489
  /** Screen point (CSS pixels) of a position on the ground (the relief), or `height` metres above it, undefined
744
490
  * behind the camera */
745
491
  project(lonLat, height = 0) {
@@ -755,41 +501,13 @@ export class GeoMap {
755
501
  */
756
502
  async addPanoramas(options) {
757
503
  // Drawn by the engine with 3D Tiles: loaded first if needed
758
- if (!this.tiles3dModule) {
504
+ if (!this.engines.tiles3d) {
759
505
  this.upgrade();
760
- await this.upgrading;
761
- if (!this.tiles3dModule)
506
+ await this.engines.upgrading;
507
+ if (!this.engines.tiles3d)
762
508
  throw new Error('isard: panoramas need the engine with 3D Tiles, which failed to load');
763
509
  }
764
- const host = {
765
- setPanorama: (...args) => {
766
- this.engine.setPanorama(...args);
767
- this.requestRender();
768
- },
769
- setPanoramaImage: (index, image) => this.engine.setPanoramaImage(index, image),
770
- clearPanorama: () => this.engine.clearPanorama(),
771
- geoidHeight: (lon, lat) => this.engineClass.geoidHeight(lon, lat),
772
- firstPerson: () => this.firstPerson,
773
- setFirstPerson: (view) => this.setFirstPerson(view),
774
- elevationAt: (lonLat) => this.elevationAt(lonLat),
775
- listen: (click, key, wheel) => {
776
- const off = this.on('click', ({ lonLat }) => click(lonLat));
777
- const container = this.getContainer();
778
- // Before the map's own wheel (its zoom, the eye's field of view)
779
- const onWheel = (e) => {
780
- e.preventDefault();
781
- e.stopImmediatePropagation();
782
- wheel(e);
783
- };
784
- container.addEventListener('keydown', key);
785
- container.addEventListener('wheel', onWheel, { capture: true, passive: false });
786
- return () => {
787
- off();
788
- container.removeEventListener('keydown', key);
789
- container.removeEventListener('wheel', onWheel, { capture: true });
790
- };
791
- },
792
- };
510
+ const host = panoramaHost(this, (lon, lat) => this.engines.geoidHeight(lon, lat));
793
511
  const panoramas = await loadPanoramas(host, options);
794
512
  this.panoramaSets.add(panoramas);
795
513
  return panoramas;
@@ -836,106 +554,47 @@ export class GeoMap {
836
554
  // ── Overlays and controls ──
837
555
  /** Adds a control in a corner of the map; controls stack from the corner inward */
838
556
  addControl(control, position = 'top-right') {
839
- if (this.controls.has(control))
840
- return this;
841
- const element = control.onAdd(this);
842
- element.classList.add('isard-ctrl');
843
- // In the bottom corners the first control added stays lowest, as in MapLibre
844
- if (position.startsWith('bottom'))
845
- this.corners[position].prepend(element);
846
- else
847
- this.corners[position].append(element);
848
- this.controls.set(control, element);
557
+ this.overlays.addControl(control, position);
849
558
  return this;
850
559
  }
851
560
  removeControl(control) {
852
- const element = this.controls.get(control);
853
- if (!element)
854
- return this;
855
- this.controls.delete(control);
856
- control.onRemove(this);
857
- element.remove();
561
+ this.overlays.removeControl(control);
858
562
  return this;
859
563
  }
860
564
  /** @internal Shows a marker or popup, placed after every frame from now on */
861
565
  attachOverlay(item, element) {
862
- this.overlay.append(element);
863
- this.positioned.add(item);
864
- item.position(this.size);
566
+ this.overlays.attach(item, element);
865
567
  }
866
568
  /** @internal */
867
569
  detachOverlay(item, element) {
868
- this.positioned.delete(item);
869
- element.remove();
570
+ this.overlays.detach(item, element);
870
571
  }
871
572
  /** @internal Places one overlay now (it moved), without drawing a frame */
872
573
  positionOverlay(item) {
873
- if (this.positioned.has(item))
874
- item.position(this.size);
574
+ this.overlays.position(item);
875
575
  }
876
576
  // ── Layers ──
877
577
  /** Replaces the basemap and the IGN hillshade, under the other layers (hillshade by default: 0, none). The former
878
578
  * names of the Géoplateforme basemaps throw, naming the new ones */
879
579
  setBasemap(basemap, hillshade = 0) {
880
580
  const name = checkBasemap(basemap);
881
- if (this.basemap && this.hasLayer(this.basemap))
882
- this.basemap.remove();
883
- if (this.hillshade && this.hasLayer(this.hillshade))
884
- this.hillshade.remove();
885
- // Its raster and labels go with it
886
- this.vector?.tiles.destroy();
887
- this.vector = undefined;
888
- const vector = VECTOR_BASEMAPS[name];
889
- if (vector) {
890
- const tiles = new VectorTiles(this, {
891
- style: vector,
892
- ...(this.basemapWorkerUrl ? { workerUrl: this.basemapWorkerUrl } : {}),
893
- });
894
- this.basemap = tiles.raster(this.basemapTileSize ? { size: this.basemapTileSize } : {});
895
- this.vector = { tiles, labels: tiles.labels() };
896
- }
897
- else {
898
- this.basemap = name === 'none' ? undefined : this.addRaster(BASEMAPS[name]);
899
- }
581
+ this.basemaps.replace(name);
900
582
  // Off the Earth, the body's own color
901
583
  if (this.earth)
902
584
  this.settings.globeColor = SEA[name];
903
585
  this.engine.setGlobeColor(...this.settings.globeColor);
904
- this.hillshade = hillshade > 0 ? this.addRaster({ ...HILLSHADE, opacity: hillshade }) : undefined;
905
- if (this.hillshade)
906
- this.toBottom(this.hillshade);
907
- if (this.basemap)
908
- this.toBottom(this.basemap);
909
- this.attributionChanged();
586
+ this.basemaps.addHillshade(hillshade);
587
+ this.credits.changed();
910
588
  }
911
589
  /** Raster tiles ({z}, {x}, {y} URL; Web Mercator, or reprojected from another `grid`: longitudes and latitudes,
912
590
  * Lambert-93, UTM…), over the layers already added */
913
591
  addRaster(options) {
914
- const { url, grid = 'mercator', opacity = 1, saturation = 1, size, before } = options;
915
- let { minZoom = 0, maxZoom = 19 } = options;
916
- // A PMTiles archive (`pmtiles://…`): its tiles read by range requests
917
- let loader = options.loader ?? pmtilesRasterLoader(url, size ?? 256);
918
- if (grid !== 'mercator' && !loader) {
919
- if (grid === 'equirectangular') {
920
- // `minZoom` and `maxZoom` are levels of the source: its level n has the pixel size of Mercator level n + 1 at
921
- // the equator
922
- loader = gridLoader(url, resolveGrid(equirectangularGrid(minZoom, maxZoom)), size ?? 256, this.remoteReprojection);
923
- [minZoom, maxZoom] = [minZoom + 1, maxZoom + 1];
924
- }
925
- else {
926
- const resolved = resolveGrid(grid);
927
- const zooms = gridZooms(resolved);
928
- minZoom = options.minZoom ?? zooms.minZoom;
929
- maxZoom = options.maxZoom ?? zooms.maxZoom;
930
- loader = gridLoader(url, resolved, size ?? 256, this.remoteReprojection);
931
- }
932
- }
592
+ const { url, opacity = 1, saturation = 1, size, before } = options;
593
+ const { loader, minZoom, maxZoom } = rasterSource(options, this.workers.remoteReprojection);
933
594
  const layer = new RasterLayer(this, { url, minZoom, maxZoom, opacity, saturation, size, loader });
934
595
  this.place(layer, before);
935
- if (options.attribution) {
936
- this.rasterCredits.set(layer, options.attribution);
937
- this.attributionChanged();
938
- }
596
+ if (options.attribution)
597
+ this.credits.set(layer, options.attribution);
939
598
  return layer;
940
599
  }
941
600
  /**
@@ -961,32 +620,17 @@ export class GeoMap {
961
620
  * another layer. The layer's `ready` resolves once the map has drawn it
962
621
  */
963
622
  add(spec) {
964
- switch (spec.type) {
965
- case 'lines':
966
- return this.addLines(spec);
967
- case 'circles':
968
- return this.addCircles(spec);
969
- case 'fills':
970
- return this.addFills(spec);
971
- case 'symbols':
972
- return this.addSymbols(spec);
973
- case 'extrusions':
974
- return this.addExtrusions(spec);
975
- case 'raster':
976
- return this.addRaster(spec);
977
- case 'ground':
978
- return this.addGround(spec);
979
- case 'network':
980
- return this.addNetwork(spec);
981
- case 'tiles3d':
982
- return this.addTiles3d(spec);
983
- case 'pointcloud':
984
- return this.addPointCloud(spec);
985
- case 'models':
986
- return this.addModels(spec);
987
- default:
988
- throw new Error(`isard: no layer of type ${String(spec.type)}`);
989
- }
623
+ return addSpec(this, spec);
624
+ }
625
+ /** @internal A layer made anew from its description, in the place of `old` in the drawing order (old removed) */
626
+ rebuildLayer(old, spec) {
627
+ const index = this.layers.indexOf(old);
628
+ const above = index >= 0 ? this.layers[index + 1] : undefined;
629
+ const layer = this.add(spec);
630
+ if (!('before' in spec && spec.before) && index >= 0)
631
+ this.moveLayer(layer, above);
632
+ old.remove();
633
+ return layer;
990
634
  }
991
635
  /** @internal Resolves once the map is idle (everything in view loaded and drawn) */
992
636
  whenIdle() {
@@ -1026,24 +670,11 @@ export class GeoMap {
1026
670
  }
1027
671
  /** @internal The glyphs of the symbol layers, loaded on first use */
1028
672
  get glyphs() {
1029
- if (!this.#glyphs) {
1030
- // The engine's context: the atlas stays within its texture size limit
1031
- const gl = this.canvas.getContext('webgl2');
1032
- const max = gl?.getParameter(gl.MAX_TEXTURE_SIZE);
1033
- this.#glyphs = new Glyphs(this.glyphsUrl, typeof max === 'number' ? max : undefined);
1034
- this.#glyphs.attach(this.engine);
1035
- }
1036
- return this.#glyphs;
673
+ return this.atlases.glyphs;
1037
674
  }
1038
675
  /** @internal The icons of the symbol layers: the application's images and the vector basemaps' sprites */
1039
676
  get icons() {
1040
- if (!this.#icons) {
1041
- const gl = this.canvas.getContext('webgl2');
1042
- const max = gl?.getParameter(gl.MAX_TEXTURE_SIZE);
1043
- this.#icons = new Icons(typeof max === 'number' ? max : undefined);
1044
- this.#icons.attach(this.engine);
1045
- }
1046
- return this.#icons;
677
+ return this.atlases.icons;
1047
678
  }
1048
679
  /**
1049
680
  * An image for the symbol layers' icons (`icon: name`), as MapLibre's `addImage`: its pixels (an image, a canvas,
@@ -1052,16 +683,13 @@ export class GeoMap {
1052
683
  * text. An image of the same name is replaced
1053
684
  */
1054
685
  addImage(name, image, options = {}) {
1055
- const pixels = 'data' in image ? image : readPixels(image);
1056
- if (!pixels)
1057
- throw new Error(`isard: the image ${name} has no pixels`);
1058
- this.icons.add(name, pixels, options);
686
+ this.atlases.addImage(name, image, options);
1059
687
  }
1060
688
  hasImage(name) {
1061
- return this.#icons?.has(name) ?? false;
689
+ return this.atlases.hasImage(name);
1062
690
  }
1063
691
  removeImage(name) {
1064
- this.#icons?.remove(name);
692
+ this.atlases.removeImage(name);
1065
693
  }
1066
694
  /**
1067
695
  * Vector tiles with a MapLibre style (OpenFreeMap's Positron by default): `tiles.raster(…)` draws its fills and lines
@@ -1070,7 +698,7 @@ export class GeoMap {
1070
698
  */
1071
699
  vectorTiles(options = {}) {
1072
700
  const tiles = new VectorTiles(this, {
1073
- ...(this.basemapWorkerUrl ? { workerUrl: this.basemapWorkerUrl } : {}),
701
+ ...(this.workers.url ? { workerUrl: this.workers.url } : {}),
1074
702
  ...options,
1075
703
  });
1076
704
  // Its workers stop with the map
@@ -1086,40 +714,8 @@ export class GeoMap {
1086
714
  * when it asks for another count or URL
1087
715
  */
1088
716
  basemapWorkers(count, url) {
1089
- const sharedUrl = this.basemapWorkerUrl;
1090
- const shared = defaultWorkerCount();
1091
- const workers = Math.max(1, count ?? shared);
1092
- const workerUrl = url ?? sharedUrl;
1093
- if (workers === shared && String(workerUrl) === String(sharedUrl)) {
1094
- this.basemapPool ??= new BasemapWorkers(shared, sharedUrl);
1095
- return { pool: this.basemapPool, own: false };
1096
- }
1097
- return { pool: new BasemapWorkers(workers, workerUrl), own: true };
717
+ return this.workers.pool(count, url);
1098
718
  }
1099
- /**
1100
- * The workers decoding the relief and reprojecting rasters: the map's pool, unless the map has no workers
1101
- * (`workers: false` or 0, or none in this browser): that is done in the page then
1102
- */
1103
- decodingPool() {
1104
- const { workers } = this.tiles3dOptions;
1105
- if (this.gone || workers === false || (workers !== undefined && workers <= 0))
1106
- return undefined;
1107
- if (typeof Worker === 'undefined' || typeof OffscreenCanvas === 'undefined')
1108
- return undefined;
1109
- return this.basemapWorkers().pool;
1110
- }
1111
- remoteHeights = (url, encoding, floor, tile, signal) => this.decodingPool()?.heights(url, encoding, floor, tile, signal, this);
1112
- remoteReprojection = (job, tile, signal) => this.decodingPool()?.reproject(job, tile, signal, this);
1113
- /** The loader of the relief tiles of `url` in a PNG encoding (one per URL, encoding and floor) */
1114
- terrainLoader = (url, encoding, floor) => {
1115
- const key = `${encoding} ${floor ?? ''} ${url}`;
1116
- let loader = this.terrainLoaders.get(key);
1117
- if (!loader) {
1118
- loader = encodedTerrainLoader(url, encoding, floor, this.remoteHeights);
1119
- this.terrainLoaders.set(key, loader);
1120
- }
1121
- return loader;
1122
- };
1123
719
  /** Adds a layer, under `before` if given */
1124
720
  place(layer, before) {
1125
721
  this.addLayer(layer);
@@ -1153,126 +749,31 @@ export class GeoMap {
1153
749
  * WebGL, three.js… with the matrices of the frame (`frame.matrixAt`); under `before` if given
1154
750
  */
1155
751
  addCustomLayer(layer, options = {}) {
1156
- const custom = new CustomLayer(this, layer, this.canvas, this, (lon, lat, altitude) => this.engineClass.customMatrix(lon, lat, altitude), () => this.getCamera());
752
+ const custom = new CustomLayer(this, layer, this.canvas, this, (lon, lat, altitude) => this.engines.customMatrix(lon, lat, altitude), () => this.getCamera());
1157
753
  return this.place(custom, options.before);
1158
754
  }
1159
755
  // ── Frames ──
1160
756
  /** Asks for a frame; several calls before the next frame draw it once */
1161
757
  requestRender() {
1162
- // Changes made by listeners during a frame are drawn by that frame; nothing is drawn without a context
1163
- if (this.inFrame || this.lost)
1164
- return;
1165
- this.frameRequest ||= requestAnimationFrame((now) => this.frame(now));
758
+ this.frames.request();
1166
759
  }
1167
760
  /** @internal Runs `step` every frame until it returns false; replaces the running animation */
1168
761
  animate(step) {
1169
- // A flight cut short no longer needs its destination
1170
- if (this.animation)
1171
- this.engine.clearPreload();
1172
- this.animation = step;
1173
- this.requestRender();
762
+ this.frames.animate(step);
1174
763
  }
1175
764
  /** @internal The pointer moved over the map (undefined: it left). Without 'hover' and 'hoverfeature' listeners,
1176
765
  * nothing is drawn: a still map stays idle under a moving mouse */
1177
766
  hover(point) {
1178
767
  if (!point) {
1179
- this.hoverPoint = undefined;
1180
- this.cancelHoverPick();
768
+ this.frames.hover(undefined);
769
+ this.hoverPick.cancel();
1181
770
  return;
1182
771
  }
1183
- if (this.listeners.hover.size === 0 && this.listeners.hoverfeature.size === 0)
772
+ if (!this.listeners.has('hover') && !this.listeners.has('hoverfeature'))
1184
773
  return;
1185
- this.hoverPoint = point;
774
+ this.frames.hover(point);
1186
775
  this.requestRender();
1187
776
  }
1188
- frame(now) {
1189
- this.frameRequest = 0;
1190
- try {
1191
- this.inFrame = true;
1192
- this.drawFrame(now);
1193
- }
1194
- finally {
1195
- // Whatever throws in a frame, the next ones are still drawn
1196
- this.inFrame = false;
1197
- }
1198
- }
1199
- drawFrame(now) {
1200
- if (this.animation && !this.animation(now))
1201
- this.animation = undefined;
1202
- const camera = this.getCamera();
1203
- const key = `${camera.center}|${camera.zoom}|${camera.pitch}|${camera.bearing}`;
1204
- const moved = key !== this.lastCamera;
1205
- if (moved) {
1206
- // The first frame is no move
1207
- if (!this.moving && this.lastCamera !== '') {
1208
- this.moving = true;
1209
- this.emit('movestart', camera);
1210
- }
1211
- this.lastCamera = key;
1212
- this.emit('move', camera);
1213
- }
1214
- else if (this.moving && !this.animation && !this.interaction?.active) {
1215
- this.moving = false;
1216
- this.emit('moveend', camera);
1217
- }
1218
- // Values for the pointer position are computed before drawing, in the same frame
1219
- let hovered;
1220
- if (this.hoverPoint) {
1221
- const point = this.hoverPoint;
1222
- hovered = point;
1223
- this.hoverPoint = undefined;
1224
- // The position under the pointer for its listeners only (a 'hoverfeature' alone does not need it)
1225
- if (this.listeners.hover.size > 0)
1226
- this.emit('hover', { point, lonLat: this.unproject(point) });
1227
- }
1228
- const t0 = performance.now();
1229
- // The engine asks for the next frame while it draws (uploads past the frame budget, fades): not ignored
1230
- this.inFrame = false;
1231
- // As the engine has it now (a listener may have moved it), for the custom layers drawn in the frame
1232
- this.frameCamera = this.layers.some((layer) => layer instanceof CustomLayer) ? this.engine.camera() : undefined;
1233
- let loading;
1234
- try {
1235
- loading = this.engine.render();
1236
- }
1237
- finally {
1238
- this.frameCamera = undefined;
1239
- }
1240
- this.inFrame = true;
1241
- // The picking pass sees the frame just drawn; its answer comes on a later frame
1242
- if (hovered && this.listeners.hoverfeature.size > 0)
1243
- this.requestHoverPick(hovered);
1244
- // Overlays follow the frame just drawn (camera, relief altitudes): all in one pass, transforms only
1245
- for (const item of this.positioned)
1246
- item.position(this.size);
1247
- this.emit('render', { ms: performance.now() - t0 });
1248
- this.inFrame = false;
1249
- // A move goes on until a frame finds the camera still: that frame sends 'moveend'
1250
- if (this.animation || (moved && this.moving))
1251
- this.requestRender();
1252
- // Idle: nothing loading (the engine with 3D Tiles neither), no animation, no frame asked for (uploads waiting,
1253
- // fades, listeners)
1254
- if (loading || this.upgrading || this.animation || this.frameRequest !== 0) {
1255
- this.settled = false;
1256
- }
1257
- else if (!this.settled) {
1258
- this.settled = true;
1259
- this.emit('idle', {});
1260
- }
1261
- }
1262
- /** Resizes when the device pixel ratio changes (browser zoom, a window moved to another screen) */
1263
- watchRatio() {
1264
- if (typeof matchMedia !== 'function')
1265
- return;
1266
- const query = matchMedia(`(resolution: ${window.devicePixelRatio || 1}dppx)`);
1267
- const changed = () => {
1268
- // A query for the new ratio
1269
- this.unwatchRatio?.();
1270
- this.watchRatio();
1271
- this.resize();
1272
- };
1273
- query.addEventListener('change', changed, { once: true });
1274
- this.unwatchRatio = () => query.removeEventListener('change', changed);
1275
- }
1276
777
  resize() {
1277
778
  if (this.lost)
1278
779
  return;
@@ -1281,11 +782,10 @@ export class GeoMap {
1281
782
  this.canvas.width = Math.round(width * ratio);
1282
783
  this.canvas.height = Math.round(height * ratio);
1283
784
  this.engine.resize(width, height, ratio);
1284
- this.size = [width, height];
785
+ this.overlays.size = [width, height];
1285
786
  this.emit('resize', { width, height });
1286
787
  // Draw at once: a resized canvas is cleared, waiting one frame would flash
1287
- cancelAnimationFrame(this.frameRequest);
1288
- this.frame(performance.now());
788
+ this.frames.drawNow();
1289
789
  }
1290
790
  destroy() {
1291
791
  if (this.gone)
@@ -1294,45 +794,30 @@ export class GeoMap {
1294
794
  // A context restored later rebuilds nothing
1295
795
  this.unwatchContext();
1296
796
  const camera = this.getCamera();
1297
- cancelAnimationFrame(this.frameRequest);
1298
- this.cancelHoverPick();
1299
- this.vector?.tiles.destroy();
1300
- for (const tiles of [...this.vectorSets])
797
+ this.frames.stop();
798
+ this.hoverPick.cancel();
799
+ this.basemaps.destroy();
800
+ [...this.vectorSets].forEach((tiles) => {
1301
801
  tiles.destroy();
802
+ });
1302
803
  this.vectorSets.clear();
1303
- this.basemapPool?.terminate();
1304
- this.basemapPool = undefined;
1305
- this.unwatchRatio?.();
1306
- this.unwatchRatio = undefined;
804
+ this.workers.terminate();
805
+ this.unwatchRatio();
1307
806
  this.unwatchHeaders();
1308
807
  this.resizeObserver.disconnect();
1309
808
  this.interaction?.destroy();
1310
- this.loader?.destroy();
809
+ this.engines.destroy();
1311
810
  // Custom layers free their WebGL objects, models their data, while the context is there
1312
- for (const layer of this.layers)
1313
- layer.dispose();
811
+ this.stack.dispose();
1314
812
  this.engine.free();
1315
813
  // Work still under way (labels being shaped, tiles arriving, timers) may call the engine after this: it finds one
1316
814
  // that does nothing, and nothing is drawn again
1317
815
  this.#engine = destroyedEngine(camera);
1318
816
  // Glyph ranges still arriving upload to it, not to the freed engine
1319
- this.#glyphs?.attach(this.#engine);
1320
- this.#icons?.attach(this.#engine);
817
+ this.atlases.attach(this.#engine);
1321
818
  this.lost = true;
1322
- for (const control of [...this.controls.keys()])
1323
- this.removeControl(control);
1324
- this.positioned.clear();
1325
- this.canvas.remove();
1326
- this.overlay.remove();
1327
- for (const position of CORNERS)
1328
- this.corners[position].remove();
1329
- this.container.classList.remove('isard-map');
1330
- for (const listeners of Object.values(this.listeners))
1331
- listeners.clear();
819
+ this.overlays.destroy();
820
+ this.listeners.clear();
1332
821
  }
1333
822
  }
1334
- /** The page's request headers to an engine (its own requests: raster tiles, tilesets, point clouds) */
1335
- function sendHeaders(engine, rules) {
1336
- engine.setRequestHeaders(rules.map(([prefix]) => prefix), Uint32Array.from(rules, ([, headers]) => Object.keys(headers).length), rules.flatMap(([, headers]) => Object.entries(headers).flat()));
1337
- }
1338
823
  //# sourceMappingURL=map.js.map