isard 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/CHANGELOG.md +134 -14
  2. package/README.md +2 -2
  3. package/dist/animation.d.ts +18 -0
  4. package/dist/animation.js +84 -0
  5. package/dist/animation.js.map +1 -0
  6. package/dist/basemap.worker.d.ts +26 -1
  7. package/dist/basemap.worker.js +2 -2
  8. package/dist/basemap.worker.js.map +8 -4
  9. package/dist/basemaps.d.ts +5 -3
  10. package/dist/basemaps.js +5 -3
  11. package/dist/basemaps.js.map +1 -1
  12. package/dist/crs.d.ts +72 -0
  13. package/dist/crs.js +243 -0
  14. package/dist/crs.js.map +1 -0
  15. package/dist/decoder.worker.js +4 -4
  16. package/dist/decoder.worker.js.map +4 -4
  17. package/dist/engine-loader.d.ts +27 -0
  18. package/dist/engine-loader.js +112 -0
  19. package/dist/engine-loader.js.map +1 -0
  20. package/dist/extrusions.js +11 -2
  21. package/dist/extrusions.js.map +1 -1
  22. package/dist/features.d.ts +8 -0
  23. package/dist/features.js +17 -10
  24. package/dist/features.js.map +1 -1
  25. package/dist/geo.d.ts +16 -0
  26. package/dist/geo.js +32 -0
  27. package/dist/geo.js.map +1 -0
  28. package/dist/geoplateforme.d.ts +21 -5
  29. package/dist/geoplateforme.js +47 -12
  30. package/dist/geoplateforme.js.map +1 -1
  31. package/dist/glyphs.d.ts +10 -3
  32. package/dist/glyphs.js +14 -2
  33. package/dist/glyphs.js.map +1 -1
  34. package/dist/gpx.d.ts +11 -0
  35. package/dist/gpx.js +74 -0
  36. package/dist/gpx.js.map +1 -0
  37. package/dist/grid.d.ts +81 -0
  38. package/dist/grid.js +270 -0
  39. package/dist/grid.js.map +1 -0
  40. package/dist/index.d.ts +7 -1
  41. package/dist/index.js +5 -0
  42. package/dist/index.js.map +1 -1
  43. package/dist/interaction.d.ts +3 -4
  44. package/dist/interaction.js +27 -7
  45. package/dist/interaction.js.map +1 -1
  46. package/dist/isard_bg.wasm +0 -0
  47. package/dist/isard_core_bg.wasm +0 -0
  48. package/dist/kml.d.ts +12 -0
  49. package/dist/kml.js +159 -0
  50. package/dist/kml.js.map +1 -0
  51. package/dist/layers.d.ts +5 -2
  52. package/dist/layers.js +22 -18
  53. package/dist/layers.js.map +1 -1
  54. package/dist/lru.d.ts +22 -0
  55. package/dist/lru.js +52 -0
  56. package/dist/lru.js.map +1 -0
  57. package/dist/map.d.ts +26 -6
  58. package/dist/map.js +196 -287
  59. package/dist/map.js.map +1 -1
  60. package/dist/resample.d.ts +41 -0
  61. package/dist/resample.js +164 -0
  62. package/dist/resample.js.map +1 -0
  63. package/dist/settings.d.ts +39 -0
  64. package/dist/settings.js +104 -0
  65. package/dist/settings.js.map +1 -0
  66. package/dist/symbols.d.ts +12 -1
  67. package/dist/symbols.js +124 -26
  68. package/dist/symbols.js.map +1 -1
  69. package/dist/terrain.d.ts +19 -7
  70. package/dist/terrain.js +50 -26
  71. package/dist/terrain.js.map +1 -1
  72. package/dist/topojson.d.ts +29 -0
  73. package/dist/topojson.js +66 -0
  74. package/dist/topojson.js.map +1 -0
  75. package/dist/types.d.ts +23 -7
  76. package/dist/types.js.map +1 -1
  77. package/dist/vectortiles.d.ts +13 -3
  78. package/dist/vectortiles.js +85 -46
  79. package/dist/vectortiles.js.map +1 -1
  80. package/dist/wasm/isard.d.ts +14 -7
  81. package/dist/wasm/isard.js +33 -10
  82. package/dist/wasm/isard_core.d.ts +6 -5
  83. package/dist/wasm/isard_core.js +41 -10
  84. package/dist/wmts.d.ts +14 -0
  85. package/dist/wmts.js +76 -0
  86. package/dist/wmts.js.map +1 -0
  87. package/dist/xml.d.ts +26 -0
  88. package/dist/xml.js +109 -0
  89. package/dist/xml.js.map +1 -0
  90. package/package.json +1 -1
  91. package/dist/equirectangular.d.ts +0 -35
  92. package/dist/equirectangular.js +0 -113
  93. package/dist/equirectangular.js.map +0 -1
