isard 1.3.0 → 1.4.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 (61) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/README.md +2 -0
  3. package/dist/basemap/basemaps.js +1 -0
  4. package/dist/basemap/basemaps.js.map +1 -1
  5. package/dist/basemap/vectortiles.d.ts +26 -0
  6. package/dist/basemap/vectortiles.js +125 -1
  7. package/dist/basemap/vectortiles.js.map +1 -1
  8. package/dist/basemap/workerpool.js +9 -0
  9. package/dist/basemap/workerpool.js.map +1 -1
  10. package/dist/basemap.worker.js +2 -2
  11. package/dist/basemap.worker.js.map +7 -4
  12. package/dist/bind/layers.js +10 -0
  13. package/dist/bind/layers.js.map +1 -1
  14. package/dist/decoder.worker.js +3 -3
  15. package/dist/decoder.worker.js.map +3 -3
  16. package/dist/engine-loader.d.ts +4 -1
  17. package/dist/engine-loader.js +9 -0
  18. package/dist/engine-loader.js.map +1 -1
  19. package/dist/grid-module.d.ts +44 -0
  20. package/dist/grid-module.js +63 -0
  21. package/dist/grid-module.js.map +1 -0
  22. package/dist/grids.d.ts +51 -0
  23. package/dist/grids.js +88 -0
  24. package/dist/grids.js.map +1 -0
  25. package/dist/index.d.ts +3 -0
  26. package/dist/index.js +2 -0
  27. package/dist/index.js.map +1 -1
  28. package/dist/isard_bg.wasm +0 -0
  29. package/dist/isard_core_bg.wasm +0 -0
  30. package/dist/isard_grid_bg.wasm +0 -0
  31. package/dist/layers/flow.d.ts +111 -0
  32. package/dist/layers/flow.js +222 -0
  33. package/dist/layers/flow.js.map +1 -0
  34. package/dist/layers/symbols.d.ts +2 -0
  35. package/dist/layers/symbols.js +2 -1
  36. package/dist/layers/symbols.js.map +1 -1
  37. package/dist/map/layer-spec.d.ts +4 -1
  38. package/dist/map/layer-spec.js +2 -0
  39. package/dist/map/layer-spec.js.map +1 -1
  40. package/dist/map/workers.d.ts +4 -0
  41. package/dist/map/workers.js +35 -0
  42. package/dist/map/workers.js.map +1 -1
  43. package/dist/map.d.ts +14 -0
  44. package/dist/map.js +19 -1
  45. package/dist/map.js.map +1 -1
  46. package/dist/react/Layer.d.ts +2 -0
  47. package/dist/react/Layer.js.map +1 -1
  48. package/dist/types.d.ts +7 -3
  49. package/dist/types.js.map +1 -1
  50. package/dist/wasm/isard.d.ts +42 -0
  51. package/dist/wasm/isard.js +91 -1
  52. package/dist/wasm/isard_core.d.ts +42 -0
  53. package/dist/wasm/isard_core.js +91 -1
  54. package/dist/wasm/isard_grid.d.ts +101 -0
  55. package/dist/wasm/isard_grid.js +375 -0
  56. package/dist/workers/basemap/street.d.ts +19 -0
  57. package/dist/workers/basemap.worker.d.ts +21 -0
  58. package/dist/workers/basemap.worker.js +23 -4
  59. package/dist/workers/basemap.worker.js.map +1 -1
  60. package/package.json +2 -1
  61. package/vite.js +3 -2
