isard 2.4.1 → 2.5.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 CHANGED
@@ -5,6 +5,38 @@ versions [Semantic Versioning](https://semver.org) (before 1.0.0, a minor versio
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [2.5.0] - 2026-11-02
9
+
10
+ With `isard-react-native` and `isard-map` (Python) 2.5.0.
11
+
12
+ ### Added
13
+
14
+ - Finding a feature and framing it (#176): `map.fitBounds(box, options)` frames a box on the ground or in space (its
15
+ bottom and top altitudes: a building, a pipe underground), all of it within the canvas less the padding, at the pitch
16
+ and bearing given; `tiles3d.refine(box)` loads the tiles meeting a box to their finest level, first, in view or not,
17
+ resolved once they are there; `tiles3d.find(matches)` gives the first feature of the tiles drawn whose properties
18
+ match; `map.geoidHeight([lon, lat])`. The example Find a building, in its four versions.
19
+ - `onGpuLost` on `<IsardMap>` (the `'gpulost'` event).
20
+
21
+ ### Fixed
22
+
23
+ - The API page listed `map.camera`, `map.query` and `map.effects`, gone since the public API was made consistent.
24
+
25
+ ## [2.4.2] - 2026-11-02
26
+
27
+ With `isard-react-native` and `isard-map` (Python) 2.4.2.
28
+
29
+ ### Added
30
+
31
+ - `map.renderer` (`'webgpu'` or `'webgl2'`) and the `'gpulost'` event: `{ reason, message, losses, recovery }`; in
32
+ `isard-react-native`, the view's `onError` hears it, and the examples' app too.
33
+
34
+ ### Fixed
35
+
36
+ - A map whose WebGPU device is lost (an iPhone taking its memory back: Paris by night went black and stayed so) is made
37
+ again lighter so as not to lose it again: the low quality and half the GPU memory budget at the first loss, the WebGL2
38
+ fallback at the next one, or when WebGPU gives no new device.
39
+
8
40
  ## [2.4.1] - 2026-11-02
9
41
 
10
42
  With `isard-react-native` and `isard-map` (Python) 2.4.1.
package/README.md CHANGED
@@ -187,10 +187,13 @@ More in [Develop and release](https://ideal-adventure-zqn6y6p.pages.github.io/gu
187
187
  By priority:
188
188
 
189
189
  - **P1**, next: the memory budget measured on a phone (a phone closes a page past about a gigabyte).
190
- - **P2**, then: rasters draped within an area; a clipping volume; a performance report.
191
- - **P3**, later: objects moving along paths; video in the scene.
192
- - **P4**, when time allows: the whole MapLibre style drawn as vectors, a 3D vector map to walk through, Panoramax's
193
- street-level photos, fluids on the globe, several projections on the globe, smaller items.
190
+ - **P2**, then: OGC API - Features and Tiles (#177); virtual point clouds (#183); Cloud-Optimized GeoTIFF (#184);
191
+ measured against iTowns on 3D data (#178); rasters draped within an area; a performance report.
192
+ - **P3**, later: several elevation sources (#179); a time for layers (#180); objects moving along paths (#181); photos
193
+ projected onto the meshes (#187) and video in the scene; composable effects over the frame (#182).
194
+ - **P4**, when time allows: Gaussian splats (#186), WebXR (#185), the whole MapLibre style drawn as vectors, a 3D
195
+ vector map to walk through, Panoramax's street-level photos, fluids on the globe, several projections on the globe,
196
+ smaller items.
194
197
 
195
198
  See [Roadmap](https://ideal-adventure-zqn6y6p.pages.github.io/guide/roadmap).
196
199
 
package/dist/index.d.ts CHANGED
@@ -67,7 +67,7 @@ export { type VectorFeature, VectorLayer, type VectorSublayer } from './layers/v
67
67
  export type { HoverPicking } from './map/hover-pick.ts';
68
68
  export { GeoMap, type LayerSpec } from './map.ts';
69
69
  export { PANORAMAX_API, type PanoramaxPicture, type PanoramaxSequenceOptions, panoramaxPanoramas, panoramaxPhoto, panoramaxPicture, panoramaxSequence, panoramaxStyle, } from './panoramax.ts';
70
- export type { AnimationOptions, AtmosphereOptions, Basemap, BasemapName, BodyOptions, CelestialBody, ClipBox, ClipCylinder, ClipPolygon, ClipVolume, Drape, FeatureColor, FeatureHit, FirstPersonView, FlyToOptions, GeoMapOptions, LoadingMode, LonLat, MapEvents, MapGpuTimings, MapHoverFeatureEvent, MapMemory, MapPointerEvent, NetworkOptions, NetworkStyle, PickOptions, Placement, Point, Projection, Quality, RasterGrid, RasterOptions, RasterStyle, SunOptions, SwipeOptions, TerrainEncoding, TerrainModel, TerrainOptions, Tiles3dOptions, Tiles3dStyle, ViewshedOptions, } from './types.ts';
70
+ export type { AnimationOptions, AtmosphereOptions, Basemap, BasemapName, BodyOptions, CelestialBody, ClipBox, ClipCylinder, ClipPolygon, ClipVolume, Drape, FeatureColor, FeatureHit, FirstPersonView, FitBoundsOptions, FitBox, FlyToOptions, GeoMapOptions, LoadingMode, LonLat, MapEvents, MapGpuTimings, MapHoverFeatureEvent, MapMemory, MapPointerEvent, NetworkOptions, NetworkStyle, PickOptions, Placement, Point, Projection, Quality, RasterGrid, RasterOptions, RasterStyle, SunOptions, SwipeOptions, TerrainEncoding, TerrainModel, TerrainOptions, Tiles3dOptions, Tiles3dStyle, ViewshedOptions, } from './types.ts';
71
71
  export { AttributionControl, type AttributionOptions, type Control, type ControlPosition, NavigationControl, type NavigationOptions, } from './ui/controls.ts';
72
72
  export { MinimapControl, type MinimapOptions } from './ui/minimap.ts';
73
73
  export { type Anchor, Marker, type MarkerEvent, type MarkerEvents, type MarkerOptions, Panel, type PanelOptions, Popup, type PopupEvents, type PopupOptions, } from './ui/overlay.ts';
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * isard: a WebGL2 map engine for large, interactive data layers, labels and 3D Tiles.\n *\n * ```ts\n * import { GeoMap } from 'isard';\n * const map = await GeoMap.create(container, { center: [2.4246, 48.8457], zoom: 13 });\n * const network = map.addNetwork({ nodes, edges, max: 1800, contour: 300 });\n * map.on('hover', ({ lonLat }) => lonLat && network.setValues(travelTimes(lonLat)));\n * map.addTiles3d({ url: 'https://…/tileset.json' });\n * map.addControl(new NavigationControl());\n * new Marker().setLonLat([2.4246, 48.8457]).setPopup(new Popup({ offset: [0, -34] }).setText('Here')).addTo(map);\n * ```\n */\nexport { OPENFREEMAP_ATTRIBUTION, TERRAIN_TILES_ATTRIBUTION } from './basemap/basemaps.ts';\nexport type { TileGrid, TileMatrix } from './basemap/grid.ts';\nexport { coniferModel, type TreeColors, treeModel } from './basemap/models/trees.ts';\nexport {\n PMTiles,\n type PmtilesEntry,\n type PmtilesHeader,\n type PmtilesTileType,\n tileIdToZxy,\n zxyToTileId,\n} from './basemap/pmtiles.ts';\nexport { basicStyle, type PmtilesOptions } from './basemap/pmtileslayer.ts';\nexport type {\n CandidateIcon,\n CandidateOverlap,\n Json,\n LabelCandidate,\n RasterSource,\n Style,\n StyleLayer,\n} from './basemap/vectorstyle.ts';\nexport type { VectorExtrusionOptions } from './basemap/vectortiles/extrusions.ts';\nexport type { FurnitureOptions } from './basemap/vectortiles/furniture.ts';\nexport type { LabelsOptions } from './basemap/vectortiles/labels.ts';\nexport type { LampsOptions } from './basemap/vectortiles/lamps.ts';\nexport type { StreetWindLayer, StreetWindOptions } from './basemap/vectortiles/street-wind.ts';\nexport type { LoadedStyle } from './basemap/vectortiles/style.ts';\nexport type { TreesOptions } from './basemap/vectortiles/trees.ts';\nexport { isStreet, type VectorsOptions } from './basemap/vectortiles/vectors.ts';\nexport type { VectorRasterOptions, VectorTilesOptions } from './basemap/vectortiles.ts';\nexport { OPENFREEMAP_STYLES, PLAN_STYLES, VectorTiles } from './basemap/vectortiles.ts';\nexport { parseWmtsGrid, wmtsGrid } from './basemap/wmts.ts';\nexport { type ColladaImages, colladaImages, loadCollada, parseCollada } from './data/collada.ts';\nexport {\n type Crs,\n type CrsLike,\n getCrs,\n lambertConformalConic,\n registerCrs,\n reproject,\n transverseMercator,\n} from './data/crs.ts';\nexport { parseGpx } from './data/gpx.ts';\nexport {\n type IfcElement,\n type IfcModel,\n type IfcOptions,\n type IfcPlace,\n type IfcProperties,\n type IfcStorey,\n loadIfc,\n WEB_IFC,\n} from './data/ifc.ts';\nexport { parseKml } from './data/kml.ts';\nexport { parseTopoJson, type TopoGeometry, type Topology } from './data/topojson.ts';\nexport type { DecoderUrls } from './decoding/decoders.ts';\nexport {\n BATIMENTS_3D_URL,\n type BdTopoBuilding,\n batiments3d,\n bdtopoBuildings,\n bdtopoRoofs,\n type GeocodedPlace,\n geoplateformeGeocode,\n geoplateformeWfs,\n geoplateformeWms,\n geoplateformeWmts,\n IGN_ATTRIBUTION,\n IGN_PRESETS,\n type IgnPreset,\n lambert93Grid,\n} from './geoplateforme.ts';\nexport { type Grid, windAt, windFields } from './grids.ts';\nexport type { CustomFrame, CustomLayerInterface } from './layers/custom.ts';\nexport { CustomLayer } from './layers/custom.ts';\nexport type { ExtrusionOptions, ExtrusionStyle, PolygonArrays, TileKey } from './layers/extrusions.ts';\nexport { ExtrusionLayer } from './layers/extrusions.ts';\nexport type {\n CircleOptions,\n CircleStyle,\n Feature,\n FeatureCollection,\n FeatureData,\n FeatureLayerStyle,\n FillOptions,\n FillStyle,\n Geometry,\n LineOptions,\n LineStyle,\n Position,\n Properties,\n Ramp,\n Size,\n Stops,\n Styled,\n} from './layers/features.ts';\nexport { CircleLayer, FeatureLayer, FillLayer, interpolate, LineLayer, parseColor } from './layers/features.ts';\nexport type { FlowField, FlowOptions, FlowStyle, TimedField } from './layers/flow.ts';\nexport { FlowLayer, flowFieldFromImage, flowFieldsFromImage } from './layers/flow.ts';\nexport type { TextPart } from './layers/glyphs.ts';\nexport { DEFAULT_FONT, GLYPHS_URL } from './layers/glyphs.ts';\nexport type { GroundOptions, GroundTexture, GroundUniform } from './layers/ground.ts';\nexport { GroundLayer } from './layers/ground.ts';\nexport type { ImageOptions, Pixels, SpriteIcon } from './layers/icons.ts';\nexport { Layer } from './layers/layers.ts';\nexport type { ModelInstance, ModelOptions } from './layers/models.ts';\nexport { ModelLayer } from './layers/models.ts';\nexport { NetworkLayer } from './layers/network.ts';\nexport {\n type CalibratedPanoramaOptions,\n type Panorama,\n type PanoramaOptions,\n type PanoramaPhoto,\n Panoramas,\n type PhotoPanoramaOptions,\n} from './layers/panoramas.ts';\nexport { type PhotoOptions, photoModel } from './layers/photos.ts';\nexport type { ClassStyle, PointCloudOptions, PointCloudStyle, PointColor } from './layers/pointclouds.ts';\nexport { CLASSIFICATIONS, ELEVATION_RAMP, PointCloudLayer } from './layers/pointclouds.ts';\nexport { RasterLayer } from './layers/raster.ts';\nexport type { Overlap, SymbolOptions, SymbolStyle } from './layers/symbols.ts';\nexport { SymbolLayer } from './layers/symbols.ts';\nexport { Tiles3dLayer } from './layers/tiles3d.ts';\nexport { type TreeKind, type TreeOptions, treeKind } from './layers/trees.ts';\nexport { type VectorFeature, VectorLayer, type VectorSublayer } from './layers/vectors.ts';\nexport type { HoverPicking } from './map/hover-pick.ts';\nexport { GeoMap, type LayerSpec } from './map.ts';\nexport {\n PANORAMAX_API,\n type PanoramaxPicture,\n type PanoramaxSequenceOptions,\n panoramaxPanoramas,\n panoramaxPhoto,\n panoramaxPicture,\n panoramaxSequence,\n panoramaxStyle,\n} from './panoramax.ts';\nexport type {\n AnimationOptions,\n AtmosphereOptions,\n Basemap,\n BasemapName,\n BodyOptions,\n CelestialBody,\n ClipBox,\n ClipCylinder,\n ClipPolygon,\n ClipVolume,\n Drape,\n FeatureColor,\n FeatureHit,\n FirstPersonView,\n FlyToOptions,\n GeoMapOptions,\n LoadingMode,\n LonLat,\n MapEvents,\n MapGpuTimings,\n MapHoverFeatureEvent,\n MapMemory,\n MapPointerEvent,\n NetworkOptions,\n NetworkStyle,\n PickOptions,\n Placement,\n Point,\n Projection,\n Quality,\n RasterGrid,\n RasterOptions,\n RasterStyle,\n SunOptions,\n SwipeOptions,\n TerrainEncoding,\n TerrainModel,\n TerrainOptions,\n Tiles3dOptions,\n Tiles3dStyle,\n ViewshedOptions,\n} from './types.ts';\nexport {\n AttributionControl,\n type AttributionOptions,\n type Control,\n type ControlPosition,\n NavigationControl,\n type NavigationOptions,\n} from './ui/controls.ts';\nexport { MinimapControl, type MinimapOptions } from './ui/minimap.ts';\nexport {\n type Anchor,\n Marker,\n type MarkerEvent,\n type MarkerEvents,\n type MarkerOptions,\n Panel,\n type PanelOptions,\n Popup,\n type PopupEvents,\n type PopupOptions,\n} from './ui/overlay.ts';\nexport { lengthLabel, roundLength, ScaleControl, type ScaleOptions } from './ui/scale.ts';\nexport { SearchControl, type SearchOptions } from './ui/search.ts';\nexport { setRequestHeaders } from './util/requests.ts';\nexport type { RoofMode } from './util/roofs.ts';\nexport { sunPosition } from './util/sun.ts';\nexport type {\n Camera,\n NetworkStats,\n PickHit,\n PickResult,\n PointCloudInfo,\n PointCloudPoint,\n PointCloudStats,\n RasterLayerStats,\n Tiles3dStats,\n} from './wasm/isard_webgpu.js';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * isard: a WebGL2 map engine for large, interactive data layers, labels and 3D Tiles.\n *\n * ```ts\n * import { GeoMap } from 'isard';\n * const map = await GeoMap.create(container, { center: [2.4246, 48.8457], zoom: 13 });\n * const network = map.addNetwork({ nodes, edges, max: 1800, contour: 300 });\n * map.on('hover', ({ lonLat }) => lonLat && network.setValues(travelTimes(lonLat)));\n * map.addTiles3d({ url: 'https://…/tileset.json' });\n * map.addControl(new NavigationControl());\n * new Marker().setLonLat([2.4246, 48.8457]).setPopup(new Popup({ offset: [0, -34] }).setText('Here')).addTo(map);\n * ```\n */\nexport { OPENFREEMAP_ATTRIBUTION, TERRAIN_TILES_ATTRIBUTION } from './basemap/basemaps.ts';\nexport type { TileGrid, TileMatrix } from './basemap/grid.ts';\nexport { coniferModel, type TreeColors, treeModel } from './basemap/models/trees.ts';\nexport {\n PMTiles,\n type PmtilesEntry,\n type PmtilesHeader,\n type PmtilesTileType,\n tileIdToZxy,\n zxyToTileId,\n} from './basemap/pmtiles.ts';\nexport { basicStyle, type PmtilesOptions } from './basemap/pmtileslayer.ts';\nexport type {\n CandidateIcon,\n CandidateOverlap,\n Json,\n LabelCandidate,\n RasterSource,\n Style,\n StyleLayer,\n} from './basemap/vectorstyle.ts';\nexport type { VectorExtrusionOptions } from './basemap/vectortiles/extrusions.ts';\nexport type { FurnitureOptions } from './basemap/vectortiles/furniture.ts';\nexport type { LabelsOptions } from './basemap/vectortiles/labels.ts';\nexport type { LampsOptions } from './basemap/vectortiles/lamps.ts';\nexport type { StreetWindLayer, StreetWindOptions } from './basemap/vectortiles/street-wind.ts';\nexport type { LoadedStyle } from './basemap/vectortiles/style.ts';\nexport type { TreesOptions } from './basemap/vectortiles/trees.ts';\nexport { isStreet, type VectorsOptions } from './basemap/vectortiles/vectors.ts';\nexport type { VectorRasterOptions, VectorTilesOptions } from './basemap/vectortiles.ts';\nexport { OPENFREEMAP_STYLES, PLAN_STYLES, VectorTiles } from './basemap/vectortiles.ts';\nexport { parseWmtsGrid, wmtsGrid } from './basemap/wmts.ts';\nexport { type ColladaImages, colladaImages, loadCollada, parseCollada } from './data/collada.ts';\nexport {\n type Crs,\n type CrsLike,\n getCrs,\n lambertConformalConic,\n registerCrs,\n reproject,\n transverseMercator,\n} from './data/crs.ts';\nexport { parseGpx } from './data/gpx.ts';\nexport {\n type IfcElement,\n type IfcModel,\n type IfcOptions,\n type IfcPlace,\n type IfcProperties,\n type IfcStorey,\n loadIfc,\n WEB_IFC,\n} from './data/ifc.ts';\nexport { parseKml } from './data/kml.ts';\nexport { parseTopoJson, type TopoGeometry, type Topology } from './data/topojson.ts';\nexport type { DecoderUrls } from './decoding/decoders.ts';\nexport {\n BATIMENTS_3D_URL,\n type BdTopoBuilding,\n batiments3d,\n bdtopoBuildings,\n bdtopoRoofs,\n type GeocodedPlace,\n geoplateformeGeocode,\n geoplateformeWfs,\n geoplateformeWms,\n geoplateformeWmts,\n IGN_ATTRIBUTION,\n IGN_PRESETS,\n type IgnPreset,\n lambert93Grid,\n} from './geoplateforme.ts';\nexport { type Grid, windAt, windFields } from './grids.ts';\nexport type { CustomFrame, CustomLayerInterface } from './layers/custom.ts';\nexport { CustomLayer } from './layers/custom.ts';\nexport type { ExtrusionOptions, ExtrusionStyle, PolygonArrays, TileKey } from './layers/extrusions.ts';\nexport { ExtrusionLayer } from './layers/extrusions.ts';\nexport type {\n CircleOptions,\n CircleStyle,\n Feature,\n FeatureCollection,\n FeatureData,\n FeatureLayerStyle,\n FillOptions,\n FillStyle,\n Geometry,\n LineOptions,\n LineStyle,\n Position,\n Properties,\n Ramp,\n Size,\n Stops,\n Styled,\n} from './layers/features.ts';\nexport { CircleLayer, FeatureLayer, FillLayer, interpolate, LineLayer, parseColor } from './layers/features.ts';\nexport type { FlowField, FlowOptions, FlowStyle, TimedField } from './layers/flow.ts';\nexport { FlowLayer, flowFieldFromImage, flowFieldsFromImage } from './layers/flow.ts';\nexport type { TextPart } from './layers/glyphs.ts';\nexport { DEFAULT_FONT, GLYPHS_URL } from './layers/glyphs.ts';\nexport type { GroundOptions, GroundTexture, GroundUniform } from './layers/ground.ts';\nexport { GroundLayer } from './layers/ground.ts';\nexport type { ImageOptions, Pixels, SpriteIcon } from './layers/icons.ts';\nexport { Layer } from './layers/layers.ts';\nexport type { ModelInstance, ModelOptions } from './layers/models.ts';\nexport { ModelLayer } from './layers/models.ts';\nexport { NetworkLayer } from './layers/network.ts';\nexport {\n type CalibratedPanoramaOptions,\n type Panorama,\n type PanoramaOptions,\n type PanoramaPhoto,\n Panoramas,\n type PhotoPanoramaOptions,\n} from './layers/panoramas.ts';\nexport { type PhotoOptions, photoModel } from './layers/photos.ts';\nexport type { ClassStyle, PointCloudOptions, PointCloudStyle, PointColor } from './layers/pointclouds.ts';\nexport { CLASSIFICATIONS, ELEVATION_RAMP, PointCloudLayer } from './layers/pointclouds.ts';\nexport { RasterLayer } from './layers/raster.ts';\nexport type { Overlap, SymbolOptions, SymbolStyle } from './layers/symbols.ts';\nexport { SymbolLayer } from './layers/symbols.ts';\nexport { Tiles3dLayer } from './layers/tiles3d.ts';\nexport { type TreeKind, type TreeOptions, treeKind } from './layers/trees.ts';\nexport { type VectorFeature, VectorLayer, type VectorSublayer } from './layers/vectors.ts';\nexport type { HoverPicking } from './map/hover-pick.ts';\nexport { GeoMap, type LayerSpec } from './map.ts';\nexport {\n PANORAMAX_API,\n type PanoramaxPicture,\n type PanoramaxSequenceOptions,\n panoramaxPanoramas,\n panoramaxPhoto,\n panoramaxPicture,\n panoramaxSequence,\n panoramaxStyle,\n} from './panoramax.ts';\nexport type {\n AnimationOptions,\n AtmosphereOptions,\n Basemap,\n BasemapName,\n BodyOptions,\n CelestialBody,\n ClipBox,\n ClipCylinder,\n ClipPolygon,\n ClipVolume,\n Drape,\n FeatureColor,\n FeatureHit,\n FirstPersonView,\n FitBoundsOptions,\n FitBox,\n FlyToOptions,\n GeoMapOptions,\n LoadingMode,\n LonLat,\n MapEvents,\n MapGpuTimings,\n MapHoverFeatureEvent,\n MapMemory,\n MapPointerEvent,\n NetworkOptions,\n NetworkStyle,\n PickOptions,\n Placement,\n Point,\n Projection,\n Quality,\n RasterGrid,\n RasterOptions,\n RasterStyle,\n SunOptions,\n SwipeOptions,\n TerrainEncoding,\n TerrainModel,\n TerrainOptions,\n Tiles3dOptions,\n Tiles3dStyle,\n ViewshedOptions,\n} from './types.ts';\nexport {\n AttributionControl,\n type AttributionOptions,\n type Control,\n type ControlPosition,\n NavigationControl,\n type NavigationOptions,\n} from './ui/controls.ts';\nexport { MinimapControl, type MinimapOptions } from './ui/minimap.ts';\nexport {\n type Anchor,\n Marker,\n type MarkerEvent,\n type MarkerEvents,\n type MarkerOptions,\n Panel,\n type PanelOptions,\n Popup,\n type PopupEvents,\n type PopupOptions,\n} from './ui/overlay.ts';\nexport { lengthLabel, roundLength, ScaleControl, type ScaleOptions } from './ui/scale.ts';\nexport { SearchControl, type SearchOptions } from './ui/search.ts';\nexport { setRequestHeaders } from './util/requests.ts';\nexport type { RoofMode } from './util/roofs.ts';\nexport { sunPosition } from './util/sun.ts';\nexport type {\n Camera,\n NetworkStats,\n PickHit,\n PickResult,\n PointCloudInfo,\n PointCloudPoint,\n PointCloudStats,\n RasterLayerStats,\n Tiles3dStats,\n} from './wasm/isard_webgpu.js';\n"]}
Binary file
Binary file
Binary file
@@ -1,6 +1,7 @@
1
1
  /** A 3D Tiles tileset as a layer: its style, its features picked */
2
- import type { Drape, Tiles3dStyle } from '../types.ts';
2
+ import type { Drape, FitBox, Tiles3dStyle } from '../types.ts';
3
3
  import type { Tiles3dStats } from '../wasm/isard_webgpu.js';
4
+ import type { Feature } from './features.ts';
4
5
  import { Layer } from './layers.ts';
5
6
  /** Textured meshes in 3D Tiles */
6
7
  export declare class Tiles3dLayer extends Layer {
@@ -9,6 +10,17 @@ export declare class Tiles3dLayer extends Layer {
9
10
  private style;
10
11
  private facades;
11
12
  private drape;
13
+ /** The box loaded to its finest tiles (`refine`): west, south, east, north, bottom, top */
14
+ private focus;
15
+ /**
16
+ * Loads the tiles meeting a box to their finest level, before the others and whether the box is in view or not (a
17
+ * feature framed to be found in them: its finest geometry, its ID); resolved once they are all there. The box stays
18
+ * so until another one, or `refine(undefined)`. `box`: as `fitBounds` takes it, altitudes in metres above the
19
+ * ellipsoid; `timeout` (ms, default 30 s): rejected past it
20
+ */
21
+ refine(box: FitBox | FitBox['bounds'] | undefined, { timeout }?: {
22
+ timeout?: number | undefined;
23
+ }): Promise<void>;
12
24
  /** Whether the walls of its untextured meshes (buildings without their photos, as the IGN's Bâtiments 3D) show
13
25
  * windows, as the vector tiles' buildings do: glass by day, some lit at night. Their faces near vertical are walls,
14
26
  * bays of 3 metres along them and floors of 3.2 metres from the ellipsoid (a mesh tells neither foot nor top) */
@@ -16,6 +28,12 @@ export declare class Tiles3dLayer extends Layer {
16
28
  /** A raster layer draped on its meshes, its roofs by default (see `Drape`); undefined: none */
17
29
  setDrape(drape: Drape | undefined): void;
18
30
  setStyle(style: Partial<Tiles3dStyle>): void;
31
+ /**
32
+ * The first feature of the tiles drawn whose properties `matches` takes (its ID, its attributes), the finest tiles
33
+ * first: a feature framed and loaded (`fitBounds`, `refine`) found in them, to show or color (`featureColor`);
34
+ * undefined if none drawn has it
35
+ */
36
+ find(matches: (properties: Record<string, unknown>) => boolean): Feature<Record<string, unknown>> | undefined;
19
37
  /** Loading counters; undefined once the layer is removed, and while the map waits for a new engine (the GPU lost) */
20
38
  stats(): Tiles3dStats | undefined;
21
39
  private applyStyle;
@@ -43,6 +43,35 @@ export class Tiles3dLayer extends Layer {
43
43
  this.facades = facades;
44
44
  this.drape = drape;
45
45
  }
46
+ /** The box loaded to its finest tiles (`refine`): west, south, east, north, bottom, top */
47
+ focus = [];
48
+ /**
49
+ * Loads the tiles meeting a box to their finest level, before the others and whether the box is in view or not (a
50
+ * feature framed to be found in them: its finest geometry, its ID); resolved once they are all there. The box stays
51
+ * so until another one, or `refine(undefined)`. `box`: as `fitBounds` takes it, altitudes in metres above the
52
+ * ellipsoid; `timeout` (ms, default 30 s): rejected past it
53
+ */
54
+ async refine(box, { timeout = 30_000 } = {}) {
55
+ const { bounds, bottom = -1e4, top = 1e4 } = !box || Array.isArray(box) ? { bounds: box } : box;
56
+ this.focus = bounds ? [...bounds, Math.min(bottom, top), Math.max(bottom, top)] : [];
57
+ if (this.id >= 0)
58
+ this.map.engine.setTiles3dFocus(this.id, Float64Array.from(this.focus));
59
+ this.map.requestRender();
60
+ if (!bounds)
61
+ return;
62
+ const focus = this.focus;
63
+ const until = performance.now() + timeout;
64
+ // Its tiles counted by the frames drawn with the box (undefined before), the box still this one
65
+ while (this.focus === focus) {
66
+ const pending = this.id < 0 ? undefined : this.map.engine.tiles3dFocusPending(this.id);
67
+ if (pending === 0)
68
+ return;
69
+ if (performance.now() >= until)
70
+ throw new Error("isard: the box's tiles were not loaded in time");
71
+ this.map.requestRender();
72
+ await new Promise((resolve) => setTimeout(resolve, 100));
73
+ }
74
+ }
46
75
  /** Whether the walls of its untextured meshes (buildings without their photos, as the IGN's Bâtiments 3D) show
47
76
  * windows, as the vector tiles' buildings do: glass by day, some lit at night. Their faces near vertical are walls,
48
77
  * bays of 3 metres along them and floors of 3.2 metres from the ellipsoid (a mesh tells neither foot nor top) */
@@ -85,6 +114,20 @@ export class Tiles3dLayer extends Layer {
85
114
  const [properties, feature] = hit;
86
115
  return { type: 'Feature', id: feature, properties: properties ?? {}, geometry: null };
87
116
  }
117
+ /**
118
+ * The first feature of the tiles drawn whose properties `matches` takes (its ID, its attributes), the finest tiles
119
+ * first: a feature framed and loaded (`fitBounds`, `refine`) found in them, to show or color (`featureColor`);
120
+ * undefined if none drawn has it
121
+ */
122
+ find(matches) {
123
+ if (this.id < 0)
124
+ return undefined;
125
+ const hit = this.map.engine.tiles3dFind(this.id, matches);
126
+ if (!hit)
127
+ return undefined;
128
+ const [properties, feature] = hit;
129
+ return { type: 'Feature', id: feature, properties, geometry: null };
130
+ }
88
131
  /** Loading counters; undefined once the layer is removed, and while the map waits for a new engine (the GPU lost) */
89
132
  stats() {
90
133
  return this.id < 0 ? undefined : this.map.engine.tiles3dStats(this.id);
@@ -95,6 +138,7 @@ export class Tiles3dLayer extends Layer {
95
138
  this.applyStyle(engine, id);
96
139
  this.applyFeatureColor(engine, id);
97
140
  engine.setTiles3dFacades(id, this.facades);
141
+ engine.setTiles3dFocus(id, Float64Array.from(this.focus));
98
142
  // A raster created before (on a new engine, the map relinks once every layer is there)
99
143
  sendDrape(engine, id, this.drape);
100
144
  return id;
@@ -1 +1 @@
1
- {"version":3,"file":"tiles3d.js","sourceRoot":"","sources":["../../src/layers/tiles3d.ts"],"names":[],"mappings":"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","sourcesContent":["/** A 3D Tiles tileset as a layer: its style, its features picked */\n\nimport { rgb } from '../settings.ts';\nimport type { Drape, FeatureColor, LayerHost, Tiles3dStyle } from '../types.ts';\nimport { parseColor } from '../util/color.ts';\nimport type { Engine, Tiles3dStats } from '../wasm/isard_webgpu.js';\nimport type { Feature } from './features.ts';\nimport { Layer } from './layers.ts';\nimport { sendDrape } from './raster.ts';\n\n/**\n * The engine's colors of a tile's features from a `featureColor`: RGBA bytes for each (alpha 0: the layer's color),\n * the CSS colors parsed once each\n */\nconst featureColors = (color: FeatureColor) => {\n const parsed = new Map<string, number[]>();\n const bytesOf = (css: string) => {\n const known = parsed.get(css);\n\n if (known) return known;\n\n const [r, g, b] = parseColor(css);\n const made = [r, g, b].map((c) => Math.round(c * 255)).concat(255);\n\n parsed.set(css, made);\n\n return made;\n };\n\n return (properties: Record<string, unknown>[] | undefined, count: number): Uint8Array => {\n const rgba = new Uint8Array(count * 4);\n\n Array.from({ length: count }, (_, i) => color(properties?.[i] ?? {}, i)).forEach((css, i) => {\n if (css) rgba.set(bytesOf(css), i * 4);\n });\n\n return rgba;\n };\n};\n\n/** Textured meshes in 3D Tiles */\nexport class Tiles3dLayer extends Layer {\n /** @internal */\n constructor(\n map: LayerHost,\n private readonly url: string,\n private readonly geoid: boolean,\n private style: Tiles3dStyle,\n private facades = false,\n private drape: Drape | undefined = undefined,\n ) {\n super(map);\n }\n\n /** Whether the walls of its untextured meshes (buildings without their photos, as the IGN's Bâtiments 3D) show\n * windows, as the vector tiles' buildings do: glass by day, some lit at night. Their faces near vertical are walls,\n * bays of 3 metres along them and floors of 3.2 metres from the ellipsoid (a mesh tells neither foot nor top) */\n setFacades(facades: boolean): void {\n this.facades = facades;\n // Kept for its creation in a new engine (the GPU lost and given back), none while it has no id\n if (this.id < 0) return;\n this.map.engine.setTiles3dFacades(this.id, facades);\n this.map.requestRender();\n }\n\n /** A raster layer draped on its meshes, its roofs by default (see `Drape`); undefined: none */\n setDrape(drape: Drape | undefined): void {\n this.drape = drape;\n // Kept for its creation in a new engine (the GPU lost and given back), none while it has no id\n if (this.id < 0) return;\n sendDrape(this.map.engine, this.id, drape);\n this.map.requestRender();\n }\n\n setStyle(style: Partial<Tiles3dStyle>): void {\n this.style = { ...this.style, ...style };\n // Kept for its creation in a new engine (the GPU lost and given back), none while it has no id\n if (this.id < 0) return;\n this.applyStyle(this.map.engine, this.id);\n // The features colored again only when their function changed: every tile asks for its colors then\n if ('featureColor' in style) this.applyFeatureColor(this.map.engine, this.id);\n this.map.requestRender();\n }\n\n /** @internal The feature picked (`pickFeature`, 'hoverfeature'): the properties of its feature in the tile\n * (`EXT_structural_metadata`, a b3dm batch table), none for a mesh without features; its index in the tile as id */\n featureAt(index: number): Feature<Record<string, unknown>> | undefined {\n if (this.id < 0) return undefined;\n\n const hit = this.map.engine.tiles3dFeature(this.id, index);\n\n if (!hit) return undefined;\n\n const [properties, feature] = hit;\n\n return { type: 'Feature', id: feature, properties: properties ?? {}, geometry: null };\n }\n\n /** Loading counters; undefined once the layer is removed, and while the map waits for a new engine (the GPU lost) */\n stats(): Tiles3dStats | undefined {\n return this.id < 0 ? undefined : this.map.engine.tiles3dStats(this.id);\n }\n\n /** @internal */\n protected create(engine: Engine): number {\n const id = engine.addTiles3d(this.url, this.style.maxScreenSpaceError * this.map.quality.errorScale, this.geoid);\n\n this.applyStyle(engine, id);\n this.applyFeatureColor(engine, id);\n engine.setTiles3dFacades(id, this.facades);\n // A raster created before (on a new engine, the map relinks once every layer is there)\n sendDrape(engine, id, this.drape);\n\n return id;\n }\n\n /** @internal On a new engine, the raster may come after the tiles: draped once both are there */\n override relink(engine: Engine): void {\n sendDrape(engine, this.id, this.drape);\n }\n\n /** @internal */\n override qualityChanged(): void {\n if (this.id >= 0) this.applyStyle(this.map.engine, this.id);\n }\n\n private applyStyle(engine: Engine, id: number): void {\n const { maxScreenSpaceError, opacity, pointSize, color } = this.style;\n // Coarser far away at a lower quality\n const error = maxScreenSpaceError * this.map.quality.errorScale;\n\n engine.setTiles3dStyle(id, error, opacity, pointSize, new Float32Array(color ? rgb(color) : []));\n }\n\n private applyFeatureColor(engine: Engine, id: number): void {\n const { featureColor } = this.style;\n\n engine.setTiles3dFeatureColors(id, featureColor ? featureColors(featureColor) : null);\n }\n}\n"]}
1
+ {"version":3,"file":"tiles3d.js","sourceRoot":"","sources":["../../src/layers/tiles3d.ts"],"names":[],"mappings":"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","sourcesContent":["/** A 3D Tiles tileset as a layer: its style, its features picked */\n\nimport { rgb } from '../settings.ts';\nimport type { Drape, FeatureColor, FitBox, LayerHost, Tiles3dStyle } from '../types.ts';\nimport { parseColor } from '../util/color.ts';\nimport type { Engine, Tiles3dStats } from '../wasm/isard_webgpu.js';\nimport type { Feature } from './features.ts';\nimport { Layer } from './layers.ts';\nimport { sendDrape } from './raster.ts';\n\n/**\n * The engine's colors of a tile's features from a `featureColor`: RGBA bytes for each (alpha 0: the layer's color),\n * the CSS colors parsed once each\n */\nconst featureColors = (color: FeatureColor) => {\n const parsed = new Map<string, number[]>();\n const bytesOf = (css: string) => {\n const known = parsed.get(css);\n\n if (known) return known;\n\n const [r, g, b] = parseColor(css);\n const made = [r, g, b].map((c) => Math.round(c * 255)).concat(255);\n\n parsed.set(css, made);\n\n return made;\n };\n\n return (properties: Record<string, unknown>[] | undefined, count: number): Uint8Array => {\n const rgba = new Uint8Array(count * 4);\n\n Array.from({ length: count }, (_, i) => color(properties?.[i] ?? {}, i)).forEach((css, i) => {\n if (css) rgba.set(bytesOf(css), i * 4);\n });\n\n return rgba;\n };\n};\n\n/** Textured meshes in 3D Tiles */\nexport class Tiles3dLayer extends Layer {\n /** @internal */\n constructor(\n map: LayerHost,\n private readonly url: string,\n private readonly geoid: boolean,\n private style: Tiles3dStyle,\n private facades = false,\n private drape: Drape | undefined = undefined,\n ) {\n super(map);\n }\n\n /** The box loaded to its finest tiles (`refine`): west, south, east, north, bottom, top */\n private focus: number[] = [];\n\n /**\n * Loads the tiles meeting a box to their finest level, before the others and whether the box is in view or not (a\n * feature framed to be found in them: its finest geometry, its ID); resolved once they are all there. The box stays\n * so until another one, or `refine(undefined)`. `box`: as `fitBounds` takes it, altitudes in metres above the\n * ellipsoid; `timeout` (ms, default 30 s): rejected past it\n */\n async refine(box: FitBox | FitBox['bounds'] | undefined, { timeout = 30_000 } = {}): Promise<void> {\n const { bounds, bottom = -1e4, top = 1e4 } = !box || Array.isArray(box) ? { bounds: box } : box;\n\n this.focus = bounds ? [...bounds, Math.min(bottom, top), Math.max(bottom, top)] : [];\n if (this.id >= 0) this.map.engine.setTiles3dFocus(this.id, Float64Array.from(this.focus));\n this.map.requestRender();\n if (!bounds) return;\n\n const focus = this.focus;\n const until = performance.now() + timeout;\n\n // Its tiles counted by the frames drawn with the box (undefined before), the box still this one\n while (this.focus === focus) {\n const pending = this.id < 0 ? undefined : this.map.engine.tiles3dFocusPending(this.id);\n\n if (pending === 0) return;\n if (performance.now() >= until) throw new Error(\"isard: the box's tiles were not loaded in time\");\n this.map.requestRender();\n await new Promise((resolve) => setTimeout(resolve, 100));\n }\n }\n\n /** Whether the walls of its untextured meshes (buildings without their photos, as the IGN's Bâtiments 3D) show\n * windows, as the vector tiles' buildings do: glass by day, some lit at night. Their faces near vertical are walls,\n * bays of 3 metres along them and floors of 3.2 metres from the ellipsoid (a mesh tells neither foot nor top) */\n setFacades(facades: boolean): void {\n this.facades = facades;\n // Kept for its creation in a new engine (the GPU lost and given back), none while it has no id\n if (this.id < 0) return;\n this.map.engine.setTiles3dFacades(this.id, facades);\n this.map.requestRender();\n }\n\n /** A raster layer draped on its meshes, its roofs by default (see `Drape`); undefined: none */\n setDrape(drape: Drape | undefined): void {\n this.drape = drape;\n // Kept for its creation in a new engine (the GPU lost and given back), none while it has no id\n if (this.id < 0) return;\n sendDrape(this.map.engine, this.id, drape);\n this.map.requestRender();\n }\n\n setStyle(style: Partial<Tiles3dStyle>): void {\n this.style = { ...this.style, ...style };\n // Kept for its creation in a new engine (the GPU lost and given back), none while it has no id\n if (this.id < 0) return;\n this.applyStyle(this.map.engine, this.id);\n // The features colored again only when their function changed: every tile asks for its colors then\n if ('featureColor' in style) this.applyFeatureColor(this.map.engine, this.id);\n this.map.requestRender();\n }\n\n /** @internal The feature picked (`pickFeature`, 'hoverfeature'): the properties of its feature in the tile\n * (`EXT_structural_metadata`, a b3dm batch table), none for a mesh without features; its index in the tile as id */\n featureAt(index: number): Feature<Record<string, unknown>> | undefined {\n if (this.id < 0) return undefined;\n\n const hit = this.map.engine.tiles3dFeature(this.id, index);\n\n if (!hit) return undefined;\n\n const [properties, feature] = hit;\n\n return { type: 'Feature', id: feature, properties: properties ?? {}, geometry: null };\n }\n\n /**\n * The first feature of the tiles drawn whose properties `matches` takes (its ID, its attributes), the finest tiles\n * first: a feature framed and loaded (`fitBounds`, `refine`) found in them, to show or color (`featureColor`);\n * undefined if none drawn has it\n */\n find(matches: (properties: Record<string, unknown>) => boolean): Feature<Record<string, unknown>> | undefined {\n if (this.id < 0) return undefined;\n\n const hit = this.map.engine.tiles3dFind(this.id, matches);\n\n if (!hit) return undefined;\n\n const [properties, feature] = hit;\n\n return { type: 'Feature', id: feature, properties, geometry: null };\n }\n\n /** Loading counters; undefined once the layer is removed, and while the map waits for a new engine (the GPU lost) */\n stats(): Tiles3dStats | undefined {\n return this.id < 0 ? undefined : this.map.engine.tiles3dStats(this.id);\n }\n\n /** @internal */\n protected create(engine: Engine): number {\n const id = engine.addTiles3d(this.url, this.style.maxScreenSpaceError * this.map.quality.errorScale, this.geoid);\n\n this.applyStyle(engine, id);\n this.applyFeatureColor(engine, id);\n engine.setTiles3dFacades(id, this.facades);\n engine.setTiles3dFocus(id, Float64Array.from(this.focus));\n // A raster created before (on a new engine, the map relinks once every layer is there)\n sendDrape(engine, id, this.drape);\n\n return id;\n }\n\n /** @internal On a new engine, the raster may come after the tiles: draped once both are there */\n override relink(engine: Engine): void {\n sendDrape(engine, this.id, this.drape);\n }\n\n /** @internal */\n override qualityChanged(): void {\n if (this.id >= 0) this.applyStyle(this.map.engine, this.id);\n }\n\n private applyStyle(engine: Engine, id: number): void {\n const { maxScreenSpaceError, opacity, pointSize, color } = this.style;\n // Coarser far away at a lower quality\n const error = maxScreenSpaceError * this.map.quality.errorScale;\n\n engine.setTiles3dStyle(id, error, opacity, pointSize, new Float32Array(color ? rgb(color) : []));\n }\n\n private applyFeatureColor(engine: Engine, id: number): void {\n const { featureColor } = this.style;\n\n engine.setTiles3dFeatureColors(id, featureColor ? featureColors(featureColor) : null);\n }\n}\n"]}
@@ -2,19 +2,23 @@
2
2
  * The engine a map runs, made asynchronously (its device asked for), and the workers decoding its 3D Tiles, started
3
3
  * with the first one.
4
4
  */
5
- import { type EngineClass } from '../engine-loader.ts';
5
+ import { type EngineClass, type EngineKind } from '../engine-loader.ts';
6
6
  import type { Settings } from '../settings.ts';
7
7
  import type { GeoMapOptions } from '../types.ts';
8
8
  import type { Engine } from '../wasm/isard_webgpu.js';
9
9
  /** What making the engine and its workers needs */
10
- export type Tiles3dEngineOptions = Pick<GeoMapOptions, 'workers' | 'workerUrl' | 'decoders' | 'pointsWasmUrl' | 'tiles3dWasmUrl' | 'assetsUrl' | 'quality' | 'antialias'>;
10
+ export type Tiles3dEngineOptions = Pick<GeoMapOptions, 'workers' | 'workerUrl' | 'decoders' | 'pointsWasmUrl' | 'tiles3dWasmUrl' | 'webgl2WasmUrl' | 'assetsUrl' | 'quality' | 'antialias'>;
11
11
  /** The engine class of a map (the engine on wgpu, through WebGPU or its WebGL2 fallback: one module, 3D Tiles
12
12
  * included) and the workers decoding its 3D Tiles, started with the first engine */
13
13
  export declare class MapEngines {
14
- private readonly engineClass;
14
+ #private;
15
+ private engineClass;
15
16
  private loader;
16
17
  private readonly options;
17
- constructor(engineClass: EngineClass, options: Tiles3dEngineOptions);
18
+ constructor(engineClass: EngineClass, renderer: EngineKind, options: Tiles3dEngineOptions);
19
+ get renderer(): EngineKind;
20
+ /** The next engines through WebGL2 (a device WebGPU keeps losing or no longer gives): its module loaded */
21
+ fallBack(): Promise<void>;
18
22
  /** A new engine drawing on `canvas` (its device asked for), with the settings and the decoding workers */
19
23
  create(canvas: HTMLCanvasElement, redraw: () => void, settings: Settings): Promise<Engine>;
20
24
  /** Height of the geoid above the ellipsoid at a position, in metres */
@@ -2,18 +2,32 @@
2
2
  * The engine a map runs, made asynchronously (its device asked for), and the workers decoding its 3D Tiles, started
3
3
  * with the first one.
4
4
  */
5
- import { startTileLoader } from "../engine-loader.js";
5
+ import { engineUrl, loadEngine, startTileLoader } from "../engine-loader.js";
6
6
  import { applySettings } from "./engine-settings.js";
7
7
  /** The engine class of a map (the engine on wgpu, through WebGPU or its WebGL2 fallback: one module, 3D Tiles
8
8
  * included) and the workers decoding its 3D Tiles, started with the first engine */
9
9
  export class MapEngines {
10
10
  engineClass;
11
+ /** What the engine draws through: WebGPU, or its WebGL2 fallback (`fallBack`) */
12
+ #renderer;
11
13
  loader;
12
14
  options;
13
- constructor(engineClass, options) {
15
+ constructor(engineClass, renderer, options) {
14
16
  this.engineClass = engineClass;
17
+ this.#renderer = renderer;
15
18
  this.options = { ...options };
16
19
  }
20
+ get renderer() {
21
+ return this.#renderer;
22
+ }
23
+ /** The next engines through WebGL2 (a device WebGPU keeps losing or no longer gives): its module loaded */
24
+ async fallBack() {
25
+ if (this.#renderer === 'webgl2')
26
+ return;
27
+ const bindings = await loadEngine('webgl2', engineUrl('webgl2', this.options));
28
+ this.engineClass = bindings.Engine;
29
+ this.#renderer = 'webgl2';
30
+ }
17
31
  /** A new engine drawing on `canvas` (its device asked for), with the settings and the decoding workers */
18
32
  async create(canvas, redraw, settings) {
19
33
  const engine = await this.engineClass.create(canvas, redraw, this.options.antialias);
@@ -1 +1 @@
1
- {"version":3,"file":"engines.js","sourceRoot":"","sources":["../../src/map/engines.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAoB,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAIxE,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAQrD;oFACoF;AACpF,MAAM,OAAO,UAAU;IACJ,WAAW,CAAc;IAClC,MAAM,CAAyB;IACtB,OAAO,CAAuB;IAE/C,YAAY,WAAwB,EAAE,OAA6B;QACjE,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;QAC/B,IAAI,CAAC,OAAO,GAAG,EAAE,GAAG,OAAO,EAAE,CAAC;IAChC,CAAC;IAED,0GAA0G;IAC1G,KAAK,CAAC,MAAM,CAAC,MAAyB,EAAE,MAAkB,EAAE,QAAkB;QAC5E,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;QAErF,aAAa,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;QAChC,IAAI,CAAC,MAAM,KAAK,eAAe,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QACtD,IAAI,IAAI,CAAC,MAAM;YAAE,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAEnD,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,uEAAuE;IACvE,WAAW,CAAC,GAAW,EAAE,GAAW;QAClC,OAAO,IAAI,CAAC,WAAW,CAAC,WAAW,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;IAChD,CAAC;IAED,yDAAyD;IACzD,YAAY,CAAC,GAAW,EAAE,GAAW,EAAE,QAAgB;QACrD,OAAO,IAAI,CAAC,WAAW,CAAC,YAAY,CAAC,GAAG,EAAE,GAAG,EAAE,QAAQ,CAAC,CAAC;IAC3D,CAAC;IAED,iCAAiC;IACjC,OAAO;QACL,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC;IACzB,CAAC;CACF","sourcesContent":["/**\n * The engine a map runs, made asynchronously (its device asked for), and the workers decoding its 3D Tiles, started\n * with the first one.\n */\n\nimport type { TileLoader } from '../decoding/loader.ts';\nimport { type EngineClass, startTileLoader } from '../engine-loader.ts';\nimport type { Settings } from '../settings.ts';\nimport type { GeoMapOptions } from '../types.ts';\nimport type { Engine } from '../wasm/isard_webgpu.js';\nimport { applySettings } from './engine-settings.ts';\n\n/** What making the engine and its workers needs */\nexport type Tiles3dEngineOptions = Pick<\n GeoMapOptions,\n 'workers' | 'workerUrl' | 'decoders' | 'pointsWasmUrl' | 'tiles3dWasmUrl' | 'assetsUrl' | 'quality' | 'antialias'\n>;\n\n/** The engine class of a map (the engine on wgpu, through WebGPU or its WebGL2 fallback: one module, 3D Tiles\n * included) and the workers decoding its 3D Tiles, started with the first engine */\nexport class MapEngines {\n private readonly engineClass: EngineClass;\n private loader: TileLoader | undefined;\n private readonly options: Tiles3dEngineOptions;\n\n constructor(engineClass: EngineClass, options: Tiles3dEngineOptions) {\n this.engineClass = engineClass;\n this.options = { ...options };\n }\n\n /** A new engine drawing on `canvas` (its device asked for), with the settings and the decoding workers */\n async create(canvas: HTMLCanvasElement, redraw: () => void, settings: Settings): Promise<Engine> {\n const engine = await this.engineClass.create(canvas, redraw, this.options.antialias);\n\n applySettings(engine, settings);\n this.loader ??= startTileLoader(this.options, engine);\n if (this.loader) engine.setTileLoader(this.loader);\n\n return engine;\n }\n\n /** Height of the geoid above the ellipsoid at a position, in metres */\n geoidHeight(lon: number, lat: number): number {\n return this.engineClass.geoidHeight(lon, lat);\n }\n\n /** The matrix of a custom layer's model at a position */\n customMatrix(lon: number, lat: number, altitude: number): Float32Array | undefined {\n return this.engineClass.customMatrix(lon, lat, altitude);\n }\n\n /** Stops the decoding workers */\n destroy(): void {\n this.loader?.destroy();\n }\n}\n"]}
1
+ {"version":3,"file":"engines.js","sourceRoot":"","sources":["../../src/map/engines.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAqC,SAAS,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAIhH,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAgBrD;oFACoF;AACpF,MAAM,OAAO,UAAU;IACb,WAAW,CAAc;IACjC,iFAAiF;IACjF,SAAS,CAAa;IACd,MAAM,CAAyB;IACtB,OAAO,CAAuB;IAE/C,YAAY,WAAwB,EAAE,QAAoB,EAAE,OAA6B;QACvF,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;QAC/B,IAAI,CAAC,SAAS,GAAG,QAAQ,CAAC;QAC1B,IAAI,CAAC,OAAO,GAAG,EAAE,GAAG,OAAO,EAAE,CAAC;IAChC,CAAC;IAED,IAAI,QAAQ;QACV,OAAO,IAAI,CAAC,SAAS,CAAC;IACxB,CAAC;IAED,2GAA2G;IAC3G,KAAK,CAAC,QAAQ;QACZ,IAAI,IAAI,CAAC,SAAS,KAAK,QAAQ;YAAE,OAAO;QAExC,MAAM,QAAQ,GAAG,MAAM,UAAU,CAAC,QAAQ,EAAE,SAAS,CAAC,QAAQ,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;QAE/E,IAAI,CAAC,WAAW,GAAG,QAAQ,CAAC,MAAM,CAAC;QACnC,IAAI,CAAC,SAAS,GAAG,QAAQ,CAAC;IAC5B,CAAC;IAED,0GAA0G;IAC1G,KAAK,CAAC,MAAM,CAAC,MAAyB,EAAE,MAAkB,EAAE,QAAkB;QAC5E,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;QAErF,aAAa,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;QAChC,IAAI,CAAC,MAAM,KAAK,eAAe,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QACtD,IAAI,IAAI,CAAC,MAAM;YAAE,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAEnD,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,uEAAuE;IACvE,WAAW,CAAC,GAAW,EAAE,GAAW;QAClC,OAAO,IAAI,CAAC,WAAW,CAAC,WAAW,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;IAChD,CAAC;IAED,yDAAyD;IACzD,YAAY,CAAC,GAAW,EAAE,GAAW,EAAE,QAAgB;QACrD,OAAO,IAAI,CAAC,WAAW,CAAC,YAAY,CAAC,GAAG,EAAE,GAAG,EAAE,QAAQ,CAAC,CAAC;IAC3D,CAAC;IAED,iCAAiC;IACjC,OAAO;QACL,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC;IACzB,CAAC;CACF","sourcesContent":["/**\n * The engine a map runs, made asynchronously (its device asked for), and the workers decoding its 3D Tiles, started\n * with the first one.\n */\n\nimport type { TileLoader } from '../decoding/loader.ts';\nimport { type EngineClass, type EngineKind, engineUrl, loadEngine, startTileLoader } from '../engine-loader.ts';\nimport type { Settings } from '../settings.ts';\nimport type { GeoMapOptions } from '../types.ts';\nimport type { Engine } from '../wasm/isard_webgpu.js';\nimport { applySettings } from './engine-settings.ts';\n\n/** What making the engine and its workers needs */\nexport type Tiles3dEngineOptions = Pick<\n GeoMapOptions,\n | 'workers'\n | 'workerUrl'\n | 'decoders'\n | 'pointsWasmUrl'\n | 'tiles3dWasmUrl'\n | 'webgl2WasmUrl'\n | 'assetsUrl'\n | 'quality'\n | 'antialias'\n>;\n\n/** The engine class of a map (the engine on wgpu, through WebGPU or its WebGL2 fallback: one module, 3D Tiles\n * included) and the workers decoding its 3D Tiles, started with the first engine */\nexport class MapEngines {\n private engineClass: EngineClass;\n /** What the engine draws through: WebGPU, or its WebGL2 fallback (`fallBack`) */\n #renderer: EngineKind;\n private loader: TileLoader | undefined;\n private readonly options: Tiles3dEngineOptions;\n\n constructor(engineClass: EngineClass, renderer: EngineKind, options: Tiles3dEngineOptions) {\n this.engineClass = engineClass;\n this.#renderer = renderer;\n this.options = { ...options };\n }\n\n get renderer(): EngineKind {\n return this.#renderer;\n }\n\n /** The next engines through WebGL2 (a device WebGPU keeps losing or no longer gives): its module loaded */\n async fallBack(): Promise<void> {\n if (this.#renderer === 'webgl2') return;\n\n const bindings = await loadEngine('webgl2', engineUrl('webgl2', this.options));\n\n this.engineClass = bindings.Engine;\n this.#renderer = 'webgl2';\n }\n\n /** A new engine drawing on `canvas` (its device asked for), with the settings and the decoding workers */\n async create(canvas: HTMLCanvasElement, redraw: () => void, settings: Settings): Promise<Engine> {\n const engine = await this.engineClass.create(canvas, redraw, this.options.antialias);\n\n applySettings(engine, settings);\n this.loader ??= startTileLoader(this.options, engine);\n if (this.loader) engine.setTileLoader(this.loader);\n\n return engine;\n }\n\n /** Height of the geoid above the ellipsoid at a position, in metres */\n geoidHeight(lon: number, lat: number): number {\n return this.engineClass.geoidHeight(lon, lat);\n }\n\n /** The matrix of a custom layer's model at a position */\n customMatrix(lon: number, lat: number, altitude: number): Float32Array | undefined {\n return this.engineClass.customMatrix(lon, lat, altitude);\n }\n\n /** Stops the decoding workers */\n destroy(): void {\n this.loader?.destroy();\n }\n}\n"]}
@@ -12,6 +12,7 @@ export class MapListeners {
12
12
  click: new Set(),
13
13
  render: new Set(),
14
14
  quality: new Set(),
15
+ gpulost: new Set(),
15
16
  idle: new Set(),
16
17
  leave: new Set(),
17
18
  clipvolume: new Set(),
@@ -1 +1 @@
1
- {"version":3,"file":"events.js","sourceRoot":"","sources":["../../src/map/events.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAG9E,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAIrC,iDAAiD;AACjD,MAAM,OAAO,YAAY;IACN,SAAS,GAAc;QACtC,SAAS,EAAE,IAAI,GAAG,EAAE;QACpB,IAAI,EAAE,IAAI,GAAG,EAAE;QACf,OAAO,EAAE,IAAI,GAAG,EAAE;QAClB,MAAM,EAAE,IAAI,GAAG,EAAE;QACjB,KAAK,EAAE,IAAI,GAAG,EAAE;QAChB,YAAY,EAAE,IAAI,GAAG,EAAE;QACvB,KAAK,EAAE,IAAI,GAAG,EAAE;QAChB,MAAM,EAAE,IAAI,GAAG,EAAE;QACjB,OAAO,EAAE,IAAI,GAAG,EAAE;QAClB,IAAI,EAAE,IAAI,GAAG,EAAE;QACf,KAAK,EAAE,IAAI,GAAG,EAAE;QAChB,UAAU,EAAE,IAAI,GAAG,EAAE;KACtB,CAAC;IAEF,qEAAqE;IACrE,EAAE,CAA4B,IAAO,EAAE,QAAuC;QAC5E,MAAM,SAAS,GAAuC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;QAE3E,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAExB,OAAO,GAAG,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IAC1C,CAAC;IAED,kGAAkG;IAClG,IAAI,CAA4B,IAAO,EAAE,KAAmB;QAC1D,MAAM,SAAS,GAAuC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;QAE3E,SAAS,CAAC,OAAO,CAAC,CAAC,QAAQ,EAAE,EAAE;YAC7B,IAAI,CAAC;gBACH,QAAQ,CAAC,KAAK,CAAC,CAAC;YAClB,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,MAAM,CAAC,KAAK,CAAC,CAAC;YAChB,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;IAED,kCAAkC;IAClC,GAAG,CAAC,IAAqB;QACvB,OAAO,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC;IACvC,CAAC;IAED,6BAA6B;IAC7B,KAAK;QACH,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,OAAO,CAAC,CAAC,SAAS,EAAE,EAAE;YAClD,SAAS,CAAC,KAAK,EAAE,CAAC;QACpB,CAAC,CAAC,CAAC;IACL,CAAC;CACF","sourcesContent":["/** The listeners of the map's events: added, called, cleared with the map. */\n\nimport type { MapEvents } from '../types.ts';\nimport { report } from './checks.ts';\n\ntype Listeners = { [K in keyof MapEvents]: Set<(event: MapEvents[K]) => void> };\n\n/** The listeners of the map's events, by type */\nexport class MapListeners {\n private readonly listeners: Listeners = {\n movestart: new Set(),\n move: new Set(),\n moveend: new Set(),\n resize: new Set(),\n hover: new Set(),\n hoverfeature: new Set(),\n click: new Set(),\n render: new Set(),\n quality: new Set(),\n idle: new Set(),\n leave: new Set(),\n clipvolume: new Set(),\n };\n\n /** Listens to an event; returns the function that stops listening */\n on<K extends keyof MapEvents>(type: K, listener: (event: MapEvents[K]) => void): () => void {\n const listeners: Set<(event: MapEvents[K]) => void> = this.listeners[type];\n\n listeners.add(listener);\n\n return () => listeners.delete(listener);\n }\n\n /** Calls the listeners; one that throws is reported and does not stop the others nor the frame */\n emit<K extends keyof MapEvents>(type: K, event: MapEvents[K]): void {\n const listeners: Set<(event: MapEvents[K]) => void> = this.listeners[type];\n\n listeners.forEach((listener) => {\n try {\n listener(event);\n } catch (error) {\n report(error);\n }\n });\n }\n\n /** Something listens to `type` */\n has(type: keyof MapEvents): boolean {\n return this.listeners[type].size > 0;\n }\n\n /** Forgets every listener */\n clear(): void {\n Object.values(this.listeners).forEach((listeners) => {\n listeners.clear();\n });\n }\n}\n"]}
1
+ {"version":3,"file":"events.js","sourceRoot":"","sources":["../../src/map/events.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAG9E,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAIrC,iDAAiD;AACjD,MAAM,OAAO,YAAY;IACN,SAAS,GAAc;QACtC,SAAS,EAAE,IAAI,GAAG,EAAE;QACpB,IAAI,EAAE,IAAI,GAAG,EAAE;QACf,OAAO,EAAE,IAAI,GAAG,EAAE;QAClB,MAAM,EAAE,IAAI,GAAG,EAAE;QACjB,KAAK,EAAE,IAAI,GAAG,EAAE;QAChB,YAAY,EAAE,IAAI,GAAG,EAAE;QACvB,KAAK,EAAE,IAAI,GAAG,EAAE;QAChB,MAAM,EAAE,IAAI,GAAG,EAAE;QACjB,OAAO,EAAE,IAAI,GAAG,EAAE;QAClB,OAAO,EAAE,IAAI,GAAG,EAAE;QAClB,IAAI,EAAE,IAAI,GAAG,EAAE;QACf,KAAK,EAAE,IAAI,GAAG,EAAE;QAChB,UAAU,EAAE,IAAI,GAAG,EAAE;KACtB,CAAC;IAEF,qEAAqE;IACrE,EAAE,CAA4B,IAAO,EAAE,QAAuC;QAC5E,MAAM,SAAS,GAAuC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;QAE3E,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAExB,OAAO,GAAG,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IAC1C,CAAC;IAED,kGAAkG;IAClG,IAAI,CAA4B,IAAO,EAAE,KAAmB;QAC1D,MAAM,SAAS,GAAuC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;QAE3E,SAAS,CAAC,OAAO,CAAC,CAAC,QAAQ,EAAE,EAAE;YAC7B,IAAI,CAAC;gBACH,QAAQ,CAAC,KAAK,CAAC,CAAC;YAClB,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,MAAM,CAAC,KAAK,CAAC,CAAC;YAChB,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;IAED,kCAAkC;IAClC,GAAG,CAAC,IAAqB;QACvB,OAAO,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC;IACvC,CAAC;IAED,6BAA6B;IAC7B,KAAK;QACH,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,OAAO,CAAC,CAAC,SAAS,EAAE,EAAE;YAClD,SAAS,CAAC,KAAK,EAAE,CAAC;QACpB,CAAC,CAAC,CAAC;IACL,CAAC;CACF","sourcesContent":["/** The listeners of the map's events: added, called, cleared with the map. */\n\nimport type { MapEvents } from '../types.ts';\nimport { report } from './checks.ts';\n\ntype Listeners = { [K in keyof MapEvents]: Set<(event: MapEvents[K]) => void> };\n\n/** The listeners of the map's events, by type */\nexport class MapListeners {\n private readonly listeners: Listeners = {\n movestart: new Set(),\n move: new Set(),\n moveend: new Set(),\n resize: new Set(),\n hover: new Set(),\n hoverfeature: new Set(),\n click: new Set(),\n render: new Set(),\n quality: new Set(),\n gpulost: new Set(),\n idle: new Set(),\n leave: new Set(),\n clipvolume: new Set(),\n };\n\n /** Listens to an event; returns the function that stops listening */\n on<K extends keyof MapEvents>(type: K, listener: (event: MapEvents[K]) => void): () => void {\n const listeners: Set<(event: MapEvents[K]) => void> = this.listeners[type];\n\n listeners.add(listener);\n\n return () => listeners.delete(listener);\n }\n\n /** Calls the listeners; one that throws is reported and does not stop the others nor the frame */\n emit<K extends keyof MapEvents>(type: K, event: MapEvents[K]): void {\n const listeners: Set<(event: MapEvents[K]) => void> = this.listeners[type];\n\n listeners.forEach((listener) => {\n try {\n listener(event);\n } catch (error) {\n report(error);\n }\n });\n }\n\n /** Something listens to `type` */\n has(type: keyof MapEvents): boolean {\n return this.listeners[type].size > 0;\n }\n\n /** Forgets every listener */\n clear(): void {\n Object.values(this.listeners).forEach((listeners) => {\n listeners.clear();\n });\n }\n}\n"]}
package/dist/map.d.ts CHANGED
@@ -26,7 +26,7 @@ import { Tiles3dLayer } from './layers/tiles3d.ts';
26
26
  import { type TreeOptions } from './layers/trees.ts';
27
27
  import { type HoverPicking } from './map/hover-pick.ts';
28
28
  import { type LayerSpec } from './map/layer-spec.ts';
29
- import type { AnimationOptions, AtmosphereOptions, Basemap, ClipVolume, FeatureHit, FirstPersonView, FlyToOptions, GeoMapOptions, LoadingMode, LonLat, MapEvents, MapGpuTimings, MapMemory, NetworkOptions, PickOptions, Point, Projection, Quality, RasterOptions, SunOptions, SwipeOptions, TerrainModel, TerrainOptions, Tiles3dOptions, ViewshedOptions } from './types.ts';
29
+ import type { AnimationOptions, AtmosphereOptions, Basemap, ClipVolume, FeatureHit, FirstPersonView, FitBoundsOptions, FitBox, FlyToOptions, GeoMapOptions, LoadingMode, LonLat, MapEvents, MapGpuTimings, MapMemory, NetworkOptions, PickOptions, Point, Projection, Quality, RasterOptions, SunOptions, SwipeOptions, TerrainModel, TerrainOptions, Tiles3dOptions, ViewshedOptions } from './types.ts';
30
30
  import { type Control, type ControlPosition } from './ui/controls.ts';
31
31
  import type { Camera, PickHit, RasterLayerStats } from './wasm/isard_webgpu.js';
32
32
  export type { LayerSpec } from './map/layer-spec.ts';
@@ -85,15 +85,25 @@ export declare class GeoMap {
85
85
  private get layers();
86
86
  /** Layers new layers go under */
87
87
  private get onTop();
88
+ /** What the map draws through: `'webgpu'`, or `'webgl2'` (the fallback of a browser without WebGPU, or of a device
89
+ * WebGPU kept losing) */
90
+ get renderer(): 'webgpu' | 'webgl2';
88
91
  /** The GPU is lost: nothing drawn, the engine dropped (`dropLostEngine`) */
89
92
  private gpuLost;
90
93
  /**
91
94
  * Follows the loss of `engine`'s WebGPU device (a phone taking its memory back, a driver reset, the GPU process
92
95
  * gone): while it is the map's, the map drops it and makes a new one at once, WebGPU having no event for the GPU
93
- * given back. The device of an engine the map freed itself (replaced, the map destroyed) is lost too: not the map's
94
- * any more, left be
96
+ * given back, lighter so as not to lose it again (`lighten`). The device of an engine the map freed itself
97
+ * (replaced, the map destroyed) is lost too: not the map's any more, left be
95
98
  */
96
99
  private watchDevice;
100
+ /**
101
+ * After a device lost, the next engine lighter, the same scene then holding less: at the first loss the low quality
102
+ * (one pixel a CSS pixel, no sun's shadows, one worker, fewer tiles far away) and half the GPU memory budget; at the
103
+ * next one, the WebGL2 fallback (a phone whose WebGPU keeps losing its device: Safari's GPU process taking its
104
+ * memory back) and half the budget again. What it did: `'lighter'` or `'webgl2'`
105
+ */
106
+ private lighten;
97
107
  /**
98
108
  * The context is lost: its engine freed at once, the map's calls going to one that does nothing until `restore`
99
109
  * builds the next. The old engine's GPU objects went with the context: deleted now, it is silent (on the context
@@ -148,6 +158,16 @@ export declare class GeoMap {
148
158
  * `flyTo`): long moves stay readable. Pitch and bearing follow the progress.
149
159
  */
150
160
  flyTo(camera: Partial<Camera>, options?: FlyToOptions): void;
161
+ /**
162
+ * Moves the camera to frame a box: a ground box (`[west, south, east, north]`), or a box in space (a building, a
163
+ * pipe underground: `{ bounds, bottom, top }`, altitudes in metres), all its corners within the canvas less the
164
+ * padding, at the pitch and bearing given (default the current ones); an ease, a flight with `fly`, a jump with
165
+ * `duration: 0`
166
+ */
167
+ fitBounds(box: FitBox | FitBox['bounds'], options?: FitBoundsOptions): void;
168
+ /** Height of the RAF20 geoid above the ellipsoid at a position (metres; 0 away from mainland France): what turns an
169
+ * altitude (NGF-IGN69: the BD TOPO's, the RGE ALTI's) into a height above the ellipsoid (`fitBounds`, `refine`) */
170
+ geoidHeight([lon, lat]: LonLat): number;
151
171
  /** Longitudes and latitudes the view covers on the ground: `[west, south, east, north]` */
152
172
  getBounds(): [west: number, south: number, east: number, north: number];
153
173
  /** Lowest zoom the camera accepts (the widest view) */
package/dist/map.js CHANGED
@@ -61,6 +61,8 @@ const BODY_START = { center: [0, 0], zoom: 2, pitch: 0, bearing: 0 };
61
61
  const FIRST_PERSON_PITCH = [1, 179];
62
62
  /** The eye's field of view at street level (degrees): from a telephoto to a wide angle, the projection still sane */
63
63
  const FIRST_PERSON_FOV = [10, 140];
64
+ /** The least GPU memory budget a lost device leaves the map (bytes, `lighten`) */
65
+ const MIN_MEMORY = 64 * 2 ** 20;
64
66
  /** A network's defaults: the values past which it is unreached (half an hour of travel, in seconds), its halo's
65
67
  * opacity at the middle of a line (a glow, not a second line) */
66
68
  const NETWORK_MAX = 1800;
@@ -141,7 +143,7 @@ export class GeoMap {
141
143
  // The engine on wgpu (#146): WebGPU, else wgpu's WebGL2 fallback
142
144
  const kind = await rendererKind(options.renderer);
143
145
  const bindings = await loadEngine(kind, engineUrl(kind, options));
144
- const map = new GeoMap(container, options, bindings.Engine);
146
+ const map = new GeoMap(container, options, bindings.Engine, kind);
145
147
  // Its device asked for: the map started on a placeholder meanwhile, its layers created on the engine once it is
146
148
  // there (as after a context loss)
147
149
  map.adopt(await map.engines.create(map.canvas, () => map.requestRender(), map.settings));
@@ -152,8 +154,8 @@ export class GeoMap {
152
154
  }
153
155
  return map;
154
156
  }
155
- constructor(container, options, engineClass) {
156
- this.engines = new MapEngines(engineClass, options);
157
+ constructor(container, options, engineClass, renderer) {
158
+ this.engines = new MapEngines(engineClass, renderer, options);
157
159
  const workerUrl = options.basemapWorkerUrl ?? defaultWorkerUrl(options.assetsUrl);
158
160
  this.tessUrl = tessUrl(options);
159
161
  this.gridUrl = gridUrl(options);
@@ -238,6 +240,13 @@ export class GeoMap {
238
240
  get onTop() {
239
241
  return this.stack.onTop;
240
242
  }
243
+ /** WebGPU devices lost so far: each one makes the next engine lighter (`lighten`) */
244
+ #losses = 0;
245
+ /** What the map draws through: `'webgpu'`, or `'webgl2'` (the fallback of a browser without WebGPU, or of a device
246
+ * WebGPU kept losing) */
247
+ get renderer() {
248
+ return this.engines.renderer;
249
+ }
241
250
  /** The GPU is lost: nothing drawn, the engine dropped (`dropLostEngine`) */
242
251
  gpuLost() {
243
252
  this.lost = true;
@@ -247,17 +256,40 @@ export class GeoMap {
247
256
  /**
248
257
  * Follows the loss of `engine`'s WebGPU device (a phone taking its memory back, a driver reset, the GPU process
249
258
  * gone): while it is the map's, the map drops it and makes a new one at once, WebGPU having no event for the GPU
250
- * given back. The device of an engine the map freed itself (replaced, the map destroyed) is lost too: not the map's
251
- * any more, left be
259
+ * given back, lighter so as not to lose it again (`lighten`). The device of an engine the map freed itself
260
+ * (replaced, the map destroyed) is lost too: not the map's any more, left be
252
261
  */
253
262
  watchDevice(engine) {
254
- void engine.gpuDevice()?.lost.then((info) => {
263
+ void engine
264
+ .gpuDevice()
265
+ ?.lost.then(async (info) => {
255
266
  if (this.gone || this.#engine !== engine)
256
267
  return;
257
- console.warn(`isard: the GPU device was lost (${info.reason}: ${info.message}), the map made again`);
258
268
  this.gpuLost();
269
+ const recovery = await this.lighten();
270
+ console.warn(`isard: the GPU device was lost (${info.reason}: ${info.message}), the map made again (${recovery})`);
271
+ this.emit('gpulost', { reason: info.reason, message: info.message, losses: this.#losses, recovery });
259
272
  this.restore();
260
- });
273
+ })
274
+ // The fallback's module not loaded: told, as a failed restore is
275
+ .catch(report);
276
+ }
277
+ /**
278
+ * After a device lost, the next engine lighter, the same scene then holding less: at the first loss the low quality
279
+ * (one pixel a CSS pixel, no sun's shadows, one worker, fewer tiles far away) and half the GPU memory budget; at the
280
+ * next one, the WebGL2 fallback (a phone whose WebGPU keeps losing its device: Safari's GPU process taking its
281
+ * memory back) and half the budget again. What it did: `'lighter'` or `'webgl2'`
282
+ */
283
+ async lighten() {
284
+ this.#losses += 1;
285
+ this.settings.memory = Math.max(MIN_MEMORY, this.settings.memory / 2);
286
+ if (this.#losses === 1 || this.engines.renderer === 'webgl2') {
287
+ this.qualityWatch = undefined;
288
+ this.applyQuality('low');
289
+ return 'lighter';
290
+ }
291
+ await this.engines.fallBack();
292
+ return 'webgl2';
261
293
  }
262
294
  /**
263
295
  * The context is lost: its engine freed at once, the map's calls going to one that does nothing until `restore`
@@ -278,8 +310,17 @@ export class GeoMap {
278
310
  restore() {
279
311
  if (this.gone)
280
312
  return;
281
- this.engines
282
- .create(this.canvas, () => this.requestRender(), this.settings)
313
+ const make = () => this.engines.create(this.canvas, () => this.requestRender(), this.settings);
314
+ make()
315
+ .catch(async (error) => {
316
+ // WebGPU giving no new device after a loss (an adapter refused): the WebGL2 fallback
317
+ if (this.engines.renderer === 'webgl2')
318
+ throw error;
319
+ console.warn(`isard: no new WebGPU device (${String(error)}), the map made again through WebGL2`);
320
+ await this.engines.fallBack();
321
+ this.emit('gpulost', { reason: 'unknown', message: String(error), losses: this.#losses, recovery: 'webgl2' });
322
+ return make();
323
+ })
283
324
  .then((engine) => this.adopt(engine))
284
325
  .catch(report);
285
326
  }
@@ -480,6 +521,35 @@ export class GeoMap {
480
521
  this.animate(flyTo(() => this.engine, from, to, viewport, options));
481
522
  this.engine.preload(to.center[0], to.center[1], to.zoom, to.pitch, to.bearing);
482
523
  }
524
+ /**
525
+ * Moves the camera to frame a box: a ground box (`[west, south, east, north]`), or a box in space (a building, a
526
+ * pipe underground: `{ bounds, bottom, top }`, altitudes in metres), all its corners within the canvas less the
527
+ * padding, at the pitch and bearing given (default the current ones); an ease, a flight with `fly`, a jump with
528
+ * `duration: 0`
529
+ */
530
+ fitBounds(box, options = {}) {
531
+ const { bounds, bottom = 0, top = bottom } = Array.isArray(box) ? { bounds: box } : box;
532
+ const current = this.getCamera();
533
+ const room = options.padding ?? 40;
534
+ const padding = typeof room === 'number'
535
+ ? [room, room, room, room]
536
+ : [room.top ?? 0, room.right ?? 0, room.bottom ?? 0, room.left ?? 0];
537
+ const camera = this.engine.fitCamera(Float64Array.from(bounds), bottom, top, Float64Array.from(padding), options.pitch ?? current.pitch, options.bearing ?? current.bearing);
538
+ if (!camera)
539
+ throw new Error('isard: fitBounds needs a box, west not past east nor south past north');
540
+ const to = { ...camera, zoom: Math.min(camera.zoom, options.maxZoom ?? Number.POSITIVE_INFINITY) };
541
+ if (options.duration === 0)
542
+ this.setCamera(to);
543
+ else if (options.fly)
544
+ this.flyTo(to, options);
545
+ else
546
+ this.easeTo(to, { duration: 600, ...options });
547
+ }
548
+ /** Height of the RAF20 geoid above the ellipsoid at a position (metres; 0 away from mainland France): what turns an
549
+ * altitude (NGF-IGN69: the BD TOPO's, the RGE ALTI's) into a height above the ellipsoid (`fitBounds`, `refine`) */
550
+ geoidHeight([lon, lat]) {
551
+ return this.engines.geoidHeight(lon, lat);
552
+ }
483
553
  /** Longitudes and latitudes the view covers on the ground: `[west, south, east, north]` */
484
554
  getBounds() {
485
555
  const [west = -180, south = -85, east = 180, north = 85] = this.engine.bounds();