@@ -0,0 +1,164 @@
1
+ /**
2
+ * Raster reprojection, the drawing half: a Web Mercator tile drawn from the source images it spans (tiles of another
3
+ * grid, or one image of a WMS), in a worker of the map (OffscreenCanvas) or in the page when the map has no workers.
4
+ * The page plans each tile (`grid.ts`): which images, where they go in a mosaic, and a mesh of control points giving,
5
+ * for the corners of 16 × 16 cells of the tile, the mosaic pixel under them. Here the images are fetched (kept for the
6
+ * neighbouring tiles), drawn into the mosaic, and every pixel of the tile sampled from it, its source position
7
+ * interpolated within its cell (bilinear), its color between the four nearest source pixels (bilinear, alpha
8
+ * premultiplied). This code knows no projection.
9
+ */
10
+ import { Lru } from "./lru.js";
11
+ /** Source images kept decoded (by the page, or shared by the workers of a pool): the neighbours of a tile share them */
12
+ export const SOURCE_CACHE = 96;
13
+ /**
14
+ * Samples the pixels of a `size` × `size` tile (RGBA, straight alpha) from a mosaic of `width` × `height` (RGBA,
15
+ * straight alpha) through the mesh of a job. Pixels whose position falls outside the mosaic, or on a NaN, stay
16
+ * transparent; the mosaic's edge fades over half a pixel
17
+ */
18
+ export function resample(source, width, height, mesh, n, size) {
19
+ const out = new Uint8ClampedArray(size * size * 4);
20
+ const cell = size / n;
21
+ const stride = n + 1;
22
+ for (let row = 0; row < size; row += 1) {
23
+ const fy = (row + 0.5) / cell;
24
+ const j = Math.min(n - 1, Math.floor(fy));
25
+ const ty = fy - j;
26
+ for (let col = 0; col < size; col += 1) {
27
+ const fx = (col + 0.5) / cell;
28
+ const i = Math.min(n - 1, Math.floor(fx));
29
+ const tx = fx - i;
30
+ const a = 2 * (j * stride + i);
31
+ const b = a + 2 * stride;
32
+ // The corners of the cell, then the position in it
33
+ const w00 = (1 - tx) * (1 - ty);
34
+ const w10 = tx * (1 - ty);
35
+ const w01 = (1 - tx) * ty;
36
+ const w11 = tx * ty;
37
+ const u = w00 * mesh[a] +
38
+ w10 * mesh[a + 2] +
39
+ w01 * mesh[b] +
40
+ w11 * mesh[b + 2];
41
+ const v = w00 * mesh[a + 1] +
42
+ w10 * mesh[a + 3] +
43
+ w01 * mesh[b + 1] +
44
+ w11 * mesh[b + 3];
45
+ // Pixel centers at half pixels: the four around (u, v)
46
+ const sx = u - 0.5;
47
+ const sy = v - 0.5;
48
+ if (!(sx > -1 && sy > -1 && sx < width && sy < height))
49
+ continue;
50
+ const x0 = Math.floor(sx);
51
+ const y0 = Math.floor(sy);
52
+ const kx = sx - x0;
53
+ const ky = sy - y0;
54
+ let r = 0;
55
+ let g = 0;
56
+ let bl = 0;
57
+ let alpha = 0;
58
+ for (let k = 0; k < 4; k += 1) {
59
+ const x = x0 + (k & 1);
60
+ const y = y0 + (k >> 1);
61
+ if (x < 0 || y < 0 || x >= width || y >= height)
62
+ continue;
63
+ const p = 4 * (y * width + x);
64
+ const wa = (k & 1 ? kx : 1 - kx) * (k >> 1 ? ky : 1 - ky) * source[p + 3];
65
+ r += wa * source[p];
66
+ g += wa * source[p + 1];
67
+ bl += wa * source[p + 2];
68
+ alpha += wa;
69
+ }
70
+ if (alpha <= 0)
71
+ continue;
72
+ const q = 4 * (row * size + col);
73
+ out[q] = r / alpha;
74
+ out[q + 1] = g / alpha;
75
+ out[q + 2] = bl / alpha;
76
+ out[q + 3] = alpha;
77
+ }
78
+ }
79
+ return out;
80
+ }
81
+ /**
82
+ * Draws planned tiles, keeping `cached` source images decoded. A missing source image (404, 204, 410) leaves its part
83
+ * empty; a tile with no source at all is missing; other errors reject (retried by the engine). In the page, or in a
84
+ * worker of the map (`close` frees the images kept)
85
+ */
86
+ export function resampler(cached = SOURCE_CACHE) {
87
+ /** Tiles being drawn from each source image: an evicted one is closed once none is */
88
+ const users = new Map();
89
+ const evicted = new Set();
90
+ const close = (image) => {
91
+ evicted.delete(image);
92
+ image.then((bitmap) => bitmap?.close(), () => undefined);
93
+ };
94
+ /** Freed now, or once the tiles drawing from it are done */
95
+ const drop = (image) => {
96
+ if (users.has(image))
97
+ evicted.add(image);
98
+ else
99
+ close(image);
100
+ };
101
+ // Source images by URL: a request is shared by the tiles that need it, its signal is not theirs
102
+ const cache = new Lru(cached, drop);
103
+ const release = (image) => {
104
+ const count = (users.get(image) ?? 1) - 1;
105
+ if (count > 0)
106
+ users.set(image, count);
107
+ else {
108
+ users.delete(image);
109
+ if (evicted.has(image))
110
+ close(image);
111
+ }
112
+ };
113
+ const fetchImage = (url) => {
114
+ let image = cache.get(url);
115
+ if (!image) {
116
+ image = (async () => {
117
+ const response = await fetch(url);
118
+ if (response.status === 404 || response.status === 204 || response.status === 410)
119
+ return null;
120
+ if (!response.ok)
121
+ throw new Error(`isard: ${response.status} for a raster to reproject`);
122
+ return createImageBitmap(await response.blob());
123
+ })();
124
+ // A failure is not kept: the engine's retry fetches again
125
+ image.catch(() => cache.delete(url));
126
+ cache.set(url, image);
127
+ }
128
+ users.set(image, (users.get(image) ?? 0) + 1);
129
+ return image;
130
+ };
131
+ const draw = async (job, signal) => {
132
+ const pending = job.sources.map((s) => [s, fetchImage(s.url)]);
133
+ try {
134
+ const images = await Promise.all(pending.map(async ([s, image]) => [s, await image]));
135
+ signal.throwIfAborted();
136
+ if (!images.some(([, image]) => image))
137
+ return null;
138
+ const canvas = new OffscreenCanvas(job.width, job.height);
139
+ const context = canvas.getContext('2d', { willReadFrequently: true });
140
+ if (!context)
141
+ throw new Error('isard: no 2D canvas to reproject the tiles');
142
+ for (const [s, image] of images)
143
+ if (image)
144
+ context.drawImage(image, s.x, s.y, s.w, s.h);
145
+ const mosaic = context.getImageData(0, 0, job.width, job.height).data;
146
+ const pixels = resample(mosaic, job.width, job.height, job.mesh, job.n, job.size);
147
+ return createImageBitmap(new ImageData(pixels, job.size, job.size), { premultiplyAlpha: 'none' });
148
+ }
149
+ finally {
150
+ for (const [, image] of pending)
151
+ release(image);
152
+ }
153
+ };
154
+ return {
155
+ draw,
156
+ cache,
157
+ close: () => {
158
+ for (const image of cache.values())
159
+ drop(image);
160
+ cache.clear();
161
+ },
162
+ };
163
+ }
164
+ //# sourceMappingURL=resample.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resample.js","sourceRoot":"","sources":["../src/resample.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Raster reprojection, the drawing half: a Web Mercator tile drawn from the source images it spans (tiles of another\n * grid, or one image of a WMS), in a worker of the map (OffscreenCanvas) or in the page when the map has no workers.\n * The page plans each tile (`grid.ts`): which images, where they go in a mosaic, and a mesh of control points giving,\n * for the corners of 16 × 16 cells of the tile, the mosaic pixel under them. Here the images are fetched (kept for the\n * neighbouring tiles), drawn into the mosaic, and every pixel of the tile sampled from it, its source position\n * interpolated within its cell (bilinear), its color between the four nearest source pixels (bilinear, alpha\n * premultiplied). This code knows no projection.\n */\nimport { Lru } from './lru.ts';\n\n/** Source images kept decoded (by the page, or shared by the workers of a pool): the neighbours of a tile share them */\nexport const SOURCE_CACHE = 96;\n\n/** A source image and the rectangle of the mosaic it covers (pixels) */\nexport interface Placed {\n url: string;\n x: number;\n y: number;\n w: number;\n h: number;\n}\n\n/** How to draw a Mercator tile of `size` pixels: the mosaic of the source images, and the mesh of `n` × `n` cells\n * ((n + 1)² points, rows from the north, x then y): the mosaic position of each corner, NaN where the source CRS has\n * none */\nexport interface Job {\n size: number;\n width: number;\n height: number;\n sources: Placed[];\n n: number;\n mesh: Float32Array;\n}\n\n/**\n * Samples the pixels of a `size` × `size` tile (RGBA, straight alpha) from a mosaic of `width` × `height` (RGBA,\n * straight alpha) through the mesh of a job. Pixels whose position falls outside the mosaic, or on a NaN, stay\n * transparent; the mosaic's edge fades over half a pixel\n */\nexport function resample(\n source: Uint8ClampedArray,\n width: number,\n height: number,\n mesh: Float32Array,\n n: number,\n size: number,\n): Uint8ClampedArray<ArrayBuffer> {\n const out = new Uint8ClampedArray(size * size * 4);\n const cell = size / n;\n const stride = n + 1;\n for (let row = 0; row < size; row += 1) {\n const fy = (row + 0.5) / cell;\n const j = Math.min(n - 1, Math.floor(fy));\n const ty = fy - j;\n for (let col = 0; col < size; col += 1) {\n const fx = (col + 0.5) / cell;\n const i = Math.min(n - 1, Math.floor(fx));\n const tx = fx - i;\n const a = 2 * (j * stride + i);\n const b = a + 2 * stride;\n // The corners of the cell, then the position in it\n const w00 = (1 - tx) * (1 - ty);\n const w10 = tx * (1 - ty);\n const w01 = (1 - tx) * ty;\n const w11 = tx * ty;\n const u =\n w00 * (mesh[a] as number) +\n w10 * (mesh[a + 2] as number) +\n w01 * (mesh[b] as number) +\n w11 * (mesh[b + 2] as number);\n const v =\n w00 * (mesh[a + 1] as number) +\n w10 * (mesh[a + 3] as number) +\n w01 * (mesh[b + 1] as number) +\n w11 * (mesh[b + 3] as number);\n // Pixel centers at half pixels: the four around (u, v)\n const sx = u - 0.5;\n const sy = v - 0.5;\n if (!(sx > -1 && sy > -1 && sx < width && sy < height)) continue;\n const x0 = Math.floor(sx);\n const y0 = Math.floor(sy);\n const kx = sx - x0;\n const ky = sy - y0;\n let r = 0;\n let g = 0;\n let bl = 0;\n let alpha = 0;\n for (let k = 0; k < 4; k += 1) {\n const x = x0 + (k & 1);\n const y = y0 + (k >> 1);\n if (x < 0 || y < 0 || x >= width || y >= height) continue;\n const p = 4 * (y * width + x);\n const wa = (k & 1 ? kx : 1 - kx) * (k >> 1 ? ky : 1 - ky) * (source[p + 3] as number);\n r += wa * (source[p] as number);\n g += wa * (source[p + 1] as number);\n bl += wa * (source[p + 2] as number);\n alpha += wa;\n }\n if (alpha <= 0) continue;\n const q = 4 * (row * size + col);\n out[q] = r / alpha;\n out[q + 1] = g / alpha;\n out[q + 2] = bl / alpha;\n out[q + 3] = alpha;\n }\n }\n return out;\n}\n\n/** Draws a planned tile: its image, null where the source has nothing; rejects on errors */\nexport type DrawJob = (job: Job, signal: AbortSignal) => Promise<ImageBitmap | null>;\n\n/**\n * Draws planned tiles, keeping `cached` source images decoded. A missing source image (404, 204, 410) leaves its part\n * empty; a tile with no source at all is missing; other errors reject (retried by the engine). In the page, or in a\n * worker of the map (`close` frees the images kept)\n */\nexport function resampler(cached = SOURCE_CACHE): { draw: DrawJob; close: () => void; cache: { capacity: number } } {\n type Image = Promise<ImageBitmap | null>;\n /** Tiles being drawn from each source image: an evicted one is closed once none is */\n const users = new Map<Image, number>();\n const evicted = new Set<Image>();\n const close = (image: Image) => {\n evicted.delete(image);\n image.then(\n (bitmap) => bitmap?.close(),\n () => undefined,\n );\n };\n /** Freed now, or once the tiles drawing from it are done */\n const drop = (image: Image) => {\n if (users.has(image)) evicted.add(image);\n else close(image);\n };\n // Source images by URL: a request is shared by the tiles that need it, its signal is not theirs\n const cache = new Lru<string, Image>(cached, drop);\n const release = (image: Image) => {\n const count = (users.get(image) ?? 1) - 1;\n if (count > 0) users.set(image, count);\n else {\n users.delete(image);\n if (evicted.has(image)) close(image);\n }\n };\n const fetchImage = (url: string): Image => {\n let image = cache.get(url);\n if (!image) {\n image = (async () => {\n const response = await fetch(url);\n if (response.status === 404 || response.status === 204 || response.status === 410) return null;\n if (!response.ok) throw new Error(`isard: ${response.status} for a raster to reproject`);\n return createImageBitmap(await response.blob());\n })();\n // A failure is not kept: the engine's retry fetches again\n image.catch(() => cache.delete(url));\n cache.set(url, image);\n }\n users.set(image, (users.get(image) ?? 0) + 1);\n return image;\n };\n const draw: DrawJob = async (job, signal) => {\n const pending = job.sources.map((s) => [s, fetchImage(s.url)] as const);\n try {\n const images = await Promise.all(pending.map(async ([s, image]) => [s, await image] as const));\n signal.throwIfAborted();\n if (!images.some(([, image]) => image)) return null;\n const canvas = new OffscreenCanvas(job.width, job.height);\n const context = canvas.getContext('2d', { willReadFrequently: true });\n if (!context) throw new Error('isard: no 2D canvas to reproject the tiles');\n for (const [s, image] of images) if (image) context.drawImage(image, s.x, s.y, s.w, s.h);\n const mosaic = context.getImageData(0, 0, job.width, job.height).data;\n const pixels = resample(mosaic, job.width, job.height, job.mesh, job.n, job.size);\n return createImageBitmap(new ImageData(pixels, job.size, job.size), { premultiplyAlpha: 'none' });\n } finally {\n for (const [, image] of pending) release(image);\n }\n };\n return {\n draw,\n cache,\n close: () => {\n for (const image of cache.values()) drop(image);\n cache.clear();\n },\n };\n}\n"]}
@@ -0,0 +1,39 @@
1
+ import type { PngEncoding } from './terrain.ts';
2
+ import type { AtmosphereOptions, BodyOptions, CelestialBody, LoadingMode, TerrainModel, TerrainOptions } from './types.ts';
3
+ /** Decodes the relief tiles of a URL for the engine: 256 × 256 altitudes, null where there is no tile */
4
+ export type TerrainLoader = (z: number, x: number, y: number, signal: AbortSignal) => Promise<Float32Array | null>;
5
+ /** Settings replayed on a new engine after a WebGL context loss */
6
+ export interface Settings {
7
+ body: Required<BodyOptions>;
8
+ zoomRange: [min: number, max: number];
9
+ maxPitch: number;
10
+ globe: boolean;
11
+ sky: boolean;
12
+ atmosphere: Float32Array | undefined;
13
+ terrain: {
14
+ url: string;
15
+ exaggeration: number;
16
+ maxZoom: number;
17
+ geoid: boolean;
18
+ /** Decodes the tiles of `url` for the engine (an encoding other than raw floats) */
19
+ loader?: TerrainLoader;
20
+ /** Credits of the model */
21
+ attribution?: string;
22
+ } | undefined;
23
+ loading: LoadingMode;
24
+ background: [r: number, g: number, b: number];
25
+ globeColor: [r: number, g: number, b: number];
26
+ }
27
+ /** A body from the option, its defaults filled in */
28
+ export declare function bodySettings(body: CelestialBody | undefined): Required<BodyOptions>;
29
+ /** The halo of an atmosphere for the engine (RGB 0-1 and its strength, the color's alpha): undefined for none */
30
+ export declare function haloSettings(atmosphere: boolean | string): Float32Array | undefined;
31
+ /** Relief settings from the option: undefined when off. On the Earth the Terrain Tiles by default (worldwide,
32
+ * Terrarium PNGs decoded by `terrainLoader`) or the IGN terrain model (France, raised by the geoid); NASA's on the Moon
33
+ * and Mars; an ArcGIS image service asked for in the body's Mercator. `terrainLoader`: the loader of PNG tiles in an
34
+ * encoding, the same for the same tiles (the engine keeps its tiles when only the exaggeration changes) */
35
+ export declare function terrainSettings(terrain: boolean | TerrainModel | TerrainOptions | undefined, body: CelestialBody | undefined, radius: number, terrainLoader: (url: string, encoding: PngEncoding, floor?: number) => TerrainLoader): Settings['terrain'];
36
+ /** Sky settings for the engine: colors (RGB 0-1) and blends, MapLibre's defaults */
37
+ export declare function atmosphereSettings(options: AtmosphereOptions | undefined): Float32Array | undefined;
38
+ /** A CSS color to RGB 0-1 (its alpha dropped); black if unknown */
39
+ export declare function rgb(color: string): [r: number, g: number, b: number];
@@ -0,0 +1,104 @@
1
+ /**
2
+ * The settings of a map's engine from its options: the body, its halo, its relief, the sky. Kept by the map and
3
+ * replayed on a new engine (after a context loss, or on the engine with 3D Tiles).
4
+ */
5
+ import { BODY_TERRAINS, IGN_ATTRIBUTION, IMAGE_SERVER, imageServerTerrain, TERRAIN_TILES, TERRAIN_TILES_ATTRIBUTION, TERRAIN_URL, } from "./basemaps.js";
6
+ import { parseCssColor } from "./color.js";
7
+ /** The named bodies; a body's `color` is that of its globe under the tiles (the Earth's follows its basemap), its
8
+ * `relief` the range of its ground (the Earth's dry land, from the Dead Sea to Everest). Mars: the sphere of its
9
+ * equatorial radius (its data take planetocentric latitudes), a faint butterscotch halo of dust */
10
+ const BODIES = {
11
+ earth: { radius: 6378137, flattening: 1 / 298.257223563, atmosphere: true, color: '#cacaca', relief: [-450, 8900] },
12
+ moon: { radius: 1737400, flattening: 0, atmosphere: false, color: '#77756f', relief: [-9200, 10800] },
13
+ mars: {
14
+ radius: 3396190,
15
+ flattening: 0,
16
+ atmosphere: 'rgba(232, 168, 120, 0.6)',
17
+ color: '#b5714a',
18
+ relief: [-8300, 21300],
19
+ },
20
+ };
21
+ /** The Earth's halo, RGB 0-1 and its strength: what `atmosphere: true` draws */
22
+ const EARTH_HALO = Float32Array.of(0.42, 0.66, 1, 1);
23
+ /** A body from the option, its defaults filled in */
24
+ export function bodySettings(body) {
25
+ if (body === undefined || typeof body === 'string')
26
+ return BODIES[body ?? 'earth'];
27
+ const relief = [-0.01 * body.radius, 0.01 * body.radius];
28
+ return { flattening: 0, atmosphere: false, color: '#808080', relief, ...body };
29
+ }
30
+ /** The halo of an atmosphere for the engine (RGB 0-1 and its strength, the color's alpha): undefined for none */
31
+ export function haloSettings(atmosphere) {
32
+ if (atmosphere === true)
33
+ return EARTH_HALO;
34
+ const color = atmosphere === false ? undefined : parseCssColor(atmosphere);
35
+ return color && Float32Array.from(color);
36
+ }
37
+ /** Relief settings from the option: undefined when off. On the Earth the Terrain Tiles by default (worldwide,
38
+ * Terrarium PNGs decoded by `terrainLoader`) or the IGN terrain model (France, raised by the geoid); NASA's on the Moon
39
+ * and Mars; an ArcGIS image service asked for in the body's Mercator. `terrainLoader`: the loader of PNG tiles in an
40
+ * encoding, the same for the same tiles (the engine keeps its tiles when only the exaggeration changes) */
41
+ export function terrainSettings(terrain, body, radius, terrainLoader) {
42
+ if (!terrain)
43
+ return undefined;
44
+ let options = {};
45
+ if (typeof terrain === 'string')
46
+ options = { model: terrain };
47
+ else if (terrain !== true)
48
+ options = terrain;
49
+ const earth = (body ?? 'earth') === 'earth';
50
+ const exaggeration = options.exaggeration ?? 1;
51
+ if (options.url === undefined && earth) {
52
+ if (options.model === 'ign') {
53
+ const geoid = options.geoid ?? true;
54
+ return { url: TERRAIN_URL, exaggeration, maxZoom: options.maxZoom ?? 15, geoid, attribution: IGN_ATTRIBUTION };
55
+ }
56
+ return {
57
+ url: TERRAIN_TILES.url,
58
+ exaggeration,
59
+ maxZoom: options.maxZoom ?? TERRAIN_TILES.maxZoom,
60
+ geoid: options.geoid ?? false,
61
+ // The sea floor of the model at sea level: the oceans stay flat under the basemap's water
62
+ loader: terrainLoader(TERRAIN_TILES.url, 'terrarium', 0),
63
+ attribution: TERRAIN_TILES_ATTRIBUTION,
64
+ };
65
+ }
66
+ const preset = body === 'moon' || body === 'mars' ? BODY_TERRAINS[body] : undefined;
67
+ const url = options.url ?? preset?.url;
68
+ if (url === undefined)
69
+ throw new Error('isard: the relief of this body needs a url');
70
+ const encoding = options.encoding ?? 'float32';
71
+ // PNG encodings are fetched tile by tile: a {bbox} or an image service would be fetched as it is
72
+ if (encoding !== 'float32' &&
73
+ (IMAGE_SERVER.test(url) || !/\{z\}/.test(url) || !/\{x\}/.test(url) || !/\{y\}/.test(url))) {
74
+ throw new Error(`isard: the '${encoding}' terrain encoding takes a tile URL with {z}, {x} and {y} (${url})`);
75
+ }
76
+ return {
77
+ url: IMAGE_SERVER.test(url) ? imageServerTerrain(url, earth, radius) : url,
78
+ exaggeration,
79
+ maxZoom: options.maxZoom ?? (options.url === undefined && preset ? preset.maxZoom : 15),
80
+ geoid: options.geoid ?? false,
81
+ ...(encoding === 'float32' ? {} : { loader: terrainLoader(url, encoding) }),
82
+ // NASA's credits for its models
83
+ ...(options.url === undefined && preset ? { attribution: preset.attribution } : {}),
84
+ };
85
+ }
86
+ /** Sky settings for the engine: colors (RGB 0-1) and blends, MapLibre's defaults */
87
+ export function atmosphereSettings(options) {
88
+ if (!options)
89
+ return undefined;
90
+ return Float32Array.from([
91
+ ...rgb(options.skyColor ?? '#9cc7ec'),
92
+ ...rgb(options.horizonColor ?? '#e8f1f8'),
93
+ ...rgb(options.fogColor ?? '#f2f5f8'),
94
+ options.skyHorizonBlend ?? 0.6,
95
+ options.horizonFogBlend ?? 0.7,
96
+ options.fogGroundBlend ?? 0.4,
97
+ ]);
98
+ }
99
+ /** A CSS color to RGB 0-1 (its alpha dropped); black if unknown */
100
+ export function rgb(color) {
101
+ const [r, g, b] = parseCssColor(color) ?? [0, 0, 0, 1];
102
+ return [r, g, b];
103
+ }
104
+ //# sourceMappingURL=settings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"settings.js","sourceRoot":"","sources":["../src/settings.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The settings of a map's engine from its options: the body, its halo, its relief, the sky. Kept by the map and\n * replayed on a new engine (after a context loss, or on the engine with 3D Tiles).\n */\nimport {\n BODY_TERRAINS,\n IGN_ATTRIBUTION,\n IMAGE_SERVER,\n imageServerTerrain,\n TERRAIN_TILES,\n TERRAIN_TILES_ATTRIBUTION,\n TERRAIN_URL,\n} from './basemaps.ts';\nimport { parseCssColor } from './color.ts';\nimport type { PngEncoding } from './terrain.ts';\nimport type {\n AtmosphereOptions,\n BodyOptions,\n CelestialBody,\n LoadingMode,\n TerrainModel,\n TerrainOptions,\n} from './types.ts';\n\n/** Decodes the relief tiles of a URL for the engine: 256 × 256 altitudes, null where there is no tile */\nexport type TerrainLoader = (z: number, x: number, y: number, signal: AbortSignal) => Promise<Float32Array | null>;\n\n/** Settings replayed on a new engine after a WebGL context loss */\nexport interface Settings {\n body: Required<BodyOptions>;\n zoomRange: [min: number, max: number];\n maxPitch: number;\n globe: boolean;\n sky: boolean;\n atmosphere: Float32Array | undefined;\n terrain:\n | {\n url: string;\n exaggeration: number;\n maxZoom: number;\n geoid: boolean;\n /** Decodes the tiles of `url` for the engine (an encoding other than raw floats) */\n loader?: TerrainLoader;\n /** Credits of the model */\n attribution?: string;\n }\n | undefined;\n loading: LoadingMode;\n background: [r: number, g: number, b: number];\n globeColor: [r: number, g: number, b: number];\n}\n\n/** The named bodies; a body's `color` is that of its globe under the tiles (the Earth's follows its basemap), its\n * `relief` the range of its ground (the Earth's dry land, from the Dead Sea to Everest). Mars: the sphere of its\n * equatorial radius (its data take planetocentric latitudes), a faint butterscotch halo of dust */\nconst BODIES: Record<Exclude<CelestialBody, BodyOptions>, Required<BodyOptions>> = {\n earth: { radius: 6378137, flattening: 1 / 298.257223563, atmosphere: true, color: '#cacaca', relief: [-450, 8900] },\n moon: { radius: 1737400, flattening: 0, atmosphere: false, color: '#77756f', relief: [-9200, 10800] },\n mars: {\n radius: 3396190,\n flattening: 0,\n atmosphere: 'rgba(232, 168, 120, 0.6)',\n color: '#b5714a',\n relief: [-8300, 21300],\n },\n};\n\n/** The Earth's halo, RGB 0-1 and its strength: what `atmosphere: true` draws */\nconst EARTH_HALO = Float32Array.of(0.42, 0.66, 1, 1);\n\n/** A body from the option, its defaults filled in */\nexport function bodySettings(body: CelestialBody | undefined): Required<BodyOptions> {\n if (body === undefined || typeof body === 'string') return BODIES[body ?? 'earth'];\n const relief: [number, number] = [-0.01 * body.radius, 0.01 * body.radius];\n return { flattening: 0, atmosphere: false, color: '#808080', relief, ...body };\n}\n\n/** The halo of an atmosphere for the engine (RGB 0-1 and its strength, the color's alpha): undefined for none */\nexport function haloSettings(atmosphere: boolean | string): Float32Array | undefined {\n if (atmosphere === true) return EARTH_HALO;\n const color = atmosphere === false ? undefined : parseCssColor(atmosphere);\n return color && Float32Array.from(color);\n}\n\n/** Relief settings from the option: undefined when off. On the Earth the Terrain Tiles by default (worldwide,\n * Terrarium PNGs decoded by `terrainLoader`) or the IGN terrain model (France, raised by the geoid); NASA's on the Moon\n * and Mars; an ArcGIS image service asked for in the body's Mercator. `terrainLoader`: the loader of PNG tiles in an\n * encoding, the same for the same tiles (the engine keeps its tiles when only the exaggeration changes) */\nexport function terrainSettings(\n terrain: boolean | TerrainModel | TerrainOptions | undefined,\n body: CelestialBody | undefined,\n radius: number,\n terrainLoader: (url: string, encoding: PngEncoding, floor?: number) => TerrainLoader,\n): Settings['terrain'] {\n if (!terrain) return undefined;\n let options: TerrainOptions = {};\n if (typeof terrain === 'string') options = { model: terrain };\n else if (terrain !== true) options = terrain;\n const earth = (body ?? 'earth') === 'earth';\n const exaggeration = options.exaggeration ?? 1;\n if (options.url === undefined && earth) {\n if (options.model === 'ign') {\n const geoid = options.geoid ?? true;\n return { url: TERRAIN_URL, exaggeration, maxZoom: options.maxZoom ?? 15, geoid, attribution: IGN_ATTRIBUTION };\n }\n return {\n url: TERRAIN_TILES.url,\n exaggeration,\n maxZoom: options.maxZoom ?? TERRAIN_TILES.maxZoom,\n geoid: options.geoid ?? false,\n // The sea floor of the model at sea level: the oceans stay flat under the basemap's water\n loader: terrainLoader(TERRAIN_TILES.url, 'terrarium', 0),\n attribution: TERRAIN_TILES_ATTRIBUTION,\n };\n }\n const preset = body === 'moon' || body === 'mars' ? BODY_TERRAINS[body] : undefined;\n const url = options.url ?? preset?.url;\n if (url === undefined) throw new Error('isard: the relief of this body needs a url');\n const encoding = options.encoding ?? 'float32';\n // PNG encodings are fetched tile by tile: a {bbox} or an image service would be fetched as it is\n if (\n encoding !== 'float32' &&\n (IMAGE_SERVER.test(url) || !/\\{z\\}/.test(url) || !/\\{x\\}/.test(url) || !/\\{y\\}/.test(url))\n ) {\n throw new Error(`isard: the '${encoding}' terrain encoding takes a tile URL with {z}, {x} and {y} (${url})`);\n }\n return {\n url: IMAGE_SERVER.test(url) ? imageServerTerrain(url, earth, radius) : url,\n exaggeration,\n maxZoom: options.maxZoom ?? (options.url === undefined && preset ? preset.maxZoom : 15),\n geoid: options.geoid ?? false,\n ...(encoding === 'float32' ? {} : { loader: terrainLoader(url, encoding) }),\n // NASA's credits for its models\n ...(options.url === undefined && preset ? { attribution: preset.attribution } : {}),\n };\n}\n\n/** Sky settings for the engine: colors (RGB 0-1) and blends, MapLibre's defaults */\nexport function atmosphereSettings(options: AtmosphereOptions | undefined): Float32Array | undefined {\n if (!options) return undefined;\n return Float32Array.from([\n ...rgb(options.skyColor ?? '#9cc7ec'),\n ...rgb(options.horizonColor ?? '#e8f1f8'),\n ...rgb(options.fogColor ?? '#f2f5f8'),\n options.skyHorizonBlend ?? 0.6,\n options.horizonFogBlend ?? 0.7,\n options.fogGroundBlend ?? 0.4,\n ]);\n}\n\n/** A CSS color to RGB 0-1 (its alpha dropped); black if unknown */\nexport function rgb(color: string): [r: number, g: number, b: number] {\n const [r, g, b] = parseCssColor(color) ?? [0, 0, 0, 1];\n return [r, g, b];\n}\n"]}
package/dist/symbols.d.ts CHANGED
@@ -44,16 +44,27 @@ export declare class SymbolLayer<P = Properties> extends Layer {
44
44
  private readonly host;
45
45
  private style;
46
46
  private list;
47
+ /** The features in groups that do not change (`setGroups`): each group's labels kept between updates */
48
+ private groups;
49
+ private readonly chunks;
47
50
  /** Bumped by each change: a shaping that finished after a newer one began is dropped */
48
51
  private generation;
52
+ /** Bumped by each change of style: the labels kept are shaped again */
53
+ private styleGeneration;
54
+ /** No style function takes the index of a feature: a group's labels do not depend on its place */
55
+ private indexFree;
49
56
  private labels;
50
57
  get features(): readonly Feature<P>[];
51
58
  setData(data: FeatureData<P>): void;
52
59
  setStyle(style: Partial<SymbolStyle<P>>): void;
53
60
  setFilter(filter: ((properties: P, index: number) => boolean) | undefined): void;
54
61
  setVisible(visible: boolean): void;
55
- /** Shapes the labels (loading their glyphs first) and sends them */
62
+ private styled;
63
+ /** The runs of text of each feature of a group (none for a feature filtered out) */
64
+ private parts;
65
+ /** Shapes the labels (loading their glyphs first) and sends them; the groups' labels still valid are kept */
56
66
  private update;
67
+ /** The labels of a group of features, `base` the index of its first feature */
57
68
  private build;
58
69
  private send;
59
70
  private sendLayer;
package/dist/symbols.js CHANGED
@@ -4,6 +4,7 @@
4
4
  * drawn with a halo, standing on the relief.
5
5
  */
6
6
  import { evaluate, featuresOf, parseColor, stops, } from "./features.js";
7
+ import { toMercator } from "./geo.js";
7
8
  import { codes, DEFAULT_FONT, shape } from "./glyphs.js";
8
9
  import { Layer } from "./layers.js";
9
10
  /** Where a geometry is labelled: its points, the middle of its longest line (with the direction of the line there),
@@ -65,17 +66,60 @@ function points(geometry) {
65
66
  return geometry.geometries.flatMap(points);
66
67
  }
67
68
  }
68
- const MAX_LAT = 85.051128779806589;
69
- const merc = ([lon, lat]) => {
70
- const phi = (Math.max(-MAX_LAT, Math.min(MAX_LAT, lat)) * Math.PI) / 180;
71
- return [(lon + 180) / 360, 0.5 - Math.log(Math.tan(Math.PI / 4 + phi / 2)) / (2 * Math.PI)];
72
- };
69
+ /** Numbers per glyph quad: its label's index, its place, its atlas coordinates, its scale */
70
+ const QUAD = 10;
71
+ /** Every character of a run has its glyph */
72
+ function present(part, loaded) {
73
+ const glyphs = loaded.get(part.font ?? DEFAULT_FONT);
74
+ if (!glyphs)
75
+ return false;
76
+ for (const ch of part.text)
77
+ if (ch !== '\n' && !glyphs.has(ch.codePointAt(0) ?? 32))
78
+ return false;
79
+ return true;
80
+ }
81
+ /** The labels of chunks one after the other, the quads' label indices moved past those of the chunks before */
82
+ function concat(chunks) {
83
+ if (chunks.length === 1 && chunks[0])
84
+ return chunks[0];
85
+ const length = (key) => chunks.reduce((n, c) => n + c[key].length, 0);
86
+ const out = {
87
+ anchors: new Float64Array(length('anchors')),
88
+ boxes: new Float32Array(length('boxes')),
89
+ styles: new Float32Array(length('styles')),
90
+ quads: new Float32Array(length('quads')),
91
+ };
92
+ const at = { anchors: 0, boxes: 0, styles: 0, quads: 0 };
93
+ let labels = 0;
94
+ for (const c of chunks) {
95
+ out.anchors.set(c.anchors, at.anchors);
96
+ out.boxes.set(c.boxes, at.boxes);
97
+ out.styles.set(c.styles, at.styles);
98
+ out.quads.set(c.quads, at.quads);
99
+ if (labels > 0)
100
+ for (let q = at.quads; q < at.quads + c.quads.length; q += QUAD)
101
+ out.quads[q] = (out.quads[q] ?? 0) + labels;
102
+ at.anchors += c.anchors.length;
103
+ at.boxes += c.boxes.length;
104
+ at.styles += c.styles.length;
105
+ at.quads += c.quads.length;
106
+ labels += c.count;
107
+ }
108
+ return out;
109
+ }
73
110
  export class SymbolLayer extends Layer {
74
111
  host;
75
112
  style;
76
113
  list;
114
+ /** The features in groups that do not change (`setGroups`): each group's labels kept between updates */
115
+ groups;
116
+ chunks = new WeakMap();
77
117
  /** Bumped by each change: a shaping that finished after a newer one began is dropped */
78
118
  generation = 0;
119
+ /** Bumped by each change of style: the labels kept are shaped again */
120
+ styleGeneration = 0;
121
+ /** No style function takes the index of a feature: a group's labels do not depend on its place */
122
+ indexFree = true;
79
123
  labels;
80
124
  /** @internal */
81
125
  constructor(host, data, style) {
@@ -83,16 +127,28 @@ export class SymbolLayer extends Layer {
83
127
  this.host = host;
84
128
  this.style = style;
85
129
  this.list = featuresOf(data);
130
+ this.styled();
86
131
  }
87
132
  get features() {
88
133
  return this.list;
89
134
  }
90
135
  setData(data) {
91
136
  this.list = featuresOf(data);
137
+ this.groups = undefined;
138
+ void this.update();
139
+ }
140
+ /**
141
+ * @internal The features as groups that are never changed once given (the labels of a vector tile and style layer),
142
+ * in order: the labels of a group seen before are not shaped again
143
+ */
144
+ setGroups(groups) {
145
+ this.groups = groups;
146
+ this.list = groups.flat();
92
147
  void this.update();
93
148
  }
94
149
  setStyle(style) {
95
150
  this.style = { ...this.style, ...style };
151
+ this.styled();
96
152
  void this.update();
97
153
  }
98
154
  setFilter(filter) {
@@ -103,11 +159,16 @@ export class SymbolLayer extends Layer {
103
159
  this.sendLayer(this.map.engine, this.id);
104
160
  this.map.requestRender();
105
161
  }
106
- /** Shapes the labels (loading their glyphs first) and sends them */
107
- async update() {
108
- const generation = ++this.generation;
162
+ styled() {
163
+ this.styleGeneration += 1;
164
+ // A function of one parameter reads the properties only
165
+ this.indexFree = Object.values(this.style).every((v) => typeof v !== 'function' || v.length < 2);
166
+ }
167
+ /** The runs of text of each feature of a group (none for a feature filtered out) */
168
+ parts(group, base) {
109
169
  const { text, font, filter } = this.style;
110
- const parts = this.list.map((f, i) => {
170
+ return group.map((f, k) => {
171
+ const i = base + k;
111
172
  if (filter && !filter(f.properties, i))
112
173
  return [];
113
174
  const t = evaluate(text, f.properties, i);
@@ -115,35 +176,69 @@ export class SymbolLayer extends Layer {
115
176
  // Each run in its font, the feature's by default
116
177
  return (typeof t === 'string' ? [{ text: t }] : t).map((p) => ({ ...p, font: p.font ?? own }));
117
178
  });
179
+ }
180
+ /** Shapes the labels (loading their glyphs first) and sends them; the groups' labels still valid are kept */
181
+ async update() {
182
+ const generation = ++this.generation;
183
+ const groups = this.groups ?? [this.list];
184
+ const keep = this.groups !== undefined;
185
+ const glyphs = this.host.glyphs;
186
+ const todo = [];
187
+ let base = 0;
188
+ for (const group of groups) {
189
+ const c = keep ? this.chunks.get(group) : undefined;
190
+ const valid = c !== undefined &&
191
+ c.style === this.styleGeneration &&
192
+ (c.base === base || this.indexFree) &&
193
+ (c.complete || c.glyphs === glyphs.version);
194
+ if (!valid)
195
+ todo.push({ group, base, parts: this.parts(group, base) });
196
+ base += group.length;
197
+ }
118
198
  const byFont = new Map();
119
- for (const p of parts.flat()) {
120
- const key = p.font ?? DEFAULT_FONT;
121
- const set = byFont.get(key) ?? new Set();
122
- for (const code of codes([p]))
123
- set.add(code);
124
- byFont.set(key, set);
199
+ for (const { parts } of todo) {
200
+ for (const p of parts.flat()) {
201
+ const key = p.font ?? DEFAULT_FONT;
202
+ const set = byFont.get(key) ?? new Set();
203
+ for (const code of codes([p]))
204
+ set.add(code);
205
+ byFont.set(key, set);
206
+ }
125
207
  }
126
208
  const loaded = new Map();
127
209
  await Promise.all([...byFont].map(async ([f, set]) => {
128
- loaded.set(f, await this.host.glyphs.load(f, set));
210
+ loaded.set(f, await glyphs.load(f, set));
129
211
  }));
130
212
  if (generation !== this.generation)
131
213
  return;
132
- this.labels = this.build(parts, loaded);
214
+ const built = new Map();
215
+ for (const { group, base: b, parts } of todo) {
216
+ const chunk = this.build(group, b, parts, loaded);
217
+ built.set(group, chunk);
218
+ if (keep)
219
+ this.chunks.set(group, chunk);
220
+ }
221
+ this.labels = concat(groups.map((g) => built.get(g) ?? this.chunks.get(g)));
133
222
  this.send(this.map.engine, this.id);
134
223
  this.map.requestRender();
135
224
  }
136
- build(parts, loaded) {
225
+ /** The labels of a group of features, `base` the index of its first feature */
226
+ build(group, base, parts, loaded) {
137
227
  const { font, size, anchor, offset, maxWidth, lineHeight, color, haloColor, haloWidth, overlap, placement } = this.style;
138
228
  const anchorList = [];
139
229
  const boxes = [];
140
230
  const styles = [];
141
231
  const quads = [];
142
- const feature = [];
143
- this.list.forEach((f, i) => {
144
- const text = parts[i];
232
+ let count = 0;
233
+ let complete = true;
234
+ group.forEach((f, k) => {
235
+ const i = base + k;
236
+ const text = parts[k];
145
237
  if (!text?.length || text.every((p) => !p.text))
146
238
  return;
239
+ // Glyphs still missing: shaped again once more glyphs are loaded
240
+ if (complete && !text.every((p) => present(p, loaded)))
241
+ complete = false;
147
242
  // Shaped at its largest size; the engine scales by the zoom stops
148
243
  const s0 = evaluate(size, f.properties, i);
149
244
  const shaped = typeof s0 === 'number' ? s0 : Math.max(...s0.map(([, v]) => v), 1);
@@ -162,14 +257,13 @@ export class SymbolLayer extends Layer {
162
257
  const fill = parseColor(evaluate(color, f.properties, i));
163
258
  const haloRgba = parseColor(evaluate(haloColor, f.properties, i));
164
259
  for (const { at, along } of anchors(f.geometry)) {
165
- const label = feature.length;
166
- feature.push(i);
167
- const [x, y] = merc(at);
260
+ const label = count++;
261
+ const [x, y] = toMercator(at);
168
262
  anchorList.push(x, y);
169
263
  // Along the line: its direction in Mercator (y towards the south)
170
264
  let direction = [0, 0];
171
265
  if (placement === 'line' && along) {
172
- const [x1, y1] = merc([at[0] + along[0], at[1] + along[1]]);
266
+ const [x1, y1] = toMercator([at[0] + along[0], at[1] + along[1]]);
173
267
  direction = [x1 - x, y1 - y];
174
268
  }
175
269
  // The collision box: the ink, with the halo and a little room
@@ -185,7 +279,11 @@ export class SymbolLayer extends Layer {
185
279
  boxes: Float32Array.from(boxes),
186
280
  styles: Float32Array.from(styles),
187
281
  quads: Float32Array.from(quads),
188
- feature,
282
+ count,
283
+ style: this.styleGeneration,
284
+ base,
285
+ glyphs: this.host.glyphs.version,
286
+ complete,
189
287
  };
190
288
  }
191
289
  send(engine, id) {