isard 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/dist/basemap.worker.d.ts +7 -1
  3. package/dist/basemap.worker.js +2 -2
  4. package/dist/basemap.worker.js.map +8 -7
  5. package/dist/decoder.worker.js +4 -4
  6. package/dist/decoder.worker.js.map +6 -5
  7. package/dist/engine-loader.d.ts +1 -1
  8. package/dist/engine-loader.js.map +1 -1
  9. package/dist/geoplateforme.js +2 -1
  10. package/dist/geoplateforme.js.map +1 -1
  11. package/dist/glyphs.js +2 -1
  12. package/dist/glyphs.js.map +1 -1
  13. package/dist/icons.js +5 -1
  14. package/dist/icons.js.map +1 -1
  15. package/dist/index.d.ts +4 -2
  16. package/dist/index.js +3 -1
  17. package/dist/index.js.map +1 -1
  18. package/dist/interaction.d.ts +2 -0
  19. package/dist/interaction.js +29 -2
  20. package/dist/interaction.js.map +1 -1
  21. package/dist/isard_bg.wasm +0 -0
  22. package/dist/isard_core_bg.wasm +0 -0
  23. package/dist/layers.d.ts +2 -0
  24. package/dist/layers.js +22 -4
  25. package/dist/layers.js.map +1 -1
  26. package/dist/loader.d.ts +2 -0
  27. package/dist/loader.js +10 -0
  28. package/dist/loader.js.map +1 -1
  29. package/dist/map.d.ts +38 -1
  30. package/dist/map.js +153 -2
  31. package/dist/map.js.map +1 -1
  32. package/dist/overlay.d.ts +40 -0
  33. package/dist/overlay.js +119 -0
  34. package/dist/overlay.js.map +1 -1
  35. package/dist/panoramas.d.ts +94 -0
  36. package/dist/panoramas.js +245 -0
  37. package/dist/panoramas.js.map +1 -0
  38. package/dist/pmtiles.js +3 -2
  39. package/dist/pmtiles.js.map +1 -1
  40. package/dist/pointclouds.js +1 -1
  41. package/dist/pointclouds.js.map +1 -1
  42. package/dist/protocol.d.ts +7 -1
  43. package/dist/protocol.js.map +1 -1
  44. package/dist/requests.d.ts +14 -0
  45. package/dist/requests.js +48 -0
  46. package/dist/requests.js.map +1 -0
  47. package/dist/resample.js +2 -1
  48. package/dist/resample.js.map +1 -1
  49. package/dist/style.js +1 -0
  50. package/dist/style.js.map +1 -1
  51. package/dist/terrain.d.ts +0 -5
  52. package/dist/terrain.js +2 -1
  53. package/dist/terrain.js.map +1 -1
  54. package/dist/types.d.ts +29 -1
  55. package/dist/types.js.map +1 -1
  56. package/dist/vectortiles.js +12 -1
  57. package/dist/vectortiles.js.map +1 -1
  58. package/dist/wasm/isard.d.ts +79 -5
  59. package/dist/wasm/isard.js +165 -7
  60. package/dist/wasm/isard_core.d.ts +38 -0
  61. package/dist/wasm/isard_core.js +99 -0
  62. package/dist/wmts.js +2 -1
  63. package/dist/wmts.js.map +1 -1
  64. package/package.json +1 -1
package/dist/map.d.ts CHANGED
@@ -3,9 +3,10 @@ import { ExtrusionLayer, type ExtrusionOptions } from './extrusions.ts';
3
3
  import { CircleLayer, type CircleOptions, FillLayer, type FillOptions, LineLayer, type LineOptions } from './features.ts';
4
4
  import { type ImageOptions, type Pixels } from './icons.ts';
5
5
  import { CustomLayer, type CustomLayerInterface, GroundLayer, type GroundOptions, type Layer, ModelLayer, type ModelOptions, NetworkLayer, RasterLayer, Tiles3dLayer } from './layers.ts';
6
+ import { type PanoramaOptions, type Panoramas } from './panoramas.ts';
6
7
  import { PointCloudLayer, type PointCloudOptions } from './pointclouds.ts';
7
8
  import { SymbolLayer, type SymbolOptions } from './symbols.ts';