package/CHANGELOG.md CHANGED
@@ -3,6 +3,42 @@
3
3
  Every notable change of `isard` (published as `@ign-junn/isard` up to 0.1.1). The format follows [Keep a Changelog](https://keepachangelog.com), the
4
4
  versions [Semantic Versioning](https://semver.org) (before 1.0.0, a minor version may change an API).
5
5
 
6
+ ## [1.4.0] - 2026-10-24
7
+
8
+ ### Added
9
+
10
+ - Flows: `map.addFlow({ field, colors, width, speed, density, particles, trail, playing })` (and `<Layer
11
+ type="flow">`), particles carried by a field of velocities (a wind, a current: its east and north components on a
12
+ grid of longitudes and latitudes, m/s), their trails animated on the globe and the relief and colored by speed;
13
+ born in the view and as dense on screen at any zoom, moved by the engine in steps of a 60th of a second, their last
14
+ positions kept on the GPU and drawn in 3D; `play`, `pause`, `step`, `setField`; `flowFieldFromImage` reads a field
15
+ from an image. The example Wind (NOAA's GFS over the world), in TypeScript and React. About 4.3 KB gzipped more for
16
+ the engine.
17
+ - A model's hours in a flow layer: `fields` at their times and `setTime` (a field between two hours their linear mix,
18
+ the particles going on), `flowFieldsFromImage` for the fields of one image; the example Wind through a day (GFS every
19
+ 3 hours) with a clock.
20
+ - A model's files read in the workers: `map.readGrids(url | bytes)` reads GRIB2 (regular latitude/longitude grids,
21
+ packed simply, in groups or in groups of spatial differences, NOAA's GFS as it comes, with or without a bitmap) and
22
+ classic NetCDF (the CF conventions: scale, offset, fill values, times "since" a date) into `Grid`s, by a module of its
23
+ own (`isard_grid_bg.wasm`, the crate `isard-grid`, 58 KB gzipped, loaded with the first file; `gridWasmUrl`);
24
+ `windFields` pairs their wind's components by time into a flow layer's `fields`. The example Wind reads the GFS's
25
+ GRIB2 file.
26
+ - The wind through a city's streets: `vectorTiles().streetWind({ wind, size, cell, height })`, particles at street
27
+ level round the style's buildings (hidden by those in front of them), the flow a potential flow round their
28
+ footprints computed in a worker by the grid module, `setWind` turning it at once (the flows of an east and a north
29
+ wind combined); `windAt(fields, time, lonLat)`, a model's wind at a place and time. The example Wind in the streets
30
+ (the GFS's wind around the Louvre at the hour of a clock), in TypeScript and React. A flow layer's particles are
31
+ born only where its field has a value.
32
+ - `'osm-dark'`, OpenFreeMap's Dark style, and its `dark` and `fiord` styles in `OPENFREEMAP_STYLES`.
33
+ - Labels over the ground: `addSymbols({ height })` (and `<Layer type="symbols" height>`), metres over the ground (the
34
+ relief under them), on a building's floor or roof; placed and drawn there.
35
+
36
+ ### Changed
37
+
38
+ - Inside a shopping mall: its five floors stacked as they stand rather than one at a time, each floor, the walls and the
39
+ names hidden on demand; its escalators and stairs climbing between floors, its lifts, toilets, information desks and
40
+ entrances from OpenStreetMap; the shops' names at their floor.
41
+
6
42
  ## [1.3.0] - 2026-10-23
7
43
 
8
44
  ### Added
package/README.md CHANGED
@@ -44,6 +44,8 @@ General-purpose map libraries spend their time on things these maps do not need.
44
44
  its `fill-extrusion` layers, sharp at any zoom (its tiles cut again past the source's deepest level), 3D trees in
45
45
  its woods, the sun's shadows at any hour, the night with lit windows and glowing streets; indoor data floor by floor
46
46
  (a shopping mall, its shops raised and named).
47
+ - 🌬️ **Wind and currents.** Particles carried by a field of velocities (a weather model's wind, an ocean's current),
48
+ their trails animated on the globe and the relief, colored by speed, as dense on screen at any zoom.
47
49
  - 🏙️ **Buildings and 3D Tiles.** Buildings colored by a value or by a network's travel times, photomeshes and city
48
50
  models in 3D Tiles (Draco, meshopt, KTX2).
49
51
  - ☁️ **Point clouds.** LiDAR in COPC read by range requests (the IGN's LiDAR HD, reprojected from Lambert-93 onto the
@@ -75,6 +75,7 @@ export const SEA = {
75
75
  'osm-positron': [0.761, 0.784, 0.792],
76
76
  'osm-liberty': [0.62, 0.741, 1],
77
77
  'osm-bright': [0.682, 0.812, 0.886],
78
+ 'osm-dark': [0.106, 0.106, 0.114],
78
79
  'ign-plan': [0.667, 0.835, 0.914],
79
80
  'ign-plan-grey': [0.792, 0.792, 0.792],
80
81
  'ign-plan-vector': [0.667, 0.835, 0.914],
@@ -1 +1 @@
1
- {"version":3,"file":"basemaps.js","sourceRoot":"","sources":["../../src/basemap/basemaps.ts"],"names":[],"mappings":"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","sourcesContent":["/** The basemaps by name: OpenFreeMap's vector styles (OpenStreetMap data, worldwide) and the Géoplateforme's (IGN,\n * France), the credits of the sources, and the relief of each body */\nimport { geoplateformeWmts, IGN_ATTRIBUTION } from '../geoplateforme.ts';\nimport type { BasemapName, RasterOptions } from '../types.ts';\n\nexport { IGN_ATTRIBUTION };\n\nconst PLAN = 'GEOGRAPHICALGRIDSYSTEMS.PLANIGNV2';\n\n/** The raster basemaps of the Géoplateforme (WMTS, Web Mercator), crediting the IGN */\nexport const BASEMAPS: Record<'ign-plan' | 'ign-plan-grey' | 'ign-ortho', RasterOptions> = {\n 'ign-plan': geoplateformeWmts(PLAN),\n 'ign-plan-grey': geoplateformeWmts(PLAN, { saturation: 0 }),\n 'ign-ortho': geoplateformeWmts('ORTHOIMAGERY.ORTHOPHOTOS', { format: 'image/jpeg' }),\n};\n\n/** The former names of the Géoplateforme basemaps (before 0.5), and their names now: an error tells it */\nexport const RENAMED_BASEMAPS: Record<string, BasemapName> = {\n plan: 'ign-plan',\n 'plan-grey': 'ign-plan-grey',\n ortho: 'ign-ortho',\n 'plan-vector': 'ign-plan-vector',\n 'plan-grey-vector': 'ign-plan-grey-vector',\n};\n\n/** Credits of OpenFreeMap's basemaps, as its TileJSON gives them */\nexport const OPENFREEMAP_ATTRIBUTION =\n '<a href=\"https://openfreemap.org\">OpenFreeMap</a> <a href=\"https://www.openmaptiles.org/\">© OpenMapTiles</a> ' +\n 'Data from <a href=\"https://www.openstreetmap.org/copyright\">OpenStreetMap</a>';\n\n/** Credits of the Terrain Tiles of AWS's open data registry (Mapzen's Terrarium tiles) and of their sources */\nexport const TERRAIN_TILES_ATTRIBUTION =\n '<a href=\"https://github.com/tilezen/joerd/blob/master/docs/attribution.md\">Terrain Tiles</a> (Mapzen, AWS)';\n\n/** IGN hillshade, grey on transparent, zooms 6 to 17 */\nexport const HILLSHADE: RasterOptions = geoplateformeWmts('IGNF_ELEVATION.ELEVATIONGRIDCOVERAGE.SHADOW', {\n minZoom: 6,\n maxZoom: 17,\n});\n\n/** IGN terrain model (RGE ALTI and its fill-ins, France), raw altitudes in metres from the Géoplateforme WMS */\nexport const TERRAIN_URL =\n 'https://data.geopf.fr/wms-r/wms?SERVICE=WMS&VERSION=1.3.0&REQUEST=GetMap&LAYERS=ELEVATION.ELEVATIONGRIDCOVERAGE.HIGHRES' +\n '&STYLES=&CRS=EPSG:3857&BBOX={bbox}&WIDTH=256&HEIGHT=256&FORMAT=image/x-bil;bits=32';\n\n/** The relief of the Moon and Mars by default: NASA's Trek image services (ArcGIS), raw 32-bit altitudes in metres\n * asked for in the body's Mercator, with their credits. The Moon: LOLA's global model, 128 pixels per degree (237 m);\n * Mars: MOLA's, 128 and 64 pixels per degree merged (463 m), above the areoid */\nexport const BODY_TERRAINS = {\n moon: {\n url: 'https://trek.nasa.gov/moon/trekarcgis/rest/services/LRO_LOLA_DEM_Global_128ppd_v04/ImageServer',\n maxZoom: 8,\n attribution: '<a href=\"https://trek.nasa.gov/moon/\">NASA Moon Trek</a>: LOLA terrain model, NASA/GSFC/MIT',\n },\n mars: {\n url: 'https://trek.nasa.gov/mars/trekarcgis/rest/services/mola128_mola64_merge_90Nto90S_SimpleC_clon0/ImageServer',\n maxZoom: 8,\n attribution: '<a href=\"https://trek.nasa.gov/mars/\">NASA Mars Trek</a>: MOLA terrain model, NASA/JPL/GSFC',\n },\n} as const;\n\n/** An ArcGIS image service (a URL ending in `/ImageServer`) */\nexport const IMAGE_SERVER = /\\/ImageServer\\/?$/;\n\n/**\n * The `exportImage` template of an ArcGIS image service for the relief: 256 × 256 raw 32-bit floats (`bsq`, followed\n * by its validity mask) of the box `{bbox}` in the Mercator of the body. On the Earth, Web Mercator (EPSG:3857);\n * elsewhere, a Mercator on the sphere of `radius` metres (Web Mercator's own formulas), given in WKT.\n */\nexport const imageServerTerrain = (url: string, earth: boolean, radius: number): string => {\n const crs = earth\n ? '3857'\n : encodeURIComponent(\n JSON.stringify({\n wkt:\n `PROJCS[\"Mercator\",GEOGCS[\"GCS\",DATUM[\"D\",SPHEROID[\"S\",${radius},0]],PRIMEM[\"Reference_Meridian\",0],` +\n 'UNIT[\"Degree\",0.0174532925199433]],PROJECTION[\"Mercator\"],PARAMETER[\"False_Easting\",0],' +\n 'PARAMETER[\"False_Northing\",0],PARAMETER[\"Central_Meridian\",0],PARAMETER[\"Standard_Parallel_1\",0],' +\n 'UNIT[\"Meter\",1]]',\n }),\n );\n\n return (\n `${url.replace(/\\/$/, '')}/exportImage?f=image&format=bsq&pixelType=F32&size=256,256` +\n `&bboxSR=${crs}&imageSR=${crs}&bbox={bbox}`\n );\n};\n\n/** The Earth's relief for `terrain: true`: the Terrain Tiles of AWS's open data registry, worldwide (SRTM, EU-DEM,\n * GMTED… merged by Mapzen), Terrarium PNGs of 256 px to level 14, altitudes above the mean sea level */\nexport const TERRAIN_TILES = {\n url: 'https://s3.amazonaws.com/elevation-tiles-prod/terrarium/{z}/{x}/{y}.png',\n maxZoom: 14,\n} as const;\n\n/** Sea color of each basemap, for the globe under the tiles (polar caps), RGB 0-1 */\nexport const SEA: Record<BasemapName, [r: number, g: number, b: number]> = {\n 'osm-positron': [0.761, 0.784, 0.792],\n 'osm-liberty': [0.62, 0.741, 1],\n 'osm-bright': [0.682, 0.812, 0.886],\n 'ign-plan': [0.667, 0.835, 0.914],\n 'ign-plan-grey': [0.792, 0.792, 0.792],\n 'ign-plan-vector': [0.667, 0.835, 0.914],\n 'ign-plan-grey-vector': [0.792, 0.792, 0.792],\n 'ign-ortho': [0.039, 0.231, 0.349],\n none: [0.85, 0.85, 0.83],\n};\n"]}
1
+ {"version":3,"file":"basemaps.js","sourceRoot":"","sources":["../../src/basemap/basemaps.ts"],"names":[],"mappings":"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","sourcesContent":["/** The basemaps by name: OpenFreeMap's vector styles (OpenStreetMap data, worldwide) and the Géoplateforme's (IGN,\n * France), the credits of the sources, and the relief of each body */\nimport { geoplateformeWmts, IGN_ATTRIBUTION } from '../geoplateforme.ts';\nimport type { BasemapName, RasterOptions } from '../types.ts';\n\nexport { IGN_ATTRIBUTION };\n\nconst PLAN = 'GEOGRAPHICALGRIDSYSTEMS.PLANIGNV2';\n\n/** The raster basemaps of the Géoplateforme (WMTS, Web Mercator), crediting the IGN */\nexport const BASEMAPS: Record<'ign-plan' | 'ign-plan-grey' | 'ign-ortho', RasterOptions> = {\n 'ign-plan': geoplateformeWmts(PLAN),\n 'ign-plan-grey': geoplateformeWmts(PLAN, { saturation: 0 }),\n 'ign-ortho': geoplateformeWmts('ORTHOIMAGERY.ORTHOPHOTOS', { format: 'image/jpeg' }),\n};\n\n/** The former names of the Géoplateforme basemaps (before 0.5), and their names now: an error tells it */\nexport const RENAMED_BASEMAPS: Record<string, BasemapName> = {\n plan: 'ign-plan',\n 'plan-grey': 'ign-plan-grey',\n ortho: 'ign-ortho',\n 'plan-vector': 'ign-plan-vector',\n 'plan-grey-vector': 'ign-plan-grey-vector',\n};\n\n/** Credits of OpenFreeMap's basemaps, as its TileJSON gives them */\nexport const OPENFREEMAP_ATTRIBUTION =\n '<a href=\"https://openfreemap.org\">OpenFreeMap</a> <a href=\"https://www.openmaptiles.org/\">© OpenMapTiles</a> ' +\n 'Data from <a href=\"https://www.openstreetmap.org/copyright\">OpenStreetMap</a>';\n\n/** Credits of the Terrain Tiles of AWS's open data registry (Mapzen's Terrarium tiles) and of their sources */\nexport const TERRAIN_TILES_ATTRIBUTION =\n '<a href=\"https://github.com/tilezen/joerd/blob/master/docs/attribution.md\">Terrain Tiles</a> (Mapzen, AWS)';\n\n/** IGN hillshade, grey on transparent, zooms 6 to 17 */\nexport const HILLSHADE: RasterOptions = geoplateformeWmts('IGNF_ELEVATION.ELEVATIONGRIDCOVERAGE.SHADOW', {\n minZoom: 6,\n maxZoom: 17,\n});\n\n/** IGN terrain model (RGE ALTI and its fill-ins, France), raw altitudes in metres from the Géoplateforme WMS */\nexport const TERRAIN_URL =\n 'https://data.geopf.fr/wms-r/wms?SERVICE=WMS&VERSION=1.3.0&REQUEST=GetMap&LAYERS=ELEVATION.ELEVATIONGRIDCOVERAGE.HIGHRES' +\n '&STYLES=&CRS=EPSG:3857&BBOX={bbox}&WIDTH=256&HEIGHT=256&FORMAT=image/x-bil;bits=32';\n\n/** The relief of the Moon and Mars by default: NASA's Trek image services (ArcGIS), raw 32-bit altitudes in metres\n * asked for in the body's Mercator, with their credits. The Moon: LOLA's global model, 128 pixels per degree (237 m);\n * Mars: MOLA's, 128 and 64 pixels per degree merged (463 m), above the areoid */\nexport const BODY_TERRAINS = {\n moon: {\n url: 'https://trek.nasa.gov/moon/trekarcgis/rest/services/LRO_LOLA_DEM_Global_128ppd_v04/ImageServer',\n maxZoom: 8,\n attribution: '<a href=\"https://trek.nasa.gov/moon/\">NASA Moon Trek</a>: LOLA terrain model, NASA/GSFC/MIT',\n },\n mars: {\n url: 'https://trek.nasa.gov/mars/trekarcgis/rest/services/mola128_mola64_merge_90Nto90S_SimpleC_clon0/ImageServer',\n maxZoom: 8,\n attribution: '<a href=\"https://trek.nasa.gov/mars/\">NASA Mars Trek</a>: MOLA terrain model, NASA/JPL/GSFC',\n },\n} as const;\n\n/** An ArcGIS image service (a URL ending in `/ImageServer`) */\nexport const IMAGE_SERVER = /\\/ImageServer\\/?$/;\n\n/**\n * The `exportImage` template of an ArcGIS image service for the relief: 256 × 256 raw 32-bit floats (`bsq`, followed\n * by its validity mask) of the box `{bbox}` in the Mercator of the body. On the Earth, Web Mercator (EPSG:3857);\n * elsewhere, a Mercator on the sphere of `radius` metres (Web Mercator's own formulas), given in WKT.\n */\nexport const imageServerTerrain = (url: string, earth: boolean, radius: number): string => {\n const crs = earth\n ? '3857'\n : encodeURIComponent(\n JSON.stringify({\n wkt:\n `PROJCS[\"Mercator\",GEOGCS[\"GCS\",DATUM[\"D\",SPHEROID[\"S\",${radius},0]],PRIMEM[\"Reference_Meridian\",0],` +\n 'UNIT[\"Degree\",0.0174532925199433]],PROJECTION[\"Mercator\"],PARAMETER[\"False_Easting\",0],' +\n 'PARAMETER[\"False_Northing\",0],PARAMETER[\"Central_Meridian\",0],PARAMETER[\"Standard_Parallel_1\",0],' +\n 'UNIT[\"Meter\",1]]',\n }),\n );\n\n return (\n `${url.replace(/\\/$/, '')}/exportImage?f=image&format=bsq&pixelType=F32&size=256,256` +\n `&bboxSR=${crs}&imageSR=${crs}&bbox={bbox}`\n );\n};\n\n/** The Earth's relief for `terrain: true`: the Terrain Tiles of AWS's open data registry, worldwide (SRTM, EU-DEM,\n * GMTED… merged by Mapzen), Terrarium PNGs of 256 px to level 14, altitudes above the mean sea level */\nexport const TERRAIN_TILES = {\n url: 'https://s3.amazonaws.com/elevation-tiles-prod/terrarium/{z}/{x}/{y}.png',\n maxZoom: 14,\n} as const;\n\n/** Sea color of each basemap, for the globe under the tiles (polar caps), RGB 0-1 */\nexport const SEA: Record<BasemapName, [r: number, g: number, b: number]> = {\n 'osm-positron': [0.761, 0.784, 0.792],\n 'osm-liberty': [0.62, 0.741, 1],\n 'osm-bright': [0.682, 0.812, 0.886],\n 'osm-dark': [0.106, 0.106, 0.114],\n 'ign-plan': [0.667, 0.835, 0.914],\n 'ign-plan-grey': [0.792, 0.792, 0.792],\n 'ign-plan-vector': [0.667, 0.835, 0.914],\n 'ign-plan-grey-vector': [0.792, 0.792, 0.792],\n 'ign-ortho': [0.039, 0.231, 0.349],\n none: [0.85, 0.85, 0.83],\n};\n"]}
@@ -10,6 +10,7 @@
10
10
  */
11
11
  import type { ExtrusionLayer, ExtrusionOptions } from '../layers/extrusions.ts';
12
12
  import type { Feature, Size } from '../layers/features.ts';
13
+ import type { FlowLayer, FlowOptions } from '../layers/flow.ts';
13
14
  import type { Layer, ModelLayer, RasterLayer } from '../layers/layers.ts';
14
15
  import type { SymbolLayer } from '../layers/symbols.ts';
15
16
  import type { VectorLayer } from '../layers/vectors.ts';
@@ -21,6 +22,8 @@ export declare const OPENFREEMAP_STYLES: {
21
22
  readonly positron: "https://tiles.openfreemap.org/styles/positron";
22
23
  readonly liberty: "https://tiles.openfreemap.org/styles/liberty";
23
24
  readonly bright: "https://tiles.openfreemap.org/styles/bright";
25
+ readonly dark: "https://tiles.openfreemap.org/styles/dark";
26
+ readonly fiord: "https://tiles.openfreemap.org/styles/fiord";
24
27
  };
25
28
  /** PLAN.IGN vector styles of the Géoplateforme (France) */
26
29
  export declare const PLAN_STYLES: {
@@ -32,6 +35,7 @@ export declare const VECTOR_BASEMAPS: {
32
35
  readonly 'osm-positron': "https://tiles.openfreemap.org/styles/positron";
33
36
  readonly 'osm-liberty': "https://tiles.openfreemap.org/styles/liberty";
34
37
  readonly 'osm-bright': "https://tiles.openfreemap.org/styles/bright";
38
+ readonly 'osm-dark': "https://tiles.openfreemap.org/styles/dark";
35
39
  readonly 'ign-plan-vector': "https://data.geopf.fr/annexes/ressources/vectorTiles/styles/PLAN.IGN/standard.json";
36
40
  readonly 'ign-plan-grey-vector': "https://data.geopf.fr/annexes/ressources/vectorTiles/styles/PLAN.IGN/gris.json";
37
41
  };
@@ -123,6 +127,28 @@ export declare class VectorTiles {
123
127
  minZoom?: number;
124
128
  maxTiles?: number;
125
129
  }): ExtrusionLayer<P>;
130
+ /**
131
+ * The wind through the streets around the view, as particles at street level (`height`, default 3 metres) between
132
+ * the style's buildings (`layers`, default its `building` and `bati_surf` layers): their footprints laid on a grid of
133
+ * `cell` metres (default 4) over a square of `size` metres (default 1200) around the view's center, the flow of the
134
+ * air round them a potential flow (it goes round the blocks and faster through the narrow streets; no wake behind
135
+ * them), computed in a worker by the grid module from street level (`minZoom`, default 15) and again when the view
136
+ * leaves the square's middle. `wind` is the wind far away (east and north, m/s, default a light breeze from the
137
+ * south-west); `setWind` changes it at once (the flow is linear in it: nothing computed again), from a model's hour
138
+ * (`windAt`) for instance; `computed` resolves once the first square's flow is there. The other options are the flow
139
+ * layer's (`addFlow`)
140
+ */
141
+ streetWind(options?: Omit<FlowOptions, 'field' | 'fields' | 'time'> & {
142
+ wind?: [u: number, v: number];
143
+ size?: number;
144
+ cell?: number;
145
+ iterations?: number;
146
+ layers?: (layer: StyleLayer) => boolean;
147
+ minZoom?: number;
148
+ }): FlowLayer & {
149
+ setWind(wind: [u: number, v: number]): void;
150
+ computed: Promise<void>;
151
+ };
126
152
  /**
127
153
  * Trees on the style's woods, forests and grass (or the fill layers `layers` keeps) as 3D models, from street level
128
154
  * (`minZoom`, default 15): the workers scatter them over the polygons of the vector tiles around the view, `spacing`
@@ -11,7 +11,7 @@
11
11
  import { withHeaders } from "../engine/requests.js";
12
12
  import { IGN_ATTRIBUTION } from "../geoplateforme.js";
13
13
  import { loadSprites, readPixels } from "../layers/icons.js";
14
- import { mercatorX, mercatorY } from "../util/geo.js";
14
+ import { latitude, longitude, mercatorX, mercatorY } from "../util/geo.js";
15
15
  import { isRecord } from "../util/guards.js";
16
16
  import { Lru } from "../util/lru.js";
17
17
  import { OPENFREEMAP_ATTRIBUTION } from "./basemaps.js";
@@ -24,6 +24,8 @@ export const OPENFREEMAP_STYLES = {
24
24
  positron: 'https://tiles.openfreemap.org/styles/positron',
25
25
  liberty: 'https://tiles.openfreemap.org/styles/liberty',
26
26
  bright: 'https://tiles.openfreemap.org/styles/bright',
27
+ dark: 'https://tiles.openfreemap.org/styles/dark',
28
+ fiord: 'https://tiles.openfreemap.org/styles/fiord',
27
29
  };
28
30
  /** PLAN.IGN vector styles of the Géoplateforme (France) */
29
31
  export const PLAN_STYLES = {
@@ -35,6 +37,7 @@ export const VECTOR_BASEMAPS = {
35
37
  'osm-positron': OPENFREEMAP_STYLES.positron,
36
38
  'osm-liberty': OPENFREEMAP_STYLES.liberty,
37
39
  'osm-bright': OPENFREEMAP_STYLES.bright,
40
+ 'osm-dark': OPENFREEMAP_STYLES.dark,
38
41
  'ign-plan-vector': PLAN_STYLES.standard,
39
42
  'ign-plan-grey-vector': PLAN_STYLES.grey,
40
43
  };
@@ -360,6 +363,127 @@ export class VectorTiles {
360
363
  this.followView(update, held, update);
361
364
  return extrusions;
362
365
  }
366
+ /**
367
+ * The wind through the streets around the view, as particles at street level (`height`, default 3 metres) between
368
+ * the style's buildings (`layers`, default its `building` and `bati_surf` layers): their footprints laid on a grid of
369
+ * `cell` metres (default 4) over a square of `size` metres (default 1200) around the view's center, the flow of the
370
+ * air round them a potential flow (it goes round the blocks and faster through the narrow streets; no wake behind
371
+ * them), computed in a worker by the grid module from street level (`minZoom`, default 15) and again when the view
372
+ * leaves the square's middle. `wind` is the wind far away (east and north, m/s, default a light breeze from the
373
+ * south-west); `setWind` changes it at once (the flow is linear in it: nothing computed again), from a model's hour
374
+ * (`windAt`) for instance; `computed` resolves once the first square's flow is there. The other options are the flow
375
+ * layer's (`addFlow`)
376
+ */
377
+ streetWind(options = {}) {
378
+ const { size = 1200, cell = 4, iterations = 300, layers = isBuildings, minZoom = 15, ...flow } = options;
379
+ const renderer = this.renderer((l) => (l.type === 'fill' || l.type === 'fill-extrusion') && layers(l), []);
380
+ const [lon, lat] = this.map.getCamera().center;
381
+ // Nothing yet: a field of no value, where no particle is born
382
+ const empty = { width: 2, height: 2, u: [NaN, NaN, NaN, NaN], v: [NaN, NaN, NaN, NaN] };
383
+ const layer = this.map.addFlow({
384
+ height: 3,
385
+ ...flow,
386
+ field: { ...empty, bounds: [lon, lat, lon + 1e-4, lat + 1e-4] },
387
+ });
388
+ const state = { wind: options.wind ?? [3, 2], asked: 0, wait: 1000 };
389
+ /** The flow of the wind far away: `u` times the flow towards the east plus `v` times the one towards the north */
390
+ const send = () => {
391
+ const { flows, grid, wind } = state;
392
+ if (!flows || !grid)
393
+ return;
394
+ const [wu, wv] = wind;
395
+ const u = flows.eastU.map((e, i) => wu * e + wv * (flows.northU[i] ?? 0));
396
+ const v = flows.eastV.map((e, i) => wu * e + wv * (flows.northV[i] ?? 0));
397
+ layer.setField({ ...grid, u, v });
398
+ };
399
+ const held = new Map();
400
+ const first = {};
401
+ const computed = new Promise((resolve) => {
402
+ first.resolve = resolve;
403
+ });
404
+ const update = async () => {
405
+ if (this.destroyed)
406
+ return;
407
+ const camera = this.map.getCamera();
408
+ const [x, y] = [mercatorX(camera.center[0]), mercatorY(camera.center[1])];
409
+ // Half the square in Mercator, at its latitude
410
+ const half = size / 2 / (40_075_016.686 * Math.cos((camera.center[1] * Math.PI) / 180));
411
+ const square = state.square;
412
+ // Off street level, or still in the square's middle: nothing to compute
413
+ if (camera.zoom < minZoom)
414
+ return;
415
+ if (square &&
416
+ Math.abs(x - (square[0] + square[2]) / 2) < half / 3 &&
417
+ Math.abs(y - (square[1] + square[3]) / 2) < half / 3) {
418
+ return;
419
+ }
420
+ const { key, maxzoom } = await renderer;
421
+ const bounds = [x - half, y - half, x + half, y + half];
422
+ const n = 2 ** maxzoom;
423
+ const tiles = [];
424
+ for (let ty = Math.floor(bounds[1] * n); ty <= Math.floor(bounds[3] * n); ty++) {
425
+ for (let tx = Math.floor(bounds[0] * n); tx <= Math.floor(bounds[2] * n); tx++)
426
+ tiles.push(tx, ty);
427
+ }
428
+ const sides = Math.max(8, Math.round(size / cell));
429
+ const work = {
430
+ type: 'street',
431
+ key,
432
+ module: this.map.gridUrl,
433
+ z: maxzoom,
434
+ tiles,
435
+ bounds,
436
+ width: sides,
437
+ height: sides,
438
+ iterations,
439
+ };
440
+ const asked = ++state.asked;
441
+ held.forEach((c) => {
442
+ c.abort();
443
+ });
444
+ held.clear();
445
+ const controller = new AbortController();
446
+ held.set('square', controller);
447
+ state.square = bounds;
448
+ const answer = await this.request(maxzoom, Math.floor(x * n), Math.floor(y * n), work, controller.signal).catch((error) => ({ error }));
449
+ if (asked !== state.asked || controller.signal.aborted)
450
+ return;
451
+ // Failed (a tile or the grid module not fetched, a worker of an older build): the square asked for again later,
452
+ // a while after each failure
453
+ if (!('street' in answer)) {
454
+ console.warn("isard: the streets' wind not computed, tried again", 'error' in answer ? answer.error : answer);
455
+ state.square = undefined;
456
+ clearTimeout(state.retry);
457
+ state.retry = window.setTimeout(() => {
458
+ update().catch(() => undefined);
459
+ }, state.wait);
460
+ state.wait = Math.min(state.wait * 2, 30_000);
461
+ return;
462
+ }
463
+ state.wait = 1000;
464
+ state.flows = answer.street;
465
+ state.grid = {
466
+ width: sides,
467
+ height: sides,
468
+ bounds: [longitude(bounds[0]), latitude(bounds[3]), longitude(bounds[2]), latitude(bounds[1])],
469
+ };
470
+ send();
471
+ first.resolve?.();
472
+ };
473
+ renderer.catch(() => undefined);
474
+ this.layers.push(layer);
475
+ this.followView(update, held, () => undefined);
476
+ this.cleanup.push(() => {
477
+ clearTimeout(state.retry);
478
+ });
479
+ return Object.assign(layer, {
480
+ computed,
481
+ setWind: (wind) => {
482
+ state.wind = wind;
483
+ send();
484
+ },
485
+ });
486
+ }
363
487
  /**
364
488
  * Trees on the style's woods, forests and grass (or the fill layers `layers` keeps) as 3D models, from street level
365
489
  * (`minZoom`, default 15): the workers scatter them over the polygons of the vector tiles around the view, `spacing`