isard 1.1.0 → 1.3.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 +121 -0
- package/README.md +44 -39
- package/dist/basemap/basemaps.d.ts +1 -1
- package/dist/basemap/basemaps.js +2 -2
- package/dist/basemap/basemaps.js.map +1 -1
- package/dist/basemap/grid.d.ts +5 -5
- package/dist/basemap/grid.js +59 -65
- package/dist/basemap/grid.js.map +1 -1
- package/dist/basemap/mvt.d.ts +1 -1
- package/dist/basemap/mvt.js +6 -6
- package/dist/basemap/mvt.js.map +1 -1
- package/dist/basemap/pmtiles.d.ts +10 -10
- package/dist/basemap/pmtiles.js +39 -31
- package/dist/basemap/pmtiles.js.map +1 -1
- package/dist/basemap/pmtileslayer.d.ts +33 -0
- package/dist/basemap/pmtileslayer.js +97 -0
- package/dist/basemap/pmtileslayer.js.map +1 -0
- package/dist/basemap/resample.d.ts +2 -2
- package/dist/basemap/resample.js +16 -14
- package/dist/basemap/resample.js.map +1 -1
- package/dist/basemap/terrain.d.ts +3 -3
- package/dist/basemap/terrain.js +6 -6
- package/dist/basemap/terrain.js.map +1 -1
- package/dist/basemap/trees.d.ts +26 -0
- package/dist/basemap/trees.js +141 -0
- package/dist/basemap/trees.js.map +1 -0
- package/dist/basemap/vectorstyle.d.ts +22 -10
- package/dist/basemap/vectorstyle.js +163 -110
- package/dist/basemap/vectorstyle.js.map +1 -1
- package/dist/basemap/vectortiles.d.ts +70 -47
- package/dist/basemap/vectortiles.js +480 -358
- package/dist/basemap/vectortiles.js.map +1 -1
- package/dist/basemap/wmts.d.ts +3 -3
- package/dist/basemap/wmts.js +16 -11
- package/dist/basemap/wmts.js.map +1 -1
- package/dist/basemap/workerpool.d.ts +17 -0
- package/dist/basemap/workerpool.js +194 -0
- package/dist/basemap/workerpool.js.map +1 -0
- package/dist/basemap.worker.js +2 -2
- package/dist/basemap.worker.js.map +21 -13
- package/dist/bind/layers.d.ts +13 -4
- package/dist/bind/layers.js +64 -40
- package/dist/bind/layers.js.map +1 -1
- package/dist/data/crs.d.ts +10 -10
- package/dist/data/crs.js +38 -34
- package/dist/data/crs.js.map +1 -1
- package/dist/data/gpx.d.ts +1 -1
- package/dist/data/gpx.js +20 -20
- package/dist/data/gpx.js.map +1 -1
- package/dist/data/kml.d.ts +1 -1
- package/dist/data/kml.js +42 -39
- package/dist/data/kml.js.map +1 -1
- package/dist/data/topojson.d.ts +1 -1
- package/dist/data/topojson.js +15 -10
- package/dist/data/topojson.js.map +1 -1
- package/dist/data/xml.d.ts +5 -5
- package/dist/data/xml.js +19 -18
- package/dist/data/xml.js.map +1 -1
- package/dist/decoder.worker.js +4 -4
- package/dist/decoder.worker.js.map +6 -6
- package/dist/engine/decoders.d.ts +2 -2
- package/dist/engine/decoders.js +28 -25
- package/dist/engine/decoders.js.map +1 -1
- package/dist/engine/loader.js +13 -14
- package/dist/engine/loader.js.map +1 -1
- package/dist/engine/requests.d.ts +1 -1
- package/dist/engine/requests.js +19 -16
- package/dist/engine/requests.js.map +1 -1
- package/dist/engine-loader.d.ts +13 -9
- package/dist/engine-loader.js +38 -28
- package/dist/engine-loader.js.map +1 -1
- package/dist/geoplateforme.d.ts +26 -10
- package/dist/geoplateforme.js +45 -20
- package/dist/geoplateforme.js.map +1 -1
- package/dist/index.d.ts +12 -4
- package/dist/index.js +9 -2
- 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/isard_tess_bg.wasm +0 -0
- package/dist/layers/extrusions.d.ts +1 -1
- package/dist/layers/extrusions.js +10 -5
- package/dist/layers/extrusions.js.map +1 -1
- package/dist/layers/features.d.ts +10 -10
- package/dist/layers/features.js +41 -42
- package/dist/layers/features.js.map +1 -1
- package/dist/layers/glyphs.d.ts +3 -3
- package/dist/layers/glyphs.js +34 -40
- package/dist/layers/glyphs.js.map +1 -1
- package/dist/layers/icons.d.ts +8 -8
- package/dist/layers/icons.js +76 -68
- package/dist/layers/icons.js.map +1 -1
- package/dist/layers/layers.d.ts +1 -1
- package/dist/layers/layers.js +42 -29
- package/dist/layers/layers.js.map +1 -1
- package/dist/layers/panoramas.d.ts +2 -2
- package/dist/layers/panoramas.js +52 -41
- package/dist/layers/panoramas.js.map +1 -1
- package/dist/layers/pointclouds.d.ts +3 -3
- package/dist/layers/pointclouds.js +25 -33
- package/dist/layers/pointclouds.js.map +1 -1
- package/dist/layers/symbols.d.ts +1 -1
- package/dist/layers/symbols.js +68 -71
- package/dist/layers/symbols.js.map +1 -1
- package/dist/layers/vectors.d.ts +60 -0
- package/dist/layers/vectors.js +130 -0
- package/dist/layers/vectors.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 +64 -0
- package/dist/map/frame-loop.js +145 -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 +54 -126
- package/dist/map.js +227 -698
- package/dist/map.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 +147 -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 +16 -0
- package/dist/react/Swipe.js +52 -0
- package/dist/react/Swipe.js.map +1 -0
- package/dist/react/VectorGeometry.d.ts +35 -0
- package/dist/react/VectorGeometry.js +82 -0
- package/dist/react/VectorGeometry.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 +33 -0
- package/dist/react/context.js +20 -0
- package/dist/react/context.js.map +1 -0
- package/dist/react/index.d.ts +16 -108
- package/dist/react/index.js +11 -397
- package/dist/react/index.js.map +1 -1
- package/dist/settings.d.ts +5 -9
- package/dist/settings.js +17 -15
- package/dist/settings.js.map +1 -1
- package/dist/types.d.ts +27 -4
- package/dist/types.js.map +1 -1
- package/dist/ui/controls.js +6 -4
- package/dist/ui/controls.js.map +1 -1
- package/dist/ui/interaction.d.ts +2 -2
- package/dist/ui/interaction.js +8 -7
- package/dist/ui/interaction.js.map +1 -1
- package/dist/ui/minimap.d.ts +25 -0
- package/dist/ui/minimap.js +90 -0
- package/dist/ui/minimap.js.map +1 -0
- package/dist/ui/overlay.js +18 -20
- package/dist/ui/overlay.js.map +1 -1
- package/dist/ui/scale.d.ts +19 -0
- package/dist/ui/scale.js +44 -0
- package/dist/ui/scale.js.map +1 -0
- package/dist/ui/search.d.ts +25 -0
- package/dist/ui/search.js +119 -0
- package/dist/ui/search.js.map +1 -0
- package/dist/ui/style.d.ts +1 -1
- package/dist/ui/style.js +19 -2
- package/dist/ui/style.js.map +1 -1
- package/dist/util/animation.d.ts +3 -3
- package/dist/util/animation.js +6 -6
- package/dist/util/animation.js.map +1 -1
- package/dist/util/color.d.ts +1 -1
- package/dist/util/color.js +14 -16
- package/dist/util/color.js.map +1 -1
- package/dist/util/geo.d.ts +6 -6
- package/dist/util/geo.js +12 -12
- package/dist/util/geo.js.map +1 -1
- 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 -1
- package/dist/util/sun.d.ts +11 -0
- package/dist/util/sun.js +26 -0
- package/dist/util/sun.js.map +1 -0
- package/dist/wasm/isard.d.ts +250 -7
- package/dist/wasm/isard.js +375 -8
- package/dist/wasm/isard_core.d.ts +226 -7
- package/dist/wasm/isard_core.js +351 -8
- package/dist/wasm/isard_tess.d.ts +109 -0
- package/dist/wasm/isard_tess.js +398 -0
- package/dist/workers/basemap/geometry.d.ts +72 -0
- package/dist/workers/basemap/labels.d.ts +8 -0
- package/dist/workers/basemap/raster.d.ts +10 -0
- package/dist/workers/basemap/tiles.d.ts +65 -0
- package/dist/workers/basemap/trees.d.ts +19 -0
- package/dist/workers/basemap.worker.d.ts +126 -22
- package/dist/workers/basemap.worker.js +76 -544
- package/dist/workers/basemap.worker.js.map +1 -1
- package/package.json +5 -4
- package/vite.js +10 -2
package/dist/map.js
CHANGED
|
@@ -2,154 +2,97 @@
|
|
|
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 {
|
|
11
|
-
import {
|
|
12
|
-
import { destroyedEngine, engineUrl, loadEngine,
|
|
8
|
+
import { SEA } from "./basemap/basemaps.js";
|
|
9
|
+
import { pmtilesLayer } from "./basemap/pmtileslayer.js";
|
|
10
|
+
import { VectorTiles } from "./basemap/vectortiles.js";
|
|
11
|
+
import { defaultWorkerUrl } from "./basemap/workerpool.js";
|
|
12
|
+
import { onHeaderRules } from "./engine/requests.js";
|
|
13
|
+
import { destroyedEngine, engineUrl, loadEngine, tessUrl } from "./engine-loader.js";
|
|
13
14
|
import { IGN_PRESETS } from "./geoplateforme.js";
|
|
14
15
|
import { ExtrusionLayer, extrusionStyle } from "./layers/extrusions.js";
|
|
15
16
|
import { CircleLayer, circleStyle, FillLayer, fillStyle, LineLayer, lineStyle, } from "./layers/features.js";
|
|
16
|
-
import { Glyphs } from "./layers/glyphs.js";
|
|
17
|
-
import { Icons, readPixels } from "./layers/icons.js";
|
|
18
17
|
import { CustomLayer, GroundLayer, ModelLayer, NetworkLayer, RasterLayer, Tiles3dLayer, } from "./layers/layers.js";
|
|
19
18
|
import { loadPanoramas } from "./layers/panoramas.js";
|
|
20
19
|
import { PointCloudLayer } from "./layers/pointclouds.js";
|
|
21
20
|
import { SymbolLayer, symbolStyle } from "./layers/symbols.js";
|
|
22
|
-
import {
|
|
21
|
+
import { VectorLayer } from "./layers/vectors.js";
|
|
22
|
+
import { BasemapLayers } from "./map/basemap-layers.js";
|
|
23
|
+
import { createCanvas, labelCanvas, watchContext, watchPixelRatio } from "./map/canvas.js";
|
|
24
|
+
import { checkBasemap, report, validateNetwork } from "./map/checks.js";
|
|
25
|
+
import { Credits } from "./map/credits.js";
|
|
26
|
+
import { mapSettings, sendHeaders } from "./map/engine-settings.js";
|
|
27
|
+
import { MapEngines } from "./map/engines.js";
|
|
28
|
+
import { MapListeners } from "./map/events.js";
|
|
29
|
+
import { FrameLoop } from "./map/frame-loop.js";
|
|
30
|
+
import { HoverPick } from "./map/hover-pick.js";
|
|
31
|
+
import { addSpec } from "./map/layer-spec.js";
|
|
32
|
+
import { LayerStack } from "./map/layer-stack.js";
|
|
33
|
+
import { OverlayHost } from "./map/overlay-host.js";
|
|
34
|
+
import { panoramaHost } from "./map/panorama-host.js";
|
|
35
|
+
import { rasterSource } from "./map/raster-source.js";
|
|
36
|
+
import { SymbolAtlases } from "./map/symbol-atlases.js";
|
|
37
|
+
import { applyViewshed } from "./map/viewshed.js";
|
|
38
|
+
import { MapWorkers } from "./map/workers.js";
|
|
39
|
+
import { atmosphereSettings, rgb, terrainSettings } from "./settings.js";
|
|
23
40
|
import { AttributionControl } from "./ui/controls.js";
|
|
24
41
|
import { Interaction } from "./ui/interaction.js";
|
|
25
|
-
import { injectStyle } from "./ui/style.js";
|
|
26
42
|
import { easeTo, flyTo } from "./util/animation.js";
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
}
|
|
43
|
+
import { sunPosition } from "./util/sun.js";
|
|
44
|
+
/** A layer whose picked features come back with their properties (lines, circles, fills, extrusions, 3D Tiles…) */
|
|
45
|
+
const hasFeatures = (layer) => 'featureAt' in layer && typeof layer.featureAt === 'function';
|
|
62
46
|
/** A map (see `GeoMap.create`); layers take it as their host */
|
|
63
47
|
export class GeoMap {
|
|
64
48
|
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
49
|
#engine;
|
|
84
|
-
/**
|
|
85
|
-
|
|
50
|
+
/** The engine class (core, or with 3D Tiles) and the engine with 3D Tiles loaded when needed */
|
|
51
|
+
engines;
|
|
52
|
+
/** The layers in drawing order and those kept on top */
|
|
53
|
+
stack = new LayerStack(() => this.#engine);
|
|
54
|
+
/** The basemap and the hillshade */
|
|
55
|
+
basemaps;
|
|
56
|
+
/** The glyphs and icons of the symbol layers */
|
|
57
|
+
atlases;
|
|
58
|
+
/** The workers of the vector tiles, the relief and the reprojected rasters */
|
|
59
|
+
workers;
|
|
60
|
+
container;
|
|
61
|
+
/** The canvas in the container, the markers and popups over it, the controls in its corners */
|
|
62
|
+
overlays;
|
|
86
63
|
settings;
|
|
64
|
+
/** @internal Where the basemap workers load the tessellation module from (`isard_tess_bg.wasm`) */
|
|
65
|
+
tessUrl;
|
|
87
66
|
/** The WebGL context is lost: nothing is drawn until it is restored */
|
|
88
67
|
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
|
-
};
|
|
68
|
+
gone = false;
|
|
69
|
+
listeners = new MapListeners();
|
|
70
|
+
/** Frames drawn on demand, with their events */
|
|
71
|
+
frames;
|
|
101
72
|
resizeObserver;
|
|
102
73
|
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
74
|
/** The map is of the Earth: a basemap by default */
|
|
114
75
|
earth;
|
|
115
76
|
/** The body, as the option named it */
|
|
116
77
|
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;
|
|
78
|
+
/** Credits of the raster layers that have some, and the attribution controls told when they change */
|
|
79
|
+
credits = new Credits();
|
|
80
|
+
/** The picking for 'hoverfeature', its answer emitted on a later frame */
|
|
81
|
+
hoverPick = new HoverPick(() => this.engine, () => this.lost, (result) => this.emit('hoverfeature', {
|
|
82
|
+
point: [result.x, result.y],
|
|
83
|
+
lonLat: result.lonLat,
|
|
84
|
+
hit: result.hit && this.featureHit(result.hit),
|
|
85
|
+
}));
|
|
129
86
|
/** What 'hoverfeature' looks for: within `radius` CSS pixels, in `layers` only if given */
|
|
130
87
|
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
88
|
/** Vector tiles made by `vectorTiles()`, destroyed with the map */
|
|
141
89
|
vectorSets = new Set();
|
|
142
|
-
/** The workers drawing vector tiles, shared by every `VectorTiles` of the map; created on first use */
|
|
143
|
-
basemapPool;
|
|
144
90
|
/** Stops watching the device pixel ratio */
|
|
145
91
|
unwatchRatio;
|
|
146
92
|
/** Stops following the page's request headers (`setRequestHeaders`) */
|
|
147
93
|
unwatchHeaders = () => undefined;
|
|
148
94
|
/** Stops listening to the canvas's context loss and restoration */
|
|
149
95
|
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
96
|
/** Loads the engine (once per page) and creates a map filling `container` */
|
|
154
97
|
static async create(container, given = {}) {
|
|
155
98
|
// A preset under the options given
|
|
@@ -167,85 +110,56 @@ export class GeoMap {
|
|
|
167
110
|
return map;
|
|
168
111
|
}
|
|
169
112
|
constructor(container, options, engineClass, tiles3dModule) {
|
|
170
|
-
this.
|
|
171
|
-
|
|
172
|
-
this.
|
|
173
|
-
this.
|
|
174
|
-
this.
|
|
175
|
-
this.basemapTileSize = options.basemapTileSize;
|
|
113
|
+
this.engines = new MapEngines(engineClass, tiles3dModule, options, () => this.gone);
|
|
114
|
+
const workerUrl = options.basemapWorkerUrl ?? defaultWorkerUrl(options.assetsUrl);
|
|
115
|
+
this.tessUrl = tessUrl(options);
|
|
116
|
+
this.workers = new MapWorkers(workerUrl, options.workers, () => this.gone, this);
|
|
117
|
+
this.basemaps = new BasemapLayers(this, this.stack, workerUrl, options.basemapTileSize);
|
|
176
118
|
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]));
|
|
119
|
+
this.canvas = createCanvas();
|
|
120
|
+
this.atlases = new SymbolAtlases(this.canvas, options.glyphs, () => this.#engine);
|
|
121
|
+
this.overlays = new OverlayHost(container, this.canvas, this);
|
|
198
122
|
this.earth = (options.body ?? 'earth') === 'earth';
|
|
199
123
|
this.body = options.body;
|
|
200
|
-
|
|
201
|
-
this.
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
124
|
+
this.settings = mapSettings(options, this.earth, this.workers.terrainLoader);
|
|
125
|
+
this.frames = new FrameLoop({
|
|
126
|
+
engine: () => this.engine,
|
|
127
|
+
camera: () => this.getCamera(),
|
|
128
|
+
lost: () => this.lost,
|
|
129
|
+
interacting: () => this.interaction?.active === true,
|
|
130
|
+
customLayers: () => this.layers.some((layer) => layer instanceof CustomLayer),
|
|
131
|
+
upgrading: () => this.engines.upgrading !== undefined,
|
|
132
|
+
drawn: (hovered) => {
|
|
133
|
+
// The picking pass sees the frame just drawn; its answer comes on a later frame
|
|
134
|
+
if (hovered && this.listeners.has('hoverfeature'))
|
|
135
|
+
this.hoverPick.request(hovered, this.hoverPicking);
|
|
136
|
+
// Overlays follow the frame just drawn
|
|
137
|
+
this.overlays.positionAll();
|
|
138
|
+
},
|
|
139
|
+
}, this.listeners);
|
|
213
140
|
this.#engine = this.createEngine();
|
|
214
141
|
// Once it has an engine: a map whose creation failed before is not left following them
|
|
215
142
|
this.unwatchHeaders = onHeaderRules((rules) => sendHeaders(this.#engine, rules));
|
|
216
143
|
// The browser may take the GPU away (driver reset, background tab on mobile): the map waits, then rebuilds
|
|
217
|
-
|
|
218
|
-
event.preventDefault();
|
|
144
|
+
this.unwatchContext = watchContext(this.canvas, () => {
|
|
219
145
|
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
|
-
};
|
|
146
|
+
this.frames.cancel();
|
|
147
|
+
}, () => this.restore());
|
|
230
148
|
const start = this.earth
|
|
231
149
|
? { center: [2.4246, 48.8457], zoom: 13, pitch: 0, bearing: 0 }
|
|
232
150
|
: { center: [0, 0], zoom: 2, pitch: 0, bearing: 0 };
|
|
233
151
|
this.setCamera({ ...start, ...options });
|
|
234
152
|
this.setBasemap(options.basemap ?? (this.earth ? 'osm-positron' : 'none'), options.hillshade ?? 0);
|
|
153
|
+
if (options.sun)
|
|
154
|
+
this.setSun(options.sun);
|
|
235
155
|
this.resizeObserver = new ResizeObserver(() => this.resize());
|
|
236
156
|
this.resizeObserver.observe(container);
|
|
237
|
-
this.
|
|
157
|
+
this.unwatchRatio = watchPixelRatio(() => this.resize());
|
|
238
158
|
this.resize();
|
|
239
159
|
const interactive = options.interactive ?? true;
|
|
240
160
|
const keyboard = interactive && (options.keyboard ?? true);
|
|
241
161
|
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;
|
|
162
|
+
labelCanvas(this.canvas, keyboard, options.ariaLabel ?? 'Map');
|
|
249
163
|
const attribution = options.attributionControl ?? true;
|
|
250
164
|
if (attribution !== false) {
|
|
251
165
|
this.addControl(new AttributionControl(attribution === true ? {} : attribution), 'bottom-right');
|
|
@@ -262,48 +176,26 @@ export class GeoMap {
|
|
|
262
176
|
get engine() {
|
|
263
177
|
return this.#engine;
|
|
264
178
|
}
|
|
179
|
+
/** Layers in drawing order (bottom first) */
|
|
180
|
+
get layers() {
|
|
181
|
+
return this.stack.layers;
|
|
182
|
+
}
|
|
183
|
+
/** Layers new layers go under */
|
|
184
|
+
get onTop() {
|
|
185
|
+
return this.stack.onTop;
|
|
186
|
+
}
|
|
265
187
|
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);
|
|
188
|
+
return this.engines.create(this.canvas, () => this.requestRender(), this.settings);
|
|
289
189
|
}
|
|
290
190
|
/** A 3D Tiles layer or a model on the core engine: loads the engine with 3D Tiles, then rebuilds the map on it
|
|
291
191
|
* (camera, settings and layers kept, as after a context loss); the layer is created then */
|
|
292
192
|
upgrade() {
|
|
293
|
-
this.
|
|
294
|
-
this.upgrading = undefined;
|
|
295
|
-
if (this.gone)
|
|
296
|
-
return;
|
|
297
|
-
this.engineClass = Engine;
|
|
298
|
-
this.tiles3dModule = module;
|
|
193
|
+
this.engines.upgrade(() => {
|
|
299
194
|
// A lost context: the engine is rebuilt on it once restored
|
|
300
195
|
if (!this.lost)
|
|
301
196
|
this.restore();
|
|
302
197
|
}, (error) => {
|
|
303
198
|
// The 3D Tiles layers stay empty; the next one added tries again
|
|
304
|
-
this.upgrading = undefined;
|
|
305
|
-
if (this.gone)
|
|
306
|
-
return;
|
|
307
199
|
report(error);
|
|
308
200
|
this.requestRender();
|
|
309
201
|
});
|
|
@@ -311,14 +203,12 @@ export class GeoMap {
|
|
|
311
203
|
/** Downloads and compiles the engine with 3D Tiles once the map is idle (`tiles3d: 'preload'`), without running
|
|
312
204
|
* it: the first 3D Tiles layer then only instantiates it */
|
|
313
205
|
preloadTiles3d() {
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
if (this.settled)
|
|
317
|
-
later();
|
|
206
|
+
if (this.frames.settled)
|
|
207
|
+
this.engines.preload();
|
|
318
208
|
else {
|
|
319
209
|
const off = this.on('idle', () => {
|
|
320
210
|
off();
|
|
321
|
-
|
|
211
|
+
this.engines.preload();
|
|
322
212
|
});
|
|
323
213
|
}
|
|
324
214
|
}
|
|
@@ -326,51 +216,28 @@ export class GeoMap {
|
|
|
326
216
|
restore() {
|
|
327
217
|
if (this.gone)
|
|
328
218
|
return;
|
|
329
|
-
this.
|
|
219
|
+
this.hoverPick.cancel();
|
|
330
220
|
const camera = this.getCamera();
|
|
331
221
|
this.#engine.free();
|
|
332
222
|
this.#engine = this.createEngine();
|
|
333
223
|
this.lost = false;
|
|
334
224
|
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
|
-
}
|
|
225
|
+
this.atlases.attach(this.#engine);
|
|
226
|
+
// Those still waiting for the engine with 3D Tiles (loading, or failed) are created with it
|
|
227
|
+
this.stack.reattach(this.#engine, (layer) => layer.needsFullEngine && !this.engines.tiles3d);
|
|
364
228
|
this.applySwipe();
|
|
365
229
|
// A street-level eye stays one in the new engine
|
|
366
230
|
if (this.#firstPerson)
|
|
367
231
|
this.setFirstPerson(this.#firstPerson);
|
|
368
|
-
// What the old engine drew over the scene: the viewshed, the panoramas shown
|
|
232
|
+
// What the old engine drew over the scene: the viewshed, the sun's shadows, the panoramas shown
|
|
369
233
|
if (this.viewshed)
|
|
370
234
|
this.setViewshed(this.viewshed);
|
|
371
|
-
|
|
235
|
+
if (this.sun)
|
|
236
|
+
this.setSun(this.sun);
|
|
237
|
+
this.panoramaSets.forEach((set) => {
|
|
372
238
|
if (set.current)
|
|
373
239
|
void set.show(set.current.index).catch(report);
|
|
240
|
+
});
|
|
374
241
|
this.resize();
|
|
375
242
|
}
|
|
376
243
|
/** The street-level eye, while there is one (`setFirstPerson`) */
|
|
@@ -392,13 +259,8 @@ export class GeoMap {
|
|
|
392
259
|
this.setCamera({ center: was.lonLat, zoom: 18, pitch: 60, bearing: was.bearing });
|
|
393
260
|
return;
|
|
394
261
|
}
|
|
395
|
-
const
|
|
396
|
-
|
|
397
|
-
pitch: 90,
|
|
398
|
-
fov: 75,
|
|
399
|
-
...view,
|
|
400
|
-
lonLat: [view.lonLat[0], view.lonLat[1]],
|
|
401
|
-
};
|
|
262
|
+
const lonLat = [view.lonLat[0], view.lonLat[1]];
|
|
263
|
+
const eye = { height: 1.7, pitch: 90, fov: 75, ...view, lonLat };
|
|
402
264
|
eye.pitch = Math.max(1, Math.min(179, eye.pitch));
|
|
403
265
|
eye.fov = Math.max(10, Math.min(140, eye.fov));
|
|
404
266
|
eye.bearing = ((eye.bearing % 360) + 360) % 360;
|
|
@@ -411,47 +273,41 @@ export class GeoMap {
|
|
|
411
273
|
/**
|
|
412
274
|
* Compares two states of a place: `layers` shown on one side of a vertical line only, at `position` CSS pixels
|
|
413
275
|
* from the left of the map (left of it by default, `side: 'right'` for the other), the layers beneath them on the
|
|
414
|
-
* other side
|
|
276
|
+
* other side; `other`: layers shown on the other side only (two meshes of a place, each on its side). Set again as
|
|
277
|
+
* the line moves (a slider, a drag); undefined stops
|
|
415
278
|
*/
|
|
416
279
|
setSwipe(options) {
|
|
417
|
-
|
|
280
|
+
const used = options && (options.layers.length > 0 || (options.other?.length ?? 0) > 0);
|
|
281
|
+
this.swipe = used ? { ...options, layers: [...options.layers], other: [...(options.other ?? [])] } : undefined;
|
|
418
282
|
this.applySwipe();
|
|
419
283
|
this.requestRender();
|
|
420
284
|
}
|
|
421
285
|
applySwipe() {
|
|
422
286
|
const s = this.swipe;
|
|
423
|
-
const
|
|
424
|
-
|
|
287
|
+
const idsOf = (layers) => Uint32Array.from((layers ?? []).filter((l) => l.id >= 0), (l) => l.id);
|
|
288
|
+
const ids = idsOf(s?.layers);
|
|
289
|
+
const others = idsOf(s?.other);
|
|
290
|
+
const shown = ids.length > 0 || others.length > 0;
|
|
291
|
+
this.engine.setSwipe(s && shown ? s.position : Number.NaN, s?.side === 'right', ids, others);
|
|
425
292
|
}
|
|
426
293
|
/** @internal Forgets a removed layer */
|
|
427
294
|
detach(layer) {
|
|
428
|
-
this.
|
|
429
|
-
this.
|
|
430
|
-
if (this.rasterCredits.delete(layer))
|
|
431
|
-
this.attributionChanged();
|
|
295
|
+
this.stack.forget(layer);
|
|
296
|
+
this.credits.forget(layer);
|
|
432
297
|
}
|
|
433
298
|
/** @internal The layer is on the map */
|
|
434
299
|
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);
|
|
300
|
+
return !this.gone && this.stack.has(layer);
|
|
442
301
|
}
|
|
443
302
|
addLayer(layer) {
|
|
444
303
|
// On the core engine, a 3D Tiles layer or a model waits for the engine with 3D Tiles: it is created with the
|
|
445
304
|
// others on it
|
|
446
|
-
if (layer.needsFullEngine && !this.
|
|
305
|
+
if (layer.needsFullEngine && !this.engines.tiles3d)
|
|
447
306
|
this.upgrade();
|
|
448
307
|
else
|
|
449
308
|
layer.attach(this.engine);
|
|
450
|
-
this.layers.push(layer);
|
|
451
309
|
// Under the layers kept on top (the basemap's labels)
|
|
452
|
-
|
|
453
|
-
if (top)
|
|
454
|
-
this.moveLayer(layer, top);
|
|
310
|
+
this.stack.add(layer);
|
|
455
311
|
this.requestRender();
|
|
456
312
|
return layer;
|
|
457
313
|
}
|
|
@@ -461,54 +317,21 @@ export class GeoMap {
|
|
|
461
317
|
}
|
|
462
318
|
/** @internal Moves a layer under `before`, or on top of all */
|
|
463
319
|
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));
|
|
320
|
+
this.stack.move(layer, before);
|
|
471
321
|
this.requestRender();
|
|
472
322
|
}
|
|
473
323
|
/** @internal See `LayerHost.replaceLayer` */
|
|
474
324
|
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);
|
|
325
|
+
this.stack.replace(layer, old);
|
|
493
326
|
}
|
|
494
327
|
// ── Events ──
|
|
495
328
|
/** Listens to an event; returns the function that stops listening */
|
|
496
329
|
on(type, listener) {
|
|
497
|
-
|
|
498
|
-
listeners.add(listener);
|
|
499
|
-
return () => listeners.delete(listener);
|
|
330
|
+
return this.listeners.on(type, listener);
|
|
500
331
|
}
|
|
501
332
|
/** @internal Calls the listeners; one that throws is reported and does not stop the others nor the frame */
|
|
502
333
|
emit(type, event) {
|
|
503
|
-
|
|
504
|
-
for (const listener of listeners) {
|
|
505
|
-
try {
|
|
506
|
-
listener(event);
|
|
507
|
-
}
|
|
508
|
-
catch (error) {
|
|
509
|
-
report(error);
|
|
510
|
-
}
|
|
511
|
-
}
|
|
334
|
+
this.listeners.emit(type, event);
|
|
512
335
|
}
|
|
513
336
|
// ── Camera ──
|
|
514
337
|
/**
|
|
@@ -537,13 +360,14 @@ export class GeoMap {
|
|
|
537
360
|
elevationAt: (lonLat) => this.elevationAt(lonLat),
|
|
538
361
|
metersPerPixel: (point) => this.metersPerPixel(point),
|
|
539
362
|
};
|
|
540
|
-
/** What is drawn over the scene, together: `viewshed` (`setViewshed`), `swipe` (`setSwipe`) */
|
|
363
|
+
/** What is drawn over the scene, together: `viewshed` (`setViewshed`), `sun` (`setSun`), `swipe` (`setSwipe`) */
|
|
541
364
|
effects = {
|
|
542
365
|
viewshed: (options) => this.setViewshed(options),
|
|
366
|
+
sun: (options) => this.setSun(options),
|
|
543
367
|
swipe: (options) => this.setSwipe(options),
|
|
544
368
|
};
|
|
545
369
|
getCamera() {
|
|
546
|
-
return this.
|
|
370
|
+
return this.frames.camera ?? this.engine.camera();
|
|
547
371
|
}
|
|
548
372
|
setCamera(camera) {
|
|
549
373
|
const c = { ...this.getCamera(), ...camera };
|
|
@@ -614,57 +438,67 @@ export class GeoMap {
|
|
|
614
438
|
this.engine.setSky(enabled);
|
|
615
439
|
this.requestRender();
|
|
616
440
|
}
|
|
441
|
+
/** The viewshed set, set again in a new engine */
|
|
442
|
+
viewshed;
|
|
443
|
+
/** The sun set, set again in a new engine */
|
|
444
|
+
sun;
|
|
445
|
+
/**
|
|
446
|
+
* The sun casting shadows: the buildings, trees, models, 3D Tiles and the relief shade the ground and one another,
|
|
447
|
+
* the walls turned away from it are in their own shade; the labels stay over them. The sun stands where it is at
|
|
448
|
+
* `date` over the map's center (now by default), or at `azimuth` and `altitude`. As it sets the night falls (`night`
|
|
449
|
+
* to set it by hand): the scene tinted in its blue, the windows of the buildings and the streets of `vectors()` lit,
|
|
450
|
+
* the labels light on dark.
|
|
451
|
+
* The shadows cover the ground around the view's center, fading out away from it, and cost two more depth passes
|
|
452
|
+
* of the solid scene each frame. Undefined removes them
|
|
453
|
+
*/
|
|
454
|
+
setSun(options) {
|
|
455
|
+
this.sun = options;
|
|
456
|
+
if (!options) {
|
|
457
|
+
this.engine.setSun(Number.NaN, Number.NaN, 0, 0);
|
|
458
|
+
this.requestRender();
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
const { azimuth, altitude } = options.azimuth !== undefined && options.altitude !== undefined
|
|
462
|
+
? { azimuth: options.azimuth, altitude: options.altitude }
|
|
463
|
+
: sunPosition(options.date ?? new Date(), this.getCamera().center);
|
|
464
|
+
// The twilight: from 6° above the horizon to 6° under it, smoothly
|
|
465
|
+
const dusk = Math.min(1, Math.max(0, (6 - altitude) / 12));
|
|
466
|
+
const night = Math.min(1, Math.max(0, options.night ?? dusk * dusk * (3 - 2 * dusk)));
|
|
467
|
+
// The shadows fade with the sun's light
|
|
468
|
+
const opacity = Math.min(1, Math.max(0, options.opacity ?? 0.35)) * (1 - night);
|
|
469
|
+
this.engine.setSun(azimuth, altitude, opacity, night);
|
|
470
|
+
this.requestRender();
|
|
471
|
+
}
|
|
617
472
|
/** Tints what can be seen from a point and what is hidden from it, within a range, all around or within a cone
|
|
618
473
|
* (`direction`): the relief, extrusions, 3D Tiles and models hide what lies behind them (only what the map has
|
|
619
474
|
* loaded for its view). Several observers (16 at most): what any of them sees is seen, what is hidden from all those
|
|
620
475
|
* 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
476
|
setViewshed(options) {
|
|
624
477
|
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();
|
|
478
|
+
if (applyViewshed(this.engine, options))
|
|
479
|
+
this.requestRender();
|
|
643
480
|
}
|
|
644
481
|
/** Relief under the basemap: true for the body's terrain model (on the Earth the worldwide Terrain Tiles, NASA's
|
|
645
482
|
* on the Moon and Mars), one of the Earth's models by name (`'ign'`), its options, or false; on another body, its
|
|
646
483
|
* options with a `url` */
|
|
647
484
|
setTerrain(terrain) {
|
|
648
|
-
this.settings.terrain = terrainSettings(terrain, this.body, this.settings.body.radius, this.terrainLoader);
|
|
485
|
+
this.settings.terrain = terrainSettings(terrain, this.body, this.settings.body.radius, this.workers.terrainLoader);
|
|
649
486
|
const t = this.settings.terrain;
|
|
650
487
|
this.engine.setTerrain(t?.url, t?.exaggeration ?? 1, t?.maxZoom, t?.geoid, t?.loader);
|
|
651
|
-
this.
|
|
488
|
+
this.credits.changed();
|
|
652
489
|
this.requestRender();
|
|
653
490
|
}
|
|
491
|
+
/** @internal Whether a relief is under the basemap: the fills of vector tiles drawn as geometry need their prisms */
|
|
492
|
+
get hasRelief() {
|
|
493
|
+
return this.settings.terrain !== undefined;
|
|
494
|
+
}
|
|
654
495
|
/** @internal The credits of the raster layers (the basemap first) and of the relief in use, each once */
|
|
655
496
|
attributions() {
|
|
656
|
-
|
|
657
|
-
const credits = [...layers, this.settings.terrain?.attribution];
|
|
658
|
-
return [...new Set(credits.filter((c) => c !== undefined))];
|
|
497
|
+
return this.credits.of(this.layers, this.settings.terrain?.attribution);
|
|
659
498
|
}
|
|
660
499
|
/** @internal Calls `listener` when the credits change; returns what stops it */
|
|
661
500
|
onAttributionChange(listener) {
|
|
662
|
-
this.
|
|
663
|
-
return () => this.attributionListeners.delete(listener);
|
|
664
|
-
}
|
|
665
|
-
attributionChanged() {
|
|
666
|
-
for (const listener of this.attributionListeners)
|
|
667
|
-
listener();
|
|
501
|
+
return this.credits.onChange(listener);
|
|
668
502
|
}
|
|
669
503
|
/** Color behind the map, a CSS color (its alpha ignored) */
|
|
670
504
|
setBackground(color) {
|
|
@@ -699,47 +533,9 @@ export class GeoMap {
|
|
|
699
533
|
const layer = this.layers.find((l) => l.id === hit.layer);
|
|
700
534
|
if (!layer)
|
|
701
535
|
return undefined;
|
|
702
|
-
const feature =
|
|
703
|
-
? layer.featureAt(hit.index)
|
|
704
|
-
: undefined;
|
|
536
|
+
const feature = hasFeatures(layer) ? layer.featureAt(hit.index) : undefined;
|
|
705
537
|
return { layer, index: hit.index, feature };
|
|
706
538
|
}
|
|
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
539
|
/** Screen point (CSS pixels) of a position on the ground (the relief), or `height` metres above it, undefined
|
|
744
540
|
* behind the camera */
|
|
745
541
|
project(lonLat, height = 0) {
|
|
@@ -755,41 +551,13 @@ export class GeoMap {
|
|
|
755
551
|
*/
|
|
756
552
|
async addPanoramas(options) {
|
|
757
553
|
// Drawn by the engine with 3D Tiles: loaded first if needed
|
|
758
|
-
if (!this.
|
|
554
|
+
if (!this.engines.tiles3d) {
|
|
759
555
|
this.upgrade();
|
|
760
|
-
await this.upgrading;
|
|
761
|
-
if (!this.
|
|
556
|
+
await this.engines.upgrading;
|
|
557
|
+
if (!this.engines.tiles3d)
|
|
762
558
|
throw new Error('isard: panoramas need the engine with 3D Tiles, which failed to load');
|
|
763
559
|
}
|
|
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
|
-
};
|
|
560
|
+
const host = panoramaHost(this, (lon, lat) => this.engines.geoidHeight(lon, lat));
|
|
793
561
|
const panoramas = await loadPanoramas(host, options);
|
|
794
562
|
this.panoramaSets.add(panoramas);
|
|
795
563
|
return panoramas;
|
|
@@ -836,106 +604,47 @@ export class GeoMap {
|
|
|
836
604
|
// ── Overlays and controls ──
|
|
837
605
|
/** Adds a control in a corner of the map; controls stack from the corner inward */
|
|
838
606
|
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);
|
|
607
|
+
this.overlays.addControl(control, position);
|
|
849
608
|
return this;
|
|
850
609
|
}
|
|
851
610
|
removeControl(control) {
|
|
852
|
-
|
|
853
|
-
if (!element)
|
|
854
|
-
return this;
|
|
855
|
-
this.controls.delete(control);
|
|
856
|
-
control.onRemove(this);
|
|
857
|
-
element.remove();
|
|
611
|
+
this.overlays.removeControl(control);
|
|
858
612
|
return this;
|
|
859
613
|
}
|
|
860
614
|
/** @internal Shows a marker or popup, placed after every frame from now on */
|
|
861
615
|
attachOverlay(item, element) {
|
|
862
|
-
this.
|
|
863
|
-
this.positioned.add(item);
|
|
864
|
-
item.position(this.size);
|
|
616
|
+
this.overlays.attach(item, element);
|
|
865
617
|
}
|
|
866
618
|
/** @internal */
|
|
867
619
|
detachOverlay(item, element) {
|
|
868
|
-
this.
|
|
869
|
-
element.remove();
|
|
620
|
+
this.overlays.detach(item, element);
|
|
870
621
|
}
|
|
871
622
|
/** @internal Places one overlay now (it moved), without drawing a frame */
|
|
872
623
|
positionOverlay(item) {
|
|
873
|
-
|
|
874
|
-
item.position(this.size);
|
|
624
|
+
this.overlays.position(item);
|
|
875
625
|
}
|
|
876
626
|
// ── Layers ──
|
|
877
627
|
/** Replaces the basemap and the IGN hillshade, under the other layers (hillshade by default: 0, none). The former
|
|
878
628
|
* names of the Géoplateforme basemaps throw, naming the new ones */
|
|
879
629
|
setBasemap(basemap, hillshade = 0) {
|
|
880
630
|
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
|
-
}
|
|
631
|
+
this.basemaps.replace(name);
|
|
900
632
|
// Off the Earth, the body's own color
|
|
901
633
|
if (this.earth)
|
|
902
634
|
this.settings.globeColor = SEA[name];
|
|
903
635
|
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();
|
|
636
|
+
this.basemaps.addHillshade(hillshade);
|
|
637
|
+
this.credits.changed();
|
|
910
638
|
}
|
|
911
639
|
/** Raster tiles ({z}, {x}, {y} URL; Web Mercator, or reprojected from another `grid`: longitudes and latitudes,
|
|
912
640
|
* Lambert-93, UTM…), over the layers already added */
|
|
913
641
|
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
|
-
}
|
|
642
|
+
const { url, opacity = 1, saturation = 1, size, before } = options;
|
|
643
|
+
const { loader, minZoom, maxZoom } = rasterSource(options, this.workers.remoteReprojection);
|
|
933
644
|
const layer = new RasterLayer(this, { url, minZoom, maxZoom, opacity, saturation, size, loader });
|
|
934
645
|
this.place(layer, before);
|
|
935
|
-
if (options.attribution)
|
|
936
|
-
this.
|
|
937
|
-
this.attributionChanged();
|
|
938
|
-
}
|
|
646
|
+
if (options.attribution)
|
|
647
|
+
this.credits.set(layer, options.attribution);
|
|
939
648
|
return layer;
|
|
940
649
|
}
|
|
941
650
|
/**
|
|
@@ -961,32 +670,7 @@ export class GeoMap {
|
|
|
961
670
|
* another layer. The layer's `ready` resolves once the map has drawn it
|
|
962
671
|
*/
|
|
963
672
|
add(spec) {
|
|
964
|
-
|
|
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
|
-
}
|
|
673
|
+
return addSpec(this, spec);
|
|
990
674
|
}
|
|
991
675
|
/** @internal A layer made anew from its description, in the place of `old` in the drawing order (old removed) */
|
|
992
676
|
rebuildLayer(old, spec) {
|
|
@@ -1007,7 +691,7 @@ export class GeoMap {
|
|
|
1007
691
|
off();
|
|
1008
692
|
resolve();
|
|
1009
693
|
});
|
|
1010
|
-
this.
|
|
694
|
+
this.frames.requestIdle();
|
|
1011
695
|
});
|
|
1012
696
|
}
|
|
1013
697
|
/** Lines from GeoJSON (line strings, and polygon outlines), styled per feature */
|
|
@@ -1036,24 +720,11 @@ export class GeoMap {
|
|
|
1036
720
|
}
|
|
1037
721
|
/** @internal The glyphs of the symbol layers, loaded on first use */
|
|
1038
722
|
get glyphs() {
|
|
1039
|
-
|
|
1040
|
-
// The engine's context: the atlas stays within its texture size limit
|
|
1041
|
-
const gl = this.canvas.getContext('webgl2');
|
|
1042
|
-
const max = gl?.getParameter(gl.MAX_TEXTURE_SIZE);
|
|
1043
|
-
this.#glyphs = new Glyphs(this.glyphsUrl, typeof max === 'number' ? max : undefined);
|
|
1044
|
-
this.#glyphs.attach(this.engine);
|
|
1045
|
-
}
|
|
1046
|
-
return this.#glyphs;
|
|
723
|
+
return this.atlases.glyphs;
|
|
1047
724
|
}
|
|
1048
725
|
/** @internal The icons of the symbol layers: the application's images and the vector basemaps' sprites */
|
|
1049
726
|
get icons() {
|
|
1050
|
-
|
|
1051
|
-
const gl = this.canvas.getContext('webgl2');
|
|
1052
|
-
const max = gl?.getParameter(gl.MAX_TEXTURE_SIZE);
|
|
1053
|
-
this.#icons = new Icons(typeof max === 'number' ? max : undefined);
|
|
1054
|
-
this.#icons.attach(this.engine);
|
|
1055
|
-
}
|
|
1056
|
-
return this.#icons;
|
|
727
|
+
return this.atlases.icons;
|
|
1057
728
|
}
|
|
1058
729
|
/**
|
|
1059
730
|
* An image for the symbol layers' icons (`icon: name`), as MapLibre's `addImage`: its pixels (an image, a canvas,
|
|
@@ -1062,31 +733,32 @@ export class GeoMap {
|
|
|
1062
733
|
* text. An image of the same name is replaced
|
|
1063
734
|
*/
|
|
1064
735
|
addImage(name, image, options = {}) {
|
|
1065
|
-
|
|
1066
|
-
if (!pixels)
|
|
1067
|
-
throw new Error(`isard: the image ${name} has no pixels`);
|
|
1068
|
-
this.icons.add(name, pixels, options);
|
|
736
|
+
this.atlases.addImage(name, image, options);
|
|
1069
737
|
}
|
|
1070
738
|
hasImage(name) {
|
|
1071
|
-
return this
|
|
739
|
+
return this.atlases.hasImage(name);
|
|
1072
740
|
}
|
|
1073
741
|
removeImage(name) {
|
|
1074
|
-
this
|
|
742
|
+
this.atlases.removeImage(name);
|
|
1075
743
|
}
|
|
1076
744
|
/**
|
|
1077
745
|
* Vector tiles with a MapLibre style (OpenFreeMap's Positron by default): `tiles.raster(…)` draws its fills and lines
|
|
1078
|
-
* (or a subset) as a raster layer, `tiles.
|
|
1079
|
-
* basemap.
|
|
746
|
+
* (or a subset) as a raster layer, `tiles.vectors(…)` as geometry, `tiles.labels(…)` its labels.
|
|
747
|
+
* `setBasemap('osm-positron')` draws the basemap with a raster layer and the labels.
|
|
1080
748
|
*/
|
|
1081
749
|
vectorTiles(options = {}) {
|
|
1082
750
|
const tiles = new VectorTiles(this, {
|
|
1083
|
-
...(this.
|
|
751
|
+
...(this.workers.url ? { workerUrl: this.workers.url } : {}),
|
|
1084
752
|
...options,
|
|
1085
753
|
});
|
|
1086
754
|
// Its workers stop with the map
|
|
1087
755
|
this.vectorSets.add(tiles);
|
|
1088
756
|
return tiles;
|
|
1089
757
|
}
|
|
758
|
+
/** @internal The geometry of a `VectorTiles` (`vectors`), under `before` if given */
|
|
759
|
+
addVectorLayer(before) {
|
|
760
|
+
return this.place(new VectorLayer(this), before);
|
|
761
|
+
}
|
|
1090
762
|
/** @internal Forgets a destroyed `VectorTiles` */
|
|
1091
763
|
dropVectorTiles(tiles) {
|
|
1092
764
|
this.vectorSets.delete(tiles);
|
|
@@ -1096,40 +768,8 @@ export class GeoMap {
|
|
|
1096
768
|
* when it asks for another count or URL
|
|
1097
769
|
*/
|
|
1098
770
|
basemapWorkers(count, url) {
|
|
1099
|
-
|
|
1100
|
-
const shared = defaultWorkerCount();
|
|
1101
|
-
const workers = Math.max(1, count ?? shared);
|
|
1102
|
-
const workerUrl = url ?? sharedUrl;
|
|
1103
|
-
if (workers === shared && String(workerUrl) === String(sharedUrl)) {
|
|
1104
|
-
this.basemapPool ??= new BasemapWorkers(shared, sharedUrl);
|
|
1105
|
-
return { pool: this.basemapPool, own: false };
|
|
1106
|
-
}
|
|
1107
|
-
return { pool: new BasemapWorkers(workers, workerUrl), own: true };
|
|
771
|
+
return this.workers.pool(count, url);
|
|
1108
772
|
}
|
|
1109
|
-
/**
|
|
1110
|
-
* The workers decoding the relief and reprojecting rasters: the map's pool, unless the map has no workers
|
|
1111
|
-
* (`workers: false` or 0, or none in this browser): that is done in the page then
|
|
1112
|
-
*/
|
|
1113
|
-
decodingPool() {
|
|
1114
|
-
const { workers } = this.tiles3dOptions;
|
|
1115
|
-
if (this.gone || workers === false || (workers !== undefined && workers <= 0))
|
|
1116
|
-
return undefined;
|
|
1117
|
-
if (typeof Worker === 'undefined' || typeof OffscreenCanvas === 'undefined')
|
|
1118
|
-
return undefined;
|
|
1119
|
-
return this.basemapWorkers().pool;
|
|
1120
|
-
}
|
|
1121
|
-
remoteHeights = (url, encoding, floor, tile, signal) => this.decodingPool()?.heights(url, encoding, floor, tile, signal, this);
|
|
1122
|
-
remoteReprojection = (job, tile, signal) => this.decodingPool()?.reproject(job, tile, signal, this);
|
|
1123
|
-
/** The loader of the relief tiles of `url` in a PNG encoding (one per URL, encoding and floor) */
|
|
1124
|
-
terrainLoader = (url, encoding, floor) => {
|
|
1125
|
-
const key = `${encoding} ${floor ?? ''} ${url}`;
|
|
1126
|
-
let loader = this.terrainLoaders.get(key);
|
|
1127
|
-
if (!loader) {
|
|
1128
|
-
loader = encodedTerrainLoader(url, encoding, floor, this.remoteHeights);
|
|
1129
|
-
this.terrainLoaders.set(key, loader);
|
|
1130
|
-
}
|
|
1131
|
-
return loader;
|
|
1132
|
-
};
|
|
1133
773
|
/** Adds a layer, under `before` if given */
|
|
1134
774
|
place(layer, before) {
|
|
1135
775
|
this.addLayer(layer);
|
|
@@ -1163,126 +803,31 @@ export class GeoMap {
|
|
|
1163
803
|
* WebGL, three.js… with the matrices of the frame (`frame.matrixAt`); under `before` if given
|
|
1164
804
|
*/
|
|
1165
805
|
addCustomLayer(layer, options = {}) {
|
|
1166
|
-
const custom = new CustomLayer(this, layer, this.canvas, this, (lon, lat, altitude) => this.
|
|
806
|
+
const custom = new CustomLayer(this, layer, this.canvas, this, (lon, lat, altitude) => this.engines.customMatrix(lon, lat, altitude), () => this.getCamera());
|
|
1167
807
|
return this.place(custom, options.before);
|
|
1168
808
|
}
|
|
1169
809
|
// ── Frames ──
|
|
1170
810
|
/** Asks for a frame; several calls before the next frame draw it once */
|
|
1171
811
|
requestRender() {
|
|
1172
|
-
|
|
1173
|
-
if (this.inFrame || this.lost)
|
|
1174
|
-
return;
|
|
1175
|
-
this.frameRequest ||= requestAnimationFrame((now) => this.frame(now));
|
|
812
|
+
this.frames.request();
|
|
1176
813
|
}
|
|
1177
814
|
/** @internal Runs `step` every frame until it returns false; replaces the running animation */
|
|
1178
815
|
animate(step) {
|
|
1179
|
-
|
|
1180
|
-
if (this.animation)
|
|
1181
|
-
this.engine.clearPreload();
|
|
1182
|
-
this.animation = step;
|
|
1183
|
-
this.requestRender();
|
|
816
|
+
this.frames.animate(step);
|
|
1184
817
|
}
|
|
1185
818
|
/** @internal The pointer moved over the map (undefined: it left). Without 'hover' and 'hoverfeature' listeners,
|
|
1186
819
|
* nothing is drawn: a still map stays idle under a moving mouse */
|
|
1187
820
|
hover(point) {
|
|
1188
821
|
if (!point) {
|
|
1189
|
-
this.
|
|
1190
|
-
this.
|
|
822
|
+
this.frames.hover(undefined);
|
|
823
|
+
this.hoverPick.cancel();
|
|
1191
824
|
return;
|
|
1192
825
|
}
|
|
1193
|
-
if (this.listeners.hover
|
|
826
|
+
if (!this.listeners.has('hover') && !this.listeners.has('hoverfeature'))
|
|
1194
827
|
return;
|
|
1195
|
-
this.
|
|
828
|
+
this.frames.hover(point);
|
|
1196
829
|
this.requestRender();
|
|
1197
830
|
}
|
|
1198
|
-
frame(now) {
|
|
1199
|
-
this.frameRequest = 0;
|
|
1200
|
-
try {
|
|
1201
|
-
this.inFrame = true;
|
|
1202
|
-
this.drawFrame(now);
|
|
1203
|
-
}
|
|
1204
|
-
finally {
|
|
1205
|
-
// Whatever throws in a frame, the next ones are still drawn
|
|
1206
|
-
this.inFrame = false;
|
|
1207
|
-
}
|
|
1208
|
-
}
|
|
1209
|
-
drawFrame(now) {
|
|
1210
|
-
if (this.animation && !this.animation(now))
|
|
1211
|
-
this.animation = undefined;
|
|
1212
|
-
const camera = this.getCamera();
|
|
1213
|
-
const key = `${camera.center}|${camera.zoom}|${camera.pitch}|${camera.bearing}`;
|
|
1214
|
-
const moved = key !== this.lastCamera;
|
|
1215
|
-
if (moved) {
|
|
1216
|
-
// The first frame is no move
|
|
1217
|
-
if (!this.moving && this.lastCamera !== '') {
|
|
1218
|
-
this.moving = true;
|
|
1219
|
-
this.emit('movestart', camera);
|
|
1220
|
-
}
|
|
1221
|
-
this.lastCamera = key;
|
|
1222
|
-
this.emit('move', camera);
|
|
1223
|
-
}
|
|
1224
|
-
else if (this.moving && !this.animation && !this.interaction?.active) {
|
|
1225
|
-
this.moving = false;
|
|
1226
|
-
this.emit('moveend', camera);
|
|
1227
|
-
}
|
|
1228
|
-
// Values for the pointer position are computed before drawing, in the same frame
|
|
1229
|
-
let hovered;
|
|
1230
|
-
if (this.hoverPoint) {
|
|
1231
|
-
const point = this.hoverPoint;
|
|
1232
|
-
hovered = point;
|
|
1233
|
-
this.hoverPoint = undefined;
|
|
1234
|
-
// The position under the pointer for its listeners only (a 'hoverfeature' alone does not need it)
|
|
1235
|
-
if (this.listeners.hover.size > 0)
|
|
1236
|
-
this.emit('hover', { point, lonLat: this.unproject(point) });
|
|
1237
|
-
}
|
|
1238
|
-
const t0 = performance.now();
|
|
1239
|
-
// The engine asks for the next frame while it draws (uploads past the frame budget, fades): not ignored
|
|
1240
|
-
this.inFrame = false;
|
|
1241
|
-
// As the engine has it now (a listener may have moved it), for the custom layers drawn in the frame
|
|
1242
|
-
this.frameCamera = this.layers.some((layer) => layer instanceof CustomLayer) ? this.engine.camera() : undefined;
|
|
1243
|
-
let loading;
|
|
1244
|
-
try {
|
|
1245
|
-
loading = this.engine.render();
|
|
1246
|
-
}
|
|
1247
|
-
finally {
|
|
1248
|
-
this.frameCamera = undefined;
|
|
1249
|
-
}
|
|
1250
|
-
this.inFrame = true;
|
|
1251
|
-
// The picking pass sees the frame just drawn; its answer comes on a later frame
|
|
1252
|
-
if (hovered && this.listeners.hoverfeature.size > 0)
|
|
1253
|
-
this.requestHoverPick(hovered);
|
|
1254
|
-
// Overlays follow the frame just drawn (camera, relief altitudes): all in one pass, transforms only
|
|
1255
|
-
for (const item of this.positioned)
|
|
1256
|
-
item.position(this.size);
|
|
1257
|
-
this.emit('render', { ms: performance.now() - t0 });
|
|
1258
|
-
this.inFrame = false;
|
|
1259
|
-
// A move goes on until a frame finds the camera still: that frame sends 'moveend'
|
|
1260
|
-
if (this.animation || (moved && this.moving))
|
|
1261
|
-
this.requestRender();
|
|
1262
|
-
// Idle: nothing loading (the engine with 3D Tiles neither), no animation, no frame asked for (uploads waiting,
|
|
1263
|
-
// fades, listeners)
|
|
1264
|
-
if (loading || this.upgrading || this.animation || this.frameRequest !== 0) {
|
|
1265
|
-
this.settled = false;
|
|
1266
|
-
}
|
|
1267
|
-
else if (!this.settled) {
|
|
1268
|
-
this.settled = true;
|
|
1269
|
-
this.emit('idle', {});
|
|
1270
|
-
}
|
|
1271
|
-
}
|
|
1272
|
-
/** Resizes when the device pixel ratio changes (browser zoom, a window moved to another screen) */
|
|
1273
|
-
watchRatio() {
|
|
1274
|
-
if (typeof matchMedia !== 'function')
|
|
1275
|
-
return;
|
|
1276
|
-
const query = matchMedia(`(resolution: ${window.devicePixelRatio || 1}dppx)`);
|
|
1277
|
-
const changed = () => {
|
|
1278
|
-
// A query for the new ratio
|
|
1279
|
-
this.unwatchRatio?.();
|
|
1280
|
-
this.watchRatio();
|
|
1281
|
-
this.resize();
|
|
1282
|
-
};
|
|
1283
|
-
query.addEventListener('change', changed, { once: true });
|
|
1284
|
-
this.unwatchRatio = () => query.removeEventListener('change', changed);
|
|
1285
|
-
}
|
|
1286
831
|
resize() {
|
|
1287
832
|
if (this.lost)
|
|
1288
833
|
return;
|
|
@@ -1291,11 +836,10 @@ export class GeoMap {
|
|
|
1291
836
|
this.canvas.width = Math.round(width * ratio);
|
|
1292
837
|
this.canvas.height = Math.round(height * ratio);
|
|
1293
838
|
this.engine.resize(width, height, ratio);
|
|
1294
|
-
this.size = [width, height];
|
|
839
|
+
this.overlays.size = [width, height];
|
|
1295
840
|
this.emit('resize', { width, height });
|
|
1296
841
|
// Draw at once: a resized canvas is cleared, waiting one frame would flash
|
|
1297
|
-
|
|
1298
|
-
this.frame(performance.now());
|
|
842
|
+
this.frames.drawNow();
|
|
1299
843
|
}
|
|
1300
844
|
destroy() {
|
|
1301
845
|
if (this.gone)
|
|
@@ -1304,45 +848,30 @@ export class GeoMap {
|
|
|
1304
848
|
// A context restored later rebuilds nothing
|
|
1305
849
|
this.unwatchContext();
|
|
1306
850
|
const camera = this.getCamera();
|
|
1307
|
-
|
|
1308
|
-
this.
|
|
1309
|
-
this.
|
|
1310
|
-
|
|
851
|
+
this.frames.stop();
|
|
852
|
+
this.hoverPick.cancel();
|
|
853
|
+
this.basemaps.destroy();
|
|
854
|
+
[...this.vectorSets].forEach((tiles) => {
|
|
1311
855
|
tiles.destroy();
|
|
856
|
+
});
|
|
1312
857
|
this.vectorSets.clear();
|
|
1313
|
-
this.
|
|
1314
|
-
this.
|
|
1315
|
-
this.unwatchRatio?.();
|
|
1316
|
-
this.unwatchRatio = undefined;
|
|
858
|
+
this.workers.terminate();
|
|
859
|
+
this.unwatchRatio();
|
|
1317
860
|
this.unwatchHeaders();
|
|
1318
861
|
this.resizeObserver.disconnect();
|
|
1319
862
|
this.interaction?.destroy();
|
|
1320
|
-
this.
|
|
863
|
+
this.engines.destroy();
|
|
1321
864
|
// Custom layers free their WebGL objects, models their data, while the context is there
|
|
1322
|
-
|
|
1323
|
-
layer.dispose();
|
|
865
|
+
this.stack.dispose();
|
|
1324
866
|
this.engine.free();
|
|
1325
867
|
// Work still under way (labels being shaped, tiles arriving, timers) may call the engine after this: it finds one
|
|
1326
868
|
// that does nothing, and nothing is drawn again
|
|
1327
869
|
this.#engine = destroyedEngine(camera);
|
|
1328
870
|
// Glyph ranges still arriving upload to it, not to the freed engine
|
|
1329
|
-
this
|
|
1330
|
-
this.#icons?.attach(this.#engine);
|
|
871
|
+
this.atlases.attach(this.#engine);
|
|
1331
872
|
this.lost = true;
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
this.positioned.clear();
|
|
1335
|
-
this.canvas.remove();
|
|
1336
|
-
this.overlay.remove();
|
|
1337
|
-
for (const position of CORNERS)
|
|
1338
|
-
this.corners[position].remove();
|
|
1339
|
-
this.container.classList.remove('isard-map');
|
|
1340
|
-
for (const listeners of Object.values(this.listeners))
|
|
1341
|
-
listeners.clear();
|
|
873
|
+
this.overlays.destroy();
|
|
874
|
+
this.listeners.clear();
|
|
1342
875
|
}
|
|
1343
876
|
}
|
|
1344
|
-
/** The page's request headers to an engine (its own requests: raster tiles, tilesets, point clouds) */
|
|
1345
|
-
function sendHeaders(engine, rules) {
|
|
1346
|
-
engine.setRequestHeaders(rules.map(([prefix]) => prefix), Uint32Array.from(rules, ([, headers]) => Object.keys(headers).length), rules.flatMap(([, headers]) => Object.entries(headers).flat()));
|
|
1347
|
-
}
|
|
1348
877
|
//# sourceMappingURL=map.js.map
|