8
- import type { AnimationOptions, AtmosphereOptions, Basemap, FeatureHit, FlyToOptions, GeoMapOptions, LoadingMode, LonLat, MapEvents, NetworkOptions, Point, Projection, RasterOptions, TerrainModel, TerrainOptions, Tiles3dOptions, ViewshedOptions } from './types.ts';
9
+ import type { AnimationOptions, AtmosphereOptions, Basemap, FeatureHit, FirstPersonView, FlyToOptions, GeoMapOptions, LoadingMode, LonLat, MapEvents, NetworkOptions, Point, Projection, RasterOptions, SwipeOptions, TerrainModel, TerrainOptions, Tiles3dOptions, ViewshedOptions } from './types.ts';
9
10
  import { type PmtilesOptions, VectorTiles, type VectorTilesOptions } from './vectortiles.ts';
10
11
  import type { Camera, PickHit, RasterLayerStats } from './wasm/isard.js';
11
12
  /** A map (see `GeoMap.create`); layers take it as their host */
@@ -82,6 +83,8 @@ export declare class GeoMap {
82
83
  private basemapPool;
83
84
  /** Stops watching the device pixel ratio */
84
85
  private unwatchRatio;
86
+ /** Stops following the page's request headers (`setRequestHeaders`) */
87
+ private unwatchHeaders;
85
88
  /** Stops listening to the canvas's context loss and restoration */
86
89
  private readonly unwatchContext;
87
90
  /** The loaders of the relief by encoding, URL and floor: the same loader for the same tiles (the engine keeps its
@@ -104,6 +107,23 @@ export declare class GeoMap {
104
107
  private preloadTiles3d;
105
108
  /** After a context loss: a new engine with the same camera, settings and layers (same handles, same order) */
106
109
  private restore;
110
+ /** The street-level eye, if the camera is one */
111
+ get firstPerson(): Readonly<Required<FirstPersonView>> | undefined;
112
+ /**
113
+ * Puts the camera at street level: an eye `height` metres above the ground, looking `bearing` and `pitch`, the
114
+ * drag turning it around (the wheel narrows or widens its field of view); undefined gives back the camera turning
115
+ * around a point, above where the eye stood, facing the same way
116
+ */
117
+ setFirstPerson(view: FirstPersonView | undefined): void;
118
+ /** The swipe set (`setSwipe`), applied again with the layers' ids of a new engine */
119
+ private swipe;
120
+ /**
121
+ * Compares two states of a place: `layers` shown on one side of a vertical line only, at `position` CSS pixels
122
+ * from the left of the map (left of it by default, `side: 'right'` for the other), the layers beneath them on the
123
+ * other side. Set again as the line moves (a slider, a drag); undefined stops
124
+ */
125
+ setSwipe(options: SwipeOptions | undefined): void;
126
+ private applySwipe;
107
127
  /** Takes a layer out of the drawing order (to put it back elsewhere, or for good) */
108
128
  private unlist;
109
129
  private add;
@@ -140,6 +160,8 @@ export declare class GeoMap {
140
160
  * (`direction`): the relief, extrusions, 3D Tiles and models hide what lies behind them (only what the map has
141
161
  * loaded for its view). Several observers (16 at most): what any of them sees is seen, what is hidden from all those
142
162
  * that reach it is hidden, in the colors and opacity of the first. Undefined or none removes it */
163
+ /** The viewshed set, set again in a new engine */
164
+ private viewshed;
143
165
  setViewshed(options: ViewshedOptions | readonly ViewshedOptions[] | undefined): void;
144
166
  /** Relief under the basemap: true for the body's terrain model (on the Earth the worldwide Terrain Tiles, NASA's
145
167
  * on the Moon and Mars), one of the Earth's models by name (`'ign'`), its options, or false; on another body, its
@@ -176,6 +198,21 @@ export declare class GeoMap {
176
198
  project(lonLat: LonLat, height?: number): Point | undefined;
177
199
  /** Position under a screen point (CSS pixels), undefined above the horizon */
178
200
  unproject(point: Point): LonLat | undefined;
201
+ /**
202
+ * Street-level panoramas (oriented images of a vehicle's cameras, the iTowns immersive format): their positions
203
+ * and calibrations loaded, none shown until `show`, which projects one onto the scene around it
204
+ */
205
+ addPanoramas(options: PanoramaOptions): Promise<Panoramas>;
206
+ /** The panorama sets, their panorama shown again in a new engine */
207
+ private readonly panoramaSets;
208
+ /** Altitude of the relief at a position, in metres (exaggeration included): from the copy of its tiles the map
209
+ * keeps, as fine as the tiles loaded there; undefined without relief or where none is loaded yet */
210
+ elevationAt(lonLat: LonLat): number | undefined;
211
+ /** Metres on the ground per CSS pixel at a screen point (default the center of the map), across the screen: for a
212
+ * scale bar; undefined above the horizon */
213
+ metersPerPixel(point?: Point): number | undefined;
214
+ /** Whether a double click zooms in (Shift: out), on by default: off while a drawing takes double clicks */
215
+ setDoubleClickZoom(enabled: boolean): void;
179
216
  /** What is drawn under a screen point (CSS pixels), on the relief or standing on it (a roof, a wall, a mesh): its
180
217
  * position and its height above the relief, in metres; undefined where nothing was drawn. The last frame's scene is
181
218
  * drawn again and read back, waiting for the GPU (a stall): for clicks and drags */
package/dist/map.js CHANGED
@@ -15,7 +15,9 @@ import { equirectangularGrid, gridLoader, gridZooms, resolveGrid } from "./grid.
15
15
  import { Icons, readPixels } from "./icons.js";
16
16
  import { Interaction } from "./interaction.js";
17
17
  import { CustomLayer, GroundLayer, ModelLayer, NetworkLayer, RasterLayer, Tiles3dLayer, } from "./layers.js";
18
+ import { loadPanoramas } from "./panoramas.js";
18
19
  import { PointCloudLayer } from "./pointclouds.js";
20
+ import { headerRules, onHeaderRules } from "./requests.js";
19
21
  import { atmosphereSettings, bodySettings, haloSettings, rgb, terrainSettings, } from "./settings.js";
20
22
  import { injectStyle } from "./style.js";
21
23
  import { SymbolLayer, symbolStyle } from "./symbols.js";
@@ -138,6 +140,8 @@ export class GeoMap {
138
140
  basemapPool;
139
141
  /** Stops watching the device pixel ratio */
140
142
  unwatchRatio;
143
+ /** Stops following the page's request headers (`setRequestHeaders`) */
144
+ unwatchHeaders = () => undefined;
141
145
  /** Stops listening to the canvas's context loss and restoration */
142
146
  unwatchContext;
143
147
  /** The loaders of the relief by encoding, URL and floor: the same loader for the same tiles (the engine keeps its
@@ -202,6 +206,8 @@ export class GeoMap {
202
206
  globeColor: this.earth ? SEA['osm-positron'] : rgb(body.color),
203
207
  };
204
208
  this.#engine = this.createEngine();
209
+ // Once it has an engine: a map whose creation failed before is not left following them
210
+ this.unwatchHeaders = onHeaderRules((rules) => sendHeaders(this.#engine, rules));
205
211
  // The browser may take the GPU away (driver reset, background tab on mobile): the map waits, then rebuilds
206
212
  const lost = (event) => {
207
213
  event.preventDefault();
@@ -256,6 +262,7 @@ export class GeoMap {
256
262
  // The body first: the layers' shaders may take its size
257
263
  const { radius, flattening, atmosphere: air, relief } = this.settings.body;
258
264
  engine.setBody(radius, flattening, haloSettings(air), ...relief);
265
+ sendHeaders(engine, headerRules());
259
266
  const { zoomRange, maxPitch, globe, sky, atmosphere, terrain, loading, background, globeColor } = this.settings;
260
267
  engine.setZoomRange(...zoomRange);
261
268
  engine.setMaxPitch(maxPitch);
@@ -349,8 +356,68 @@ export class GeoMap {
349
356
  report(error);
350
357
  }
351
358
  }
359
+ this.applySwipe();
360
+ // A street-level eye stays one in the new engine
361
+ if (this.#firstPerson)
362
+ this.setFirstPerson(this.#firstPerson);
363
+ // What the old engine drew over the scene: the viewshed, the panoramas shown
364
+ if (this.viewshed)
365
+ this.setViewshed(this.viewshed);
366
+ for (const set of this.panoramaSets)
367
+ if (set.current)
368
+ void set.show(set.current.index).catch(report);
352
369
  this.resize();
353
370
  }
371
+ /** The street-level eye, while there is one (`setFirstPerson`) */
372
+ #firstPerson;
373
+ /** The street-level eye, if the camera is one */
374
+ get firstPerson() {
375
+ return this.#firstPerson;
376
+ }
377
+ /**
378
+ * Puts the camera at street level: an eye `height` metres above the ground, looking `bearing` and `pitch`, the
379
+ * drag turning it around (the wheel narrows or widens its field of view); undefined gives back the camera turning
380
+ * around a point, above where the eye stood, facing the same way
381
+ */
382
+ setFirstPerson(view) {
383
+ if (!view) {
384
+ const was = this.#firstPerson;
385
+ this.#firstPerson = undefined;
386
+ if (was)
387
+ this.setCamera({ center: was.lonLat, zoom: 18, pitch: 60, bearing: was.bearing });
388
+ return;
389
+ }
390
+ const eye = {
391
+ height: 1.7,
392
+ pitch: 90,
393
+ fov: 75,
394
+ ...view,
395
+ lonLat: [view.lonLat[0], view.lonLat[1]],
396
+ };
397
+ eye.pitch = Math.max(1, Math.min(179, eye.pitch));
398
+ eye.fov = Math.max(10, Math.min(140, eye.fov));
399
+ eye.bearing = ((eye.bearing % 360) + 360) % 360;
400
+ this.#firstPerson = eye;
401
+ this.engine.setFirstPerson(eye.lonLat[0], eye.lonLat[1], eye.height, eye.bearing, eye.pitch, eye.fov);
402
+ this.requestRender();
403
+ }
404
+ /** The swipe set (`setSwipe`), applied again with the layers' ids of a new engine */
405
+ swipe;
406
+ /**
407
+ * Compares two states of a place: `layers` shown on one side of a vertical line only, at `position` CSS pixels
408
+ * from the left of the map (left of it by default, `side: 'right'` for the other), the layers beneath them on the
409
+ * other side. Set again as the line moves (a slider, a drag); undefined stops
410
+ */
411
+ setSwipe(options) {
412
+ this.swipe = options && options.layers.length > 0 ? { ...options, layers: [...options.layers] } : undefined;
413
+ this.applySwipe();
414
+ this.requestRender();
415
+ }
416
+ applySwipe() {
417
+ const s = this.swipe;
418
+ const ids = Uint32Array.from((s?.layers ?? []).filter((l) => l.id >= 0), (l) => l.id);
419
+ this.engine.setSwipe(s && ids.length > 0 ? s.position : Number.NaN, s?.side === 'right', ids);
420
+ }
354
421
  /** @internal Forgets a removed layer */
355
422
  detach(layer) {
356
423
  this.unlist(layer);
@@ -515,7 +582,10 @@ export class GeoMap {
515
582
  * (`direction`): the relief, extrusions, 3D Tiles and models hide what lies behind them (only what the map has
516
583
  * loaded for its view). Several observers (16 at most): what any of them sees is seen, what is hidden from all those
517
584
  * that reach it is hidden, in the colors and opacity of the first. Undefined or none removes it */
585
+ /** The viewshed set, set again in a new engine */
586
+ viewshed;
518
587
  setViewshed(options) {
588
+ this.viewshed = options;
519
589
  let observers = [];
520
590
  if (options)
521
591
  observers = 'position' in options ? [options] : options;
@@ -643,6 +713,77 @@ export class GeoMap {
643
713
  unproject(point) {
644
714
  return this.engine.ground(point[0], point[1]);
645
715
  }
716
+ /**
717
+ * Street-level panoramas (oriented images of a vehicle's cameras, the iTowns immersive format): their positions
718
+ * and calibrations loaded, none shown until `show`, which projects one onto the scene around it
719
+ */
720
+ async addPanoramas(options) {
721
+ // Drawn by the engine with 3D Tiles: loaded first if needed
722
+ if (!this.tiles3dModule) {
723
+ this.upgrade();
724
+ await this.upgrading;
725
+ if (!this.tiles3dModule)
726
+ throw new Error('isard: panoramas need the engine with 3D Tiles, which failed to load');
727
+ }
728
+ const host = {
729
+ setPanorama: (...args) => {
730
+ this.engine.setPanorama(...args);
731
+ this.requestRender();
732
+ },
733
+ setPanoramaImage: (index, image) => this.engine.setPanoramaImage(index, image),
734
+ clearPanorama: () => this.engine.clearPanorama(),
735
+ geoidHeight: (lon, lat) => this.engineClass.geoidHeight(lon, lat),
736
+ firstPerson: () => this.firstPerson,
737
+ setFirstPerson: (view) => this.setFirstPerson(view),
738
+ elevationAt: (lonLat) => this.elevationAt(lonLat),
739
+ listen: (click, key, wheel) => {
740
+ const off = this.on('click', ({ lonLat }) => click(lonLat));
741
+ const container = this.getContainer();
742
+ // Before the map's own wheel (its zoom, the eye's field of view)
743
+ const onWheel = (e) => {
744
+ e.preventDefault();
745
+ e.stopImmediatePropagation();
746
+ wheel(e);
747
+ };
748
+ container.addEventListener('keydown', key);
749
+ container.addEventListener('wheel', onWheel, { capture: true, passive: false });
750
+ return () => {
751
+ off();
752
+ container.removeEventListener('keydown', key);
753
+ container.removeEventListener('wheel', onWheel, { capture: true });
754
+ };
755
+ },
756
+ };
757
+ const panoramas = await loadPanoramas(host, options);
758
+ this.panoramaSets.add(panoramas);
759
+ return panoramas;
760
+ }
761
+ /** The panorama sets, their panorama shown again in a new engine */
762
+ panoramaSets = new Set();
763
+ /** Altitude of the relief at a position, in metres (exaggeration included): from the copy of its tiles the map
764
+ * keeps, as fine as the tiles loaded there; undefined without relief or where none is loaded yet */
765
+ elevationAt(lonLat) {
766
+ return this.engine.elevationAt(lonLat[0], lonLat[1]);
767
+ }
768
+ /** Metres on the ground per CSS pixel at a screen point (default the center of the map), across the screen: for a
769
+ * scale bar; undefined above the horizon */
770
+ metersPerPixel(point) {
771
+ const [x, y] = point ?? [this.canvas.clientWidth / 2, this.canvas.clientHeight / 2];
772
+ const a = this.unproject([x - 0.5, y]);
773
+ const b = this.unproject([x + 0.5, y]);
774
+ if (!a || !b)
775
+ return undefined;
776
+ // Haversine, on the mean radius of the body
777
+ const rad = Math.PI / 180;
778
+ const [dLat, dLon] = [(b[1] - a[1]) * rad, (b[0] - a[0]) * rad];
779
+ const h = Math.sin(dLat / 2) ** 2 + Math.cos(a[1] * rad) * Math.cos(b[1] * rad) * Math.sin(dLon / 2) ** 2;
780
+ return 2 * this.settings.body.radius * Math.asin(Math.min(1, Math.sqrt(h)));
781
+ }
782
+ /** Whether a double click zooms in (Shift: out), on by default: off while a drawing takes double clicks */
783
+ setDoubleClickZoom(enabled) {
784
+ if (this.interaction)
785
+ this.interaction.doubleClickZoom = enabled;
786
+ }
646
787
  /** What is drawn under a screen point (CSS pixels), on the relief or standing on it (a roof, a wall, a mesh): its
647
788
  * position and its height above the relief, in metres; undefined where nothing was drawn. The last frame's scene is
648
789
  * drawn again and read back, waiting for the GPU (a stall): for clicks and drags */
@@ -907,8 +1048,13 @@ export class GeoMap {
907
1048
  }
908
1049
  /** Textured meshes in 3D Tiles (photogrammetry, buildings), drawn with depth over the basemap */
909
1050
  addTiles3d(options) {
910
- const { url, maxScreenSpaceError = 16, opacity = 1, pointSize } = options;
911
- return this.add(new Tiles3dLayer(this, url, { maxScreenSpaceError, opacity, ...(pointSize !== undefined ? { pointSize } : {}) }));
1051
+ const { url, maxScreenSpaceError = 16, opacity = 1, pointSize, color, geoid = false } = options;
1052
+ return this.add(new Tiles3dLayer(this, url, geoid, {
1053
+ maxScreenSpaceError,
1054
+ opacity,
1055
+ ...(pointSize !== undefined ? { pointSize } : {}),
1056
+ ...(color !== undefined ? { color } : {}),
1057
+ }));
912
1058
  }
913
1059
  /**
914
1060
  * A point cloud (see `PointCloudOptions`): a COPC file read by range requests (IGN's LiDAR HD…), or 3D Tiles
@@ -1077,6 +1223,7 @@ export class GeoMap {
1077
1223
  this.basemapPool = undefined;
1078
1224
  this.unwatchRatio?.();
1079
1225
  this.unwatchRatio = undefined;
1226
+ this.unwatchHeaders();
1080
1227
  this.resizeObserver.disconnect();
1081
1228
  this.interaction?.destroy();
1082
1229
  this.loader?.destroy();
@@ -1103,4 +1250,8 @@ export class GeoMap {
1103
1250
  listeners.clear();
1104
1251
  }
1105
1252
  }
1253
+ /** The page's request headers to an engine (its own requests: raster tiles, tilesets, point clouds) */
1254
+ function sendHeaders(engine, rules) {
1255
+ engine.setRequestHeaders(rules.map(([prefix]) => prefix), Uint32Array.from(rules, ([, headers]) => Object.keys(headers).length), rules.flatMap(([, headers]) => Object.entries(headers).flat()));
1256
+ }
1106
1257
  //# sourceMappingURL=map.js.map