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.
- package/CHANGELOG.md +51 -0
- package/dist/{basemaps.d.ts → basemap/basemaps.d.ts} +3 -3
- package/dist/{basemaps.js → basemap/basemaps.js} +3 -3
- package/dist/basemap/basemaps.js.map +1 -0
- package/dist/{grid.d.ts → basemap/grid.d.ts} +6 -6
- package/dist/{grid.js → basemap/grid.js} +61 -67
- package/dist/basemap/grid.js.map +1 -0
- package/dist/{mvt.d.ts → basemap/mvt.d.ts} +1 -1
- package/dist/{mvt.js → basemap/mvt.js} +6 -6
- package/dist/basemap/mvt.js.map +1 -0
- package/dist/{pmtiles.d.ts → basemap/pmtiles.d.ts} +16 -10
- package/dist/{pmtiles.js → basemap/pmtiles.js} +41 -33
- package/dist/basemap/pmtiles.js.map +1 -0
- package/dist/{resample.d.ts → basemap/resample.d.ts} +11 -2
- package/dist/{resample.js → basemap/resample.js} +18 -16
- package/dist/basemap/resample.js.map +1 -0
- package/dist/{terrain.d.ts → basemap/terrain.d.ts} +3 -3
- package/dist/{terrain.js → basemap/terrain.js} +7 -7
- package/dist/basemap/terrain.js.map +1 -0
- package/dist/{vectorstyle.d.ts → basemap/vectorstyle.d.ts} +12 -10
- package/dist/{vectorstyle.js → basemap/vectorstyle.js} +129 -111
- package/dist/basemap/vectorstyle.js.map +1 -0
- package/dist/{vectortiles.d.ts → basemap/vectortiles.d.ts} +18 -18
- package/dist/{vectortiles.js → basemap/vectortiles.js} +157 -109
- package/dist/basemap/vectortiles.js.map +1 -0
- package/dist/{wmts.d.ts → basemap/wmts.d.ts} +3 -3
- package/dist/{wmts.js → basemap/wmts.js} +19 -14
- package/dist/basemap/wmts.js.map +1 -0
- package/dist/basemap.worker.js +2 -2
- package/dist/basemap.worker.js.map +14 -13
- package/dist/bind/layers.d.ts +46 -0
- package/dist/bind/layers.js +154 -0
- package/dist/bind/layers.js.map +1 -0
- package/dist/{crs.d.ts → data/crs.d.ts} +11 -11
- package/dist/{crs.js → data/crs.js} +38 -34
- package/dist/data/crs.js.map +1 -0
- package/dist/{gpx.d.ts → data/gpx.d.ts} +2 -2
- package/dist/{gpx.js → data/gpx.js} +20 -20
- package/dist/data/gpx.js.map +1 -0
- package/dist/{kml.d.ts → data/kml.d.ts} +2 -2
- package/dist/{kml.js → data/kml.js} +42 -39
- package/dist/data/kml.js.map +1 -0
- package/dist/{topojson.d.ts → data/topojson.d.ts} +2 -2
- package/dist/{topojson.js → data/topojson.js} +15 -10
- package/dist/data/topojson.js.map +1 -0
- package/dist/{xml.d.ts → data/xml.d.ts} +5 -5
- package/dist/{xml.js → data/xml.js} +19 -18
- package/dist/data/xml.js.map +1 -0
- package/dist/decoder.worker.js +4 -4
- package/dist/decoder.worker.js.map +7 -7
- package/dist/{decoders.d.ts → engine/decoders.d.ts} +3 -3
- package/dist/{decoders.js → engine/decoders.js} +28 -25
- package/dist/engine/decoders.js.map +1 -0
- package/dist/{loader.d.ts → engine/loader.d.ts} +1 -1
- package/dist/{loader.js → engine/loader.js} +17 -14
- package/dist/engine/loader.js.map +1 -0
- package/dist/{protocol.d.ts → engine/protocol.d.ts} +1 -1
- package/dist/engine/protocol.js +3 -0
- package/dist/engine/protocol.js.map +1 -0
- package/dist/{requests.d.ts → engine/requests.d.ts} +1 -1
- package/dist/{requests.js → engine/requests.js} +19 -16
- package/dist/engine/requests.js.map +1 -0
- package/dist/engine-loader.d.ts +13 -9
- package/dist/engine-loader.js +30 -30
- package/dist/engine-loader.js.map +1 -1
- package/dist/formats.d.ts +3 -3
- package/dist/formats.js +3 -3
- package/dist/formats.js.map +1 -1
- package/dist/geoplateforme.d.ts +13 -13
- package/dist/geoplateforme.js +23 -21
- package/dist/geoplateforme.js.map +1 -1
- package/dist/index.d.ts +30 -29
- package/dist/index.js +18 -18
- package/dist/index.js.map +1 -1
- package/dist/isard_bg.wasm +0 -0
- package/dist/isard_core_bg.wasm +0 -0
- package/dist/isard_points_bg.wasm +0 -0
- package/dist/{extrusions.d.ts → layers/extrusions.d.ts} +1 -1
- package/dist/{extrusions.js → layers/extrusions.js} +10 -5
- package/dist/layers/extrusions.js.map +1 -0
- package/dist/{features.d.ts → layers/features.d.ts} +10 -10
- package/dist/{features.js → layers/features.js} +44 -45
- package/dist/layers/features.js.map +1 -0
- package/dist/{glyphs.d.ts → layers/glyphs.d.ts} +6 -6
- package/dist/{glyphs.js → layers/glyphs.js} +45 -41
- package/dist/layers/glyphs.js.map +1 -0
- package/dist/{icons.d.ts → layers/icons.d.ts} +10 -10
- package/dist/{icons.js → layers/icons.js} +84 -69
- package/dist/layers/icons.js.map +1 -0
- package/dist/{layers.d.ts → layers/layers.d.ts} +9 -4
- package/dist/{layers.js → layers/layers.js} +35 -31
- package/dist/layers/layers.js.map +1 -0
- package/dist/{panoramas.d.ts → layers/panoramas.d.ts} +3 -3
- package/dist/{panoramas.js → layers/panoramas.js} +54 -43
- package/dist/layers/panoramas.js.map +1 -0
- package/dist/{pointclouds.d.ts → layers/pointclouds.d.ts} +5 -5
- package/dist/{pointclouds.js → layers/pointclouds.js} +26 -34
- package/dist/layers/pointclouds.js.map +1 -0
- package/dist/{symbols.d.ts → layers/symbols.d.ts} +3 -3
- package/dist/{symbols.js → layers/symbols.js} +69 -72
- package/dist/layers/symbols.js.map +1 -0
- package/dist/map/basemap-layers.d.ts +26 -0
- package/dist/map/basemap-layers.js +61 -0
- package/dist/map/basemap-layers.js.map +1 -0
- package/dist/map/canvas.d.ts +17 -0
- package/dist/map/canvas.js +56 -0
- package/dist/map/canvas.js.map +1 -0
- package/dist/map/checks.d.ts +11 -0
- package/dist/map/checks.js +39 -0
- package/dist/map/checks.js.map +1 -0
- package/dist/map/credits.d.ts +17 -0
- package/dist/map/credits.js +34 -0
- package/dist/map/credits.js.map +1 -0
- package/dist/map/engine-settings.d.ts +15 -0
- package/dist/map/engine-settings.js +45 -0
- package/dist/map/engine-settings.js.map +1 -0
- package/dist/map/engines.d.ts +41 -0
- package/dist/map/engines.js +77 -0
- package/dist/map/engines.js.map +1 -0
- package/dist/map/events.d.ts +14 -0
- package/dist/map/events.js +46 -0
- package/dist/map/events.js.map +1 -0
- package/dist/map/frame-loop.d.ts +61 -0
- package/dist/map/frame-loop.js +139 -0
- package/dist/map/frame-loop.js.map +1 -0
- package/dist/map/hover-pick.d.ts +30 -0
- package/dist/map/hover-pick.js +56 -0
- package/dist/map/hover-pick.js.map +1 -0
- package/dist/map/layer-spec.d.ts +42 -0
- package/dist/map/layer-spec.js +34 -0
- package/dist/map/layer-spec.js.map +1 -0
- package/dist/map/layer-stack.d.ts +36 -0
- package/dist/map/layer-stack.js +101 -0
- package/dist/map/layer-stack.js.map +1 -0
- package/dist/map/overlay-host.d.ts +36 -0
- package/dist/map/overlay-host.js +101 -0
- package/dist/map/overlay-host.js.map +1 -0
- package/dist/map/panorama-host.d.ts +5 -0
- package/dist/map/panorama-host.js +32 -0
- package/dist/map/panorama-host.js.map +1 -0
- package/dist/map/raster-source.d.ts +11 -0
- package/dist/map/raster-source.js +26 -0
- package/dist/map/raster-source.js.map +1 -0
- package/dist/map/symbol-atlases.d.ts +26 -0
- package/dist/map/symbol-atlases.js +59 -0
- package/dist/map/symbol-atlases.js.map +1 -0
- package/dist/map/viewshed.d.ts +5 -0
- package/dist/map/viewshed.js +28 -0
- package/dist/map/viewshed.js.map +1 -0
- package/dist/map/workers.d.ts +46 -0
- package/dist/map/workers.js +73 -0
- package/dist/map/workers.js.map +1 -0
- package/dist/map.d.ts +46 -127
- package/dist/map.js +183 -698
- package/dist/map.js.map +1 -1
- package/dist/projections.d.ts +3 -3
- package/dist/projections.js +2 -2
- package/dist/projections.js.map +1 -1
- package/dist/react/Control.d.ts +11 -0
- package/dist/react/Control.js +18 -0
- package/dist/react/Control.js.map +1 -0
- package/dist/react/CustomLayer.d.ts +11 -0
- package/dist/react/CustomLayer.js +30 -0
- package/dist/react/CustomLayer.js.map +1 -0
- package/dist/react/IsardMap.d.ts +43 -0
- package/dist/react/IsardMap.js +146 -0
- package/dist/react/IsardMap.js.map +1 -0
- package/dist/react/Layer.d.ts +42 -0
- package/dist/react/Layer.js +74 -0
- package/dist/react/Layer.js.map +1 -0
- package/dist/react/Marker.d.ts +24 -0
- package/dist/react/Marker.js +53 -0
- package/dist/react/Marker.js.map +1 -0
- package/dist/react/Panel.d.ts +11 -0
- package/dist/react/Panel.js +37 -0
- package/dist/react/Panel.js.map +1 -0
- package/dist/react/Popup.d.ts +18 -0
- package/dist/react/Popup.js +60 -0
- package/dist/react/Popup.js.map +1 -0
- package/dist/react/Swipe.d.ts +9 -0
- package/dist/react/Swipe.js +41 -0
- package/dist/react/Swipe.js.map +1 -0
- package/dist/react/Viewshed.d.ts +5 -0
- package/dist/react/Viewshed.js +28 -0
- package/dist/react/Viewshed.js.map +1 -0
- package/dist/react/context.d.ts +32 -0
- package/dist/react/context.js +20 -0
- package/dist/react/context.js.map +1 -0
- package/dist/react/index.d.ts +26 -0
- package/dist/react/index.js +22 -0
- package/dist/react/index.js.map +1 -0
- package/dist/settings.d.ts +6 -10
- package/dist/settings.js +19 -17
- package/dist/settings.js.map +1 -1
- package/dist/types.d.ts +8 -8
- package/dist/types.js +1 -0
- package/dist/types.js.map +1 -1
- package/dist/{controls.d.ts → ui/controls.d.ts} +1 -1
- package/dist/{controls.js → ui/controls.js} +6 -4
- package/dist/ui/controls.js.map +1 -0
- package/dist/{interaction.d.ts → ui/interaction.d.ts} +9 -5
- package/dist/{interaction.js → ui/interaction.js} +9 -8
- package/dist/ui/interaction.js.map +1 -0
- package/dist/{overlay.d.ts → ui/overlay.d.ts} +2 -2
- package/dist/{overlay.js → ui/overlay.js} +18 -20
- package/dist/ui/overlay.js.map +1 -0
- package/dist/{style.d.ts → ui/style.d.ts} +1 -1
- package/dist/{style.js → ui/style.js} +2 -2
- package/dist/ui/style.js.map +1 -0
- package/dist/{animation.d.ts → util/animation.d.ts} +9 -5
- package/dist/{animation.js → util/animation.js} +6 -6
- package/dist/util/animation.js.map +1 -0
- package/dist/{color.d.ts → util/color.d.ts} +1 -1
- package/dist/{color.js → util/color.js} +14 -16
- package/dist/util/color.js.map +1 -0
- package/dist/{geo.d.ts → util/geo.d.ts} +6 -6
- package/dist/{geo.js → util/geo.js} +12 -12
- package/dist/util/geo.js.map +1 -0
- package/dist/util/guards.d.ts +7 -0
- package/dist/util/guards.js +8 -0
- package/dist/util/guards.js.map +1 -0
- package/dist/util/lru.js.map +1 -0
- package/dist/wasm/isard.d.ts +168 -0
- package/dist/wasm/isard.js +146 -0
- package/dist/wasm/isard_core.d.ts +144 -0
- package/dist/wasm/isard_core.js +122 -0
- package/dist/{basemap.worker.d.ts → workers/basemap.worker.d.ts} +17 -7
- package/dist/workers/basemap.worker.js +658 -0
- package/dist/workers/basemap.worker.js.map +1 -0
- package/package.json +24 -1
- package/vite.js +1 -1
- package/dist/animation.js.map +0 -1
- package/dist/basemaps.js.map +0 -1
- package/dist/color.js.map +0 -1
- package/dist/controls.js.map +0 -1
- package/dist/crs.js.map +0 -1
- package/dist/decoders.js.map +0 -1
- package/dist/extrusions.js.map +0 -1
- package/dist/features.js.map +0 -1
- package/dist/geo.js.map +0 -1
- package/dist/glyphs.js.map +0 -1
- package/dist/gpx.js.map +0 -1
- package/dist/grid.js.map +0 -1
- package/dist/icons.js.map +0 -1
- package/dist/interaction.js.map +0 -1
- package/dist/kml.js.map +0 -1
- package/dist/layers.js.map +0 -1
- package/dist/loader.js.map +0 -1
- package/dist/lru.js.map +0 -1
- package/dist/mvt.js.map +0 -1
- package/dist/overlay.js.map +0 -1
- package/dist/panoramas.js.map +0 -1
- package/dist/pmtiles.js.map +0 -1
- package/dist/pointclouds.js.map +0 -1
- package/dist/protocol.js +0 -2
- package/dist/protocol.js.map +0 -1
- package/dist/requests.js.map +0 -1
- package/dist/resample.js.map +0 -1
- package/dist/style.js.map +0 -1
- package/dist/symbols.js.map +0 -1
- package/dist/terrain.js.map +0 -1
- package/dist/topojson.js.map +0 -1
- package/dist/vectorstyle.js.map +0 -1
- package/dist/vectortiles.js.map +0 -1
- package/dist/wmts.js.map +0 -1
- package/dist/xml.js.map +0 -1
- /package/dist/{lru.d.ts → util/lru.d.ts} +0 -0
- /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 {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import { destroyedEngine, engineUrl, loadEngine
|
|
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 {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
/**
|
|
85
|
-
|
|
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
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
-
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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.
|
|
171
|
-
|
|
172
|
-
this.
|
|
173
|
-
this.
|
|
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 =
|
|
178
|
-
|
|
179
|
-
|
|
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
|
-
|
|
201
|
-
this.
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
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
|
-
|
|
218
|
-
event.preventDefault();
|
|
137
|
+
this.unwatchContext = watchContext(this.canvas, () => {
|
|
219
138
|
this.lost = true;
|
|
220
|
-
|
|
221
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
315
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
336
|
-
|
|
337
|
-
|
|
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
|
-
|
|
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
|
|
396
|
-
|
|
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.
|
|
429
|
-
this.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
626
|
-
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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 =
|
|
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.
|
|
504
|
+
if (!this.engines.tiles3d) {
|
|
759
505
|
this.upgrade();
|
|
760
|
-
await this.upgrading;
|
|
761
|
-
if (!this.
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
905
|
-
|
|
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,
|
|
915
|
-
|
|
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.
|
|
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
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
689
|
+
return this.atlases.hasImage(name);
|
|
1062
690
|
}
|
|
1063
691
|
removeImage(name) {
|
|
1064
|
-
this
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
1180
|
-
this.
|
|
768
|
+
this.frames.hover(undefined);
|
|
769
|
+
this.hoverPick.cancel();
|
|
1181
770
|
return;
|
|
1182
771
|
}
|
|
1183
|
-
if (this.listeners.hover
|
|
772
|
+
if (!this.listeners.has('hover') && !this.listeners.has('hoverfeature'))
|
|
1184
773
|
return;
|
|
1185
|
-
this.
|
|
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
|
-
|
|
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
|
-
|
|
1298
|
-
this.
|
|
1299
|
-
this.
|
|
1300
|
-
|
|
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.
|
|
1304
|
-
this.
|
|
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.
|
|
809
|
+
this.engines.destroy();
|
|
1311
810
|
// Custom layers free their WebGL objects, models their data, while the context is there
|
|
1312
|
-
|
|
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
|
|
1320
|
-
this.#icons?.attach(this.#engine);
|
|
817
|
+
this.atlases.attach(this.#engine);
|
|
1321
818
|
this.lost = true;
|
|
1322
|
-
|
|
1323
|
-
|
|
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
|