isard 0.0.0-stage → 0.2.1
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 +176 -0
- package/LICENSE +21 -0
- package/README.md +150 -2
- package/dist/basemap.worker.d.ts +76 -0
- package/dist/basemap.worker.js +4 -0
- package/dist/basemap.worker.js.map +13 -0
- package/dist/basemaps.d.ts +48 -0
- package/dist/basemaps.js +97 -0
- package/dist/basemaps.js.map +1 -0
- package/dist/color.d.ts +8 -0
- package/dist/color.js +98 -0
- package/dist/color.js.map +1 -0
- package/dist/controls.d.ts +43 -0
- package/dist/controls.js +115 -0
- package/dist/controls.js.map +1 -0
- package/dist/decoder.worker.js +6 -0
- package/dist/decoder.worker.js.map +12 -0
- package/dist/decoders.d.ts +27 -0
- package/dist/decoders.js +109 -0
- package/dist/decoders.js.map +1 -0
- package/dist/equirectangular.d.ts +35 -0
- package/dist/equirectangular.js +143 -0
- package/dist/equirectangular.js.map +1 -0
- package/dist/extrusions.d.ts +79 -0
- package/dist/extrusions.js +224 -0
- package/dist/extrusions.js.map +1 -0
- package/dist/features.d.ts +159 -0
- package/dist/features.js +387 -0
- package/dist/features.js.map +1 -0
- package/dist/geoplateforme.d.ts +35 -0
- package/dist/geoplateforme.js +56 -0
- package/dist/geoplateforme.js.map +1 -0
- package/dist/glyphs.d.ts +104 -0
- package/dist/glyphs.js +383 -0
- package/dist/glyphs.js.map +1 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/interaction.d.ts +53 -0
- package/dist/interaction.js +259 -0
- package/dist/interaction.js.map +1 -0
- package/dist/isard_bg.wasm +0 -0
- package/dist/isard_core_bg.wasm +0 -0
- package/dist/layers.d.ts +103 -0
- package/dist/layers.js +290 -0
- package/dist/layers.js.map +1 -0
- package/dist/loader.d.ts +30 -0
- package/dist/loader.js +102 -0
- package/dist/loader.js.map +1 -0
- package/dist/map.d.ts +211 -0
- package/dist/map.js +1222 -0
- package/dist/map.js.map +1 -0
- package/dist/mvt.d.ts +19 -0
- package/dist/mvt.js +231 -0
- package/dist/mvt.js.map +1 -0
- package/dist/overlay.d.ts +118 -0
- package/dist/overlay.js +334 -0
- package/dist/overlay.js.map +1 -0
- package/dist/protocol.d.ts +30 -0
- package/dist/protocol.js +2 -0
- package/dist/protocol.js.map +1 -0
- package/dist/style.d.ts +7 -0
- package/dist/style.js +130 -0
- package/dist/style.js.map +1 -0
- package/dist/symbols.d.ts +61 -0
- package/dist/symbols.js +238 -0
- package/dist/symbols.js.map +1 -0
- package/dist/terrain.d.ts +14 -0
- package/dist/terrain.js +56 -0
- package/dist/terrain.js.map +1 -0
- package/dist/types.d.ts +312 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/vectorstyle.d.ts +83 -0
- package/dist/vectorstyle.js +518 -0
- package/dist/vectorstyle.js.map +1 -0
- package/dist/vectortiles.d.ts +124 -0
- package/dist/vectortiles.js +523 -0
- package/dist/vectortiles.js.map +1 -0
- package/dist/wasm/isard.d.ts +612 -0
- package/dist/wasm/isard.js +2623 -0
- package/dist/wasm/isard_core.d.ts +469 -0
- package/dist/wasm/isard_core.js +2385 -0
- package/package.json +49 -4
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Equirectangular tiles (longitudes and latitudes, two 256 px tiles side by side at level 0, rows from the north)
|
|
3
|
+
* reprojected into the Web Mercator tiles the engine draws: a raster loader that fetches the source tiles a Mercator
|
|
4
|
+
* tile spans and draws them into it, one row of pixels at a time (along a row the latitude is constant, so each row is
|
|
5
|
+
* a horizontal strip of the source, scaled). The planetary services (NASA's Trek, USGS) serve this grid only.
|
|
6
|
+
*/
|
|
7
|
+
/** One strip drawn: from source tile (`tx`, `ty`) at the plan's level, the source rectangle (`sx`, `sy`, `sw`, one
|
|
8
|
+
* row high) to the destination rectangle (`dx`, `dy`, `dw`, one row high) */
|
|
9
|
+
export interface Strip {
|
|
10
|
+
tx: number;
|
|
11
|
+
ty: number;
|
|
12
|
+
sx: number;
|
|
13
|
+
sy: number;
|
|
14
|
+
sw: number;
|
|
15
|
+
dx: number;
|
|
16
|
+
dy: number;
|
|
17
|
+
dw: number;
|
|
18
|
+
}
|
|
19
|
+
/** Where the pixels of a Mercator tile come from: the source level, and a strip per row and source column */
|
|
20
|
+
export interface Plan {
|
|
21
|
+
level: number;
|
|
22
|
+
strips: Strip[];
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* The strips filling Mercator tile `z`/`x`/`y` (`size` pixels a side) from equirectangular tiles of levels `minLevel`
|
|
26
|
+
* to `maxLevel`. The source level has the same pixel size along the parallels (one less than `z`: the source is twice
|
|
27
|
+
* as wide at level 0), finer towards the poles, where the Mercator pixels shrink along the meridians.
|
|
28
|
+
*/
|
|
29
|
+
export declare function plan(z: number, x: number, y: number, size: number, maxLevel: number, minLevel?: number): Plan;
|
|
30
|
+
/**
|
|
31
|
+
* A raster loader drawing Web Mercator tiles of `size` pixels from the equirectangular tiles of `url` ({z}, {x}, {y};
|
|
32
|
+
* levels `minLevel` to `maxLevel`). A missing source tile (404) leaves its part empty; a tile with no source at all is
|
|
33
|
+
* missing; other errors reject (retried by the engine).
|
|
34
|
+
*/
|
|
35
|
+
export declare function equirectangularLoader(url: string, maxLevel: number, size: number, minLevel?: number): (z: number, x: number, y: number, signal: AbortSignal) => Promise<ImageBitmap | null>;
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Equirectangular tiles (longitudes and latitudes, two 256 px tiles side by side at level 0, rows from the north)
|
|
3
|
+
* reprojected into the Web Mercator tiles the engine draws: a raster loader that fetches the source tiles a Mercator
|
|
4
|
+
* tile spans and draws them into it, one row of pixels at a time (along a row the latitude is constant, so each row is
|
|
5
|
+
* a horizontal strip of the source, scaled). The planetary services (NASA's Trek, USGS) serve this grid only.
|
|
6
|
+
*/
|
|
7
|
+
/** Side of the source tiles, pixels */
|
|
8
|
+
const SOURCE = 256;
|
|
9
|
+
/** Source levels added at most towards the poles, where Web Mercator stretches the latitudes */
|
|
10
|
+
const MAX_BOOST = 3;
|
|
11
|
+
/** Source tiles kept decoded: the neighbours of a Mercator tile share them */
|
|
12
|
+
const CACHE = 96;
|
|
13
|
+
/** Latitude (degrees) of a Mercator y in [0, 1] (towards the south) */
|
|
14
|
+
const latitude = (y) => (Math.atan(Math.sinh(Math.PI * (1 - 2 * y))) * 180) / Math.PI;
|
|
15
|
+
/**
|
|
16
|
+
* The strips filling Mercator tile `z`/`x`/`y` (`size` pixels a side) from equirectangular tiles of levels `minLevel`
|
|
17
|
+
* to `maxLevel`. The source level has the same pixel size along the parallels (one less than `z`: the source is twice
|
|
18
|
+
* as wide at level 0), finer towards the poles, where the Mercator pixels shrink along the meridians.
|
|
19
|
+
*/
|
|
20
|
+
export function plan(z, x, y, size, maxLevel, minLevel = 0) {
|
|
21
|
+
const n = 2 ** z;
|
|
22
|
+
const center = latitude((y + 0.5) / n);
|
|
23
|
+
const boost = Math.min(MAX_BOOST, Math.max(0, Math.round(-Math.log2(Math.cos((center * Math.PI) / 180)))));
|
|
24
|
+
const level = Math.max(0, minLevel, Math.min(maxLevel, z - 1 + boost));
|
|
25
|
+
// The source in pixels at that level: 360° across, 180° down
|
|
26
|
+
const width = 2 * SOURCE * 2 ** level;
|
|
27
|
+
const height = SOURCE * 2 ** level;
|
|
28
|
+
const west = (x / n) * width;
|
|
29
|
+
const east = ((x + 1) / n) * width;
|
|
30
|
+
const scale = size / (east - west);
|
|
31
|
+
const strips = [];
|
|
32
|
+
for (let row = 0; row < size; row += 1) {
|
|
33
|
+
const lat = latitude((y + (row + 0.5) / size) / n);
|
|
34
|
+
const v = Math.max(0, Math.min(height - 1e-6, ((90 - lat) / 180) * height));
|
|
35
|
+
const ty = Math.floor(v / SOURCE);
|
|
36
|
+
// The source row centered on the destination row's latitude, kept inside its tile (no bleeding into the next)
|
|
37
|
+
const sy = Math.max(0, Math.min(SOURCE - 1, v - ty * SOURCE - 0.5));
|
|
38
|
+
for (let tx = Math.floor(west / SOURCE); tx * SOURCE < east; tx += 1) {
|
|
39
|
+
const from = Math.max(west, tx * SOURCE);
|
|
40
|
+
const to = Math.min(east, (tx + 1) * SOURCE);
|
|
41
|
+
strips.push({
|
|
42
|
+
tx,
|
|
43
|
+
ty,
|
|
44
|
+
sx: from - tx * SOURCE,
|
|
45
|
+
sy,
|
|
46
|
+
sw: to - from,
|
|
47
|
+
dx: (from - west) * scale,
|
|
48
|
+
dy: row,
|
|
49
|
+
dw: (to - from) * scale,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return { level, strips };
|
|
54
|
+
}
|
|
55
|
+
/** A tile URL of the source: {z}, {x} and {y} replaced */
|
|
56
|
+
const tileUrl = (template, z, x, y) => template.replaceAll('{z}', String(z)).replaceAll('{x}', String(x)).replaceAll('{y}', String(y));
|
|
57
|
+
/**
|
|
58
|
+
* A raster loader drawing Web Mercator tiles of `size` pixels from the equirectangular tiles of `url` ({z}, {x}, {y};
|
|
59
|
+
* levels `minLevel` to `maxLevel`). A missing source tile (404) leaves its part empty; a tile with no source at all is
|
|
60
|
+
* missing; other errors reject (retried by the engine).
|
|
61
|
+
*/
|
|
62
|
+
export function equirectangularLoader(url, maxLevel, size, minLevel = 0) {
|
|
63
|
+
// Source tiles by key, oldest first: a request is shared by the tiles that need it, its signal is not theirs
|
|
64
|
+
const cache = new Map();
|
|
65
|
+
/** Tiles being drawn from each source tile: an evicted one is closed once none is */
|
|
66
|
+
const users = new Map();
|
|
67
|
+
const evicted = new Set();
|
|
68
|
+
const close = (tile) => {
|
|
69
|
+
evicted.delete(tile);
|
|
70
|
+
tile.then((image) => image?.close(), () => undefined);
|
|
71
|
+
};
|
|
72
|
+
const release = (tile) => {
|
|
73
|
+
const count = (users.get(tile) ?? 1) - 1;
|
|
74
|
+
if (count > 0)
|
|
75
|
+
users.set(tile, count);
|
|
76
|
+
else {
|
|
77
|
+
users.delete(tile);
|
|
78
|
+
if (evicted.has(tile))
|
|
79
|
+
close(tile);
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
const source = (z, x, y) => {
|
|
83
|
+
const key = `${z}/${x}/${y}`;
|
|
84
|
+
let tile = cache.get(key);
|
|
85
|
+
if (tile) {
|
|
86
|
+
cache.delete(key);
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
tile = (async () => {
|
|
90
|
+
const response = await fetch(tileUrl(url, z, x, y));
|
|
91
|
+
if (response.status === 404 || response.status === 204 || response.status === 410)
|
|
92
|
+
return null;
|
|
93
|
+
if (!response.ok)
|
|
94
|
+
throw new Error(`isard: ${response.status} for an equirectangular tile`);
|
|
95
|
+
return createImageBitmap(await response.blob());
|
|
96
|
+
})();
|
|
97
|
+
// A failure is not kept: the engine's retry fetches again
|
|
98
|
+
tile.catch(() => cache.delete(key));
|
|
99
|
+
}
|
|
100
|
+
cache.set(key, tile);
|
|
101
|
+
users.set(tile, (users.get(tile) ?? 0) + 1);
|
|
102
|
+
for (const [old, image] of cache) {
|
|
103
|
+
if (cache.size <= CACHE)
|
|
104
|
+
break;
|
|
105
|
+
cache.delete(old);
|
|
106
|
+
// Its image freed now, or once the tiles drawing from it are done
|
|
107
|
+
if (users.has(image))
|
|
108
|
+
evicted.add(image);
|
|
109
|
+
else
|
|
110
|
+
close(image);
|
|
111
|
+
}
|
|
112
|
+
return tile;
|
|
113
|
+
};
|
|
114
|
+
return async (z, x, y, signal) => {
|
|
115
|
+
const { level, strips } = plan(z, x, y, size, maxLevel, minLevel);
|
|
116
|
+
const keys = [...new Set(strips.map((s) => `${s.tx}/${s.ty}`))];
|
|
117
|
+
const tiles = keys.map((key) => {
|
|
118
|
+
const [tx, ty] = key.split('/').map(Number);
|
|
119
|
+
return [key, source(level, tx, ty)];
|
|
120
|
+
});
|
|
121
|
+
try {
|
|
122
|
+
const images = new Map(await Promise.all(tiles.map(async ([key, tile]) => [key, await tile])));
|
|
123
|
+
signal.throwIfAborted();
|
|
124
|
+
if (![...images.values()].some(Boolean))
|
|
125
|
+
return null;
|
|
126
|
+
const canvas = new OffscreenCanvas(size, size);
|
|
127
|
+
const context = canvas.getContext('2d');
|
|
128
|
+
if (!context)
|
|
129
|
+
throw new Error('isard: no 2D canvas to reproject the tiles');
|
|
130
|
+
for (const s of strips) {
|
|
131
|
+
const image = images.get(`${s.tx}/${s.ty}`);
|
|
132
|
+
if (image)
|
|
133
|
+
context.drawImage(image, s.sx, s.sy, s.sw, 1, s.dx, s.dy, s.dw, 1);
|
|
134
|
+
}
|
|
135
|
+
return canvas.transferToImageBitmap();
|
|
136
|
+
}
|
|
137
|
+
finally {
|
|
138
|
+
for (const [, tile] of tiles)
|
|
139
|
+
release(tile);
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
//# sourceMappingURL=equirectangular.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"equirectangular.js","sourceRoot":"","sources":["../src/equirectangular.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Equirectangular tiles (longitudes and latitudes, two 256 px tiles side by side at level 0, rows from the north)\n * reprojected into the Web Mercator tiles the engine draws: a raster loader that fetches the source tiles a Mercator\n * tile spans and draws them into it, one row of pixels at a time (along a row the latitude is constant, so each row is\n * a horizontal strip of the source, scaled). The planetary services (NASA's Trek, USGS) serve this grid only.\n */\n\n/** Side of the source tiles, pixels */\nconst SOURCE = 256;\n/** Source levels added at most towards the poles, where Web Mercator stretches the latitudes */\nconst MAX_BOOST = 3;\n/** Source tiles kept decoded: the neighbours of a Mercator tile share them */\nconst CACHE = 96;\n\n/** One strip drawn: from source tile (`tx`, `ty`) at the plan's level, the source rectangle (`sx`, `sy`, `sw`, one\n * row high) to the destination rectangle (`dx`, `dy`, `dw`, one row high) */\nexport interface Strip {\n tx: number;\n ty: number;\n sx: number;\n sy: number;\n sw: number;\n dx: number;\n dy: number;\n dw: number;\n}\n\n/** Where the pixels of a Mercator tile come from: the source level, and a strip per row and source column */\nexport interface Plan {\n level: number;\n strips: Strip[];\n}\n\n/** Latitude (degrees) of a Mercator y in [0, 1] (towards the south) */\nconst latitude = (y: number) => (Math.atan(Math.sinh(Math.PI * (1 - 2 * y))) * 180) / Math.PI;\n\n/**\n * The strips filling Mercator tile `z`/`x`/`y` (`size` pixels a side) from equirectangular tiles of levels `minLevel`\n * to `maxLevel`. The source level has the same pixel size along the parallels (one less than `z`: the source is twice\n * as wide at level 0), finer towards the poles, where the Mercator pixels shrink along the meridians.\n */\nexport function plan(z: number, x: number, y: number, size: number, maxLevel: number, minLevel = 0): Plan {\n const n = 2 ** z;\n const center = latitude((y + 0.5) / n);\n const boost = Math.min(MAX_BOOST, Math.max(0, Math.round(-Math.log2(Math.cos((center * Math.PI) / 180)))));\n const level = Math.max(0, minLevel, Math.min(maxLevel, z - 1 + boost));\n // The source in pixels at that level: 360° across, 180° down\n const width = 2 * SOURCE * 2 ** level;\n const height = SOURCE * 2 ** level;\n const west = (x / n) * width;\n const east = ((x + 1) / n) * width;\n const scale = size / (east - west);\n const strips: Strip[] = [];\n for (let row = 0; row < size; row += 1) {\n const lat = latitude((y + (row + 0.5) / size) / n);\n const v = Math.max(0, Math.min(height - 1e-6, ((90 - lat) / 180) * height));\n const ty = Math.floor(v / SOURCE);\n // The source row centered on the destination row's latitude, kept inside its tile (no bleeding into the next)\n const sy = Math.max(0, Math.min(SOURCE - 1, v - ty * SOURCE - 0.5));\n for (let tx = Math.floor(west / SOURCE); tx * SOURCE < east; tx += 1) {\n const from = Math.max(west, tx * SOURCE);\n const to = Math.min(east, (tx + 1) * SOURCE);\n strips.push({\n tx,\n ty,\n sx: from - tx * SOURCE,\n sy,\n sw: to - from,\n dx: (from - west) * scale,\n dy: row,\n dw: (to - from) * scale,\n });\n }\n }\n return { level, strips };\n}\n\n/** A tile URL of the source: {z}, {x} and {y} replaced */\nconst tileUrl = (template: string, z: number, x: number, y: number) =>\n template.replaceAll('{z}', String(z)).replaceAll('{x}', String(x)).replaceAll('{y}', String(y));\n\n/**\n * A raster loader drawing Web Mercator tiles of `size` pixels from the equirectangular tiles of `url` ({z}, {x}, {y};\n * levels `minLevel` to `maxLevel`). A missing source tile (404) leaves its part empty; a tile with no source at all is\n * missing; other errors reject (retried by the engine).\n */\nexport function equirectangularLoader(\n url: string,\n maxLevel: number,\n size: number,\n minLevel = 0,\n): (z: number, x: number, y: number, signal: AbortSignal) => Promise<ImageBitmap | null> {\n // Source tiles by key, oldest first: a request is shared by the tiles that need it, its signal is not theirs\n const cache = new Map<string, Promise<ImageBitmap | null>>();\n /** Tiles being drawn from each source tile: an evicted one is closed once none is */\n const users = new Map<Promise<ImageBitmap | null>, number>();\n const evicted = new Set<Promise<ImageBitmap | null>>();\n const close = (tile: Promise<ImageBitmap | null>) => {\n evicted.delete(tile);\n tile.then(\n (image) => image?.close(),\n () => undefined,\n );\n };\n const release = (tile: Promise<ImageBitmap | null>) => {\n const count = (users.get(tile) ?? 1) - 1;\n if (count > 0) users.set(tile, count);\n else {\n users.delete(tile);\n if (evicted.has(tile)) close(tile);\n }\n };\n const source = (z: number, x: number, y: number): Promise<ImageBitmap | null> => {\n const key = `${z}/${x}/${y}`;\n let tile = cache.get(key);\n if (tile) {\n cache.delete(key);\n } else {\n tile = (async () => {\n const response = await fetch(tileUrl(url, z, x, y));\n if (response.status === 404 || response.status === 204 || response.status === 410) return null;\n if (!response.ok) throw new Error(`isard: ${response.status} for an equirectangular tile`);\n return createImageBitmap(await response.blob());\n })();\n // A failure is not kept: the engine's retry fetches again\n tile.catch(() => cache.delete(key));\n }\n cache.set(key, tile);\n users.set(tile, (users.get(tile) ?? 0) + 1);\n for (const [old, image] of cache) {\n if (cache.size <= CACHE) break;\n cache.delete(old);\n // Its image freed now, or once the tiles drawing from it are done\n if (users.has(image)) evicted.add(image);\n else close(image);\n }\n return tile;\n };\n return async (z, x, y, signal) => {\n const { level, strips } = plan(z, x, y, size, maxLevel, minLevel);\n const keys = [...new Set(strips.map((s) => `${s.tx}/${s.ty}`))];\n const tiles = keys.map((key) => {\n const [tx, ty] = key.split('/').map(Number) as [number, number];\n return [key, source(level, tx, ty)] as const;\n });\n try {\n const images = new Map(await Promise.all(tiles.map(async ([key, tile]) => [key, await tile] as const)));\n signal.throwIfAborted();\n if (![...images.values()].some(Boolean)) return null;\n const canvas = new OffscreenCanvas(size, size);\n const context = canvas.getContext('2d');\n if (!context) throw new Error('isard: no 2D canvas to reproject the tiles');\n for (const s of strips) {\n const image = images.get(`${s.tx}/${s.ty}`);\n if (image) context.drawImage(image, s.sx, s.sy, s.sw, 1, s.dx, s.dy, s.dw, 1);\n }\n return canvas.transferToImageBitmap();\n } finally {\n for (const [, tile] of tiles) release(tile);\n }\n };\n}\n"]}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extruded polygons (buildings), styled per feature like the other feature layers, coming in tiles (or one set): an
|
|
3
|
+
* application loading a tiled source (BD TOPO vector tiles…) hands each tile's buildings to `setTile` and drops them
|
|
4
|
+
* with `removeTile`. Colored by their color, by a value per building through the ramp (water at a building), or by
|
|
5
|
+
* the node values of a network layer (`linkNetwork`: the travel time to each building, recolored with the network).
|
|
6
|
+
*/
|
|
7
|
+
import { type Feature, type FeatureData, type FeatureLayerStyle, type Placement, type Properties, type Styled } from './features.ts';
|
|
8
|
+
import { Layer, type NetworkLayer } from './layers.ts';
|
|
9
|
+
export interface ExtrusionStyle<P = Properties> extends FeatureLayerStyle<P> {
|
|
10
|
+
/** CSS color (default `#e4e1dc`) */
|
|
11
|
+
color: Styled<string, P>;
|
|
12
|
+
/** Height of the roof over the ground, metres (default 6) */
|
|
13
|
+
height: Styled<number, P>;
|
|
14
|
+
/** Height of the foot of the walls, metres (default 0) */
|
|
15
|
+
base: Styled<number, P>;
|
|
16
|
+
}
|
|
17
|
+
export interface ExtrusionOptions<P = Properties> extends Partial<ExtrusionStyle<P>>, Placement {
|
|
18
|
+
/** Polygon and MultiPolygon features, as one set (tiles come with `setTile`) */
|
|
19
|
+
data?: FeatureData<P>;
|
|
20
|
+
}
|
|
21
|
+
/** Key of a tile of extrusions (see `ExtrusionLayer.setTile`) */
|
|
22
|
+
export type TileKey = string | number;
|
|
23
|
+
/**
|
|
24
|
+
* Polygons as typed arrays, for tiles decoded in a worker (no GeoJSON in between): points in Mercator [0, 1] (x, y
|
|
25
|
+
* interleaved, y towards the south), the first point of each ring and the first ring of each polygon (one more entry
|
|
26
|
+
* than rings and polygons; the first ring of a polygon is its outline), the feature of each polygon
|
|
27
|
+
*/
|
|
28
|
+
export interface PolygonArrays {
|
|
29
|
+
points: Float64Array;
|
|
30
|
+
ringStart: Uint32Array;
|
|
31
|
+
polygonStart: Uint32Array;
|
|
32
|
+
feature: Uint32Array;
|
|
33
|
+
}
|
|
34
|
+
export declare class ExtrusionLayer<P = Properties> extends Layer {
|
|
35
|
+
private style;
|
|
36
|
+
private readonly tiles;
|
|
37
|
+
private nextBatch;
|
|
38
|
+
private network;
|
|
39
|
+
/** Replaces the set given as `data` */
|
|
40
|
+
setData(data: FeatureData<P>): void;
|
|
41
|
+
/** Adds or replaces the buildings of a tile: GeoJSON, or typed arrays with the properties of each feature */
|
|
42
|
+
setTile(key: TileKey, data: FeatureData<P> | {
|
|
43
|
+
arrays: PolygonArrays;
|
|
44
|
+
properties: P[];
|
|
45
|
+
}): void;
|
|
46
|
+
removeTile(key: TileKey): void;
|
|
47
|
+
/** Keys of the tiles held */
|
|
48
|
+
get tileKeys(): TileKey[];
|
|
49
|
+
/** The properties of the features of a tile (or of the data) */
|
|
50
|
+
properties(key?: TileKey): readonly P[];
|
|
51
|
+
/** The feature of a picked index (its geometry if given as GeoJSON): tiles are numbered one after the other, in the
|
|
52
|
+
* order they were added */
|
|
53
|
+
featureAt(index: number): Feature<P> | undefined;
|
|
54
|
+
/** The tile and the index in it of a picked index */
|
|
55
|
+
tileAt(index: number): {
|
|
56
|
+
key: TileKey | undefined;
|
|
57
|
+
index: number;
|
|
58
|
+
} | undefined;
|
|
59
|
+
setStyle(style: Partial<ExtrusionStyle<P>>): void;
|
|
60
|
+
setVisible(visible: boolean): void;
|
|
61
|
+
setFilter(filter: ((properties: P, index: number) => boolean) | undefined): void;
|
|
62
|
+
/** One value per building of a tile (or of the data), colored through the ramp; NaN keeps a building's color */
|
|
63
|
+
setValues(values: Float32Array | undefined, key?: TileKey): void;
|
|
64
|
+
/**
|
|
65
|
+
* Colors the buildings by the node values of a network layer, recolored with it: each building takes min(value(A)
|
|
66
|
+
* + cost from A, value(B) + cost from B) from its anchors (`setAnchors`), through the ramp, up to `max` (above, or
|
|
67
|
+
* unreached: its own color). Undefined: back to the buildings' own values.
|
|
68
|
+
*/
|
|
69
|
+
linkNetwork(network: NetworkLayer | undefined, max?: number): void;
|
|
70
|
+
/** Per building of a tile (or of the data): node A, node B, cost from A, cost from B (4 floats each) */
|
|
71
|
+
setAnchors(anchors: Float32Array, key?: TileKey): void;
|
|
72
|
+
private put;
|
|
73
|
+
private mask;
|
|
74
|
+
private sendTile;
|
|
75
|
+
private sendStyle;
|
|
76
|
+
private sendLayer;
|
|
77
|
+
private sendLink;
|
|
78
|
+
}
|
|
79
|
+
export declare function extrusionStyle<P>(options: ExtrusionOptions<P>): ExtrusionStyle<P>;
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extruded polygons (buildings), styled per feature like the other feature layers, coming in tiles (or one set): an
|
|
3
|
+
* application loading a tiled source (BD TOPO vector tiles…) hands each tile's buildings to `setTile` and drops them
|
|
4
|
+
* with `removeTile`. Colored by their color, by a value per building through the ramp (water at a building), or by
|
|
5
|
+
* the node values of a network layer (`linkNetwork`: the travel time to each building, recolored with the network).
|
|
6
|
+
*/
|
|
7
|
+
import { evaluate, FLOATS, featuresOf, parseColor, polygonGeometry, rampTexels, stops, } from "./features.js";
|
|
8
|
+
import { Layer } from "./layers.js";
|
|
9
|
+
/** TileKey of the set given as `data` */
|
|
10
|
+
const DATA = Symbol('data');
|
|
11
|
+
export class ExtrusionLayer extends Layer {
|
|
12
|
+
style;
|
|
13
|
+
tiles = new Map();
|
|
14
|
+
nextBatch = 1;
|
|
15
|
+
network;
|
|
16
|
+
/** @internal */
|
|
17
|
+
constructor(map, data, style) {
|
|
18
|
+
super(map);
|
|
19
|
+
this.style = style;
|
|
20
|
+
if (data) {
|
|
21
|
+
const features = featuresOf(data);
|
|
22
|
+
const properties = features.map((f) => f.properties);
|
|
23
|
+
this.tiles.set(DATA, {
|
|
24
|
+
batch: 0,
|
|
25
|
+
properties,
|
|
26
|
+
features,
|
|
27
|
+
arrays: undefined,
|
|
28
|
+
values: undefined,
|
|
29
|
+
anchors: undefined,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/** Replaces the set given as `data` */
|
|
34
|
+
setData(data) {
|
|
35
|
+
const features = featuresOf(data);
|
|
36
|
+
this.put(DATA, features.map((f) => f.properties), features, undefined);
|
|
37
|
+
}
|
|
38
|
+
/** Adds or replaces the buildings of a tile: GeoJSON, or typed arrays with the properties of each feature */
|
|
39
|
+
setTile(key, data) {
|
|
40
|
+
if ('arrays' in data)
|
|
41
|
+
this.put(key, data.properties, undefined, data.arrays);
|
|
42
|
+
else {
|
|
43
|
+
const features = featuresOf(data);
|
|
44
|
+
this.put(key, features.map((f) => f.properties), features, undefined);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
removeTile(key) {
|
|
48
|
+
const tile = this.tiles.get(key);
|
|
49
|
+
if (!tile)
|
|
50
|
+
return;
|
|
51
|
+
this.tiles.delete(key);
|
|
52
|
+
this.map.engine.removeExtrusionBatch(this.id, tile.batch);
|
|
53
|
+
this.map.requestRender();
|
|
54
|
+
}
|
|
55
|
+
/** Keys of the tiles held */
|
|
56
|
+
get tileKeys() {
|
|
57
|
+
return [...this.tiles.keys()].filter((k) => k !== DATA);
|
|
58
|
+
}
|
|
59
|
+
/** The properties of the features of a tile (or of the data) */
|
|
60
|
+
properties(key) {
|
|
61
|
+
return this.tiles.get(key ?? DATA)?.properties ?? [];
|
|
62
|
+
}
|
|
63
|
+
/** The feature of a picked index (its geometry if given as GeoJSON): tiles are numbered one after the other, in the
|
|
64
|
+
* order they were added */
|
|
65
|
+
featureAt(index) {
|
|
66
|
+
let rest = index;
|
|
67
|
+
for (const tile of [...this.tiles.values()].sort((a, b) => a.batch - b.batch)) {
|
|
68
|
+
const n = tile.properties.length;
|
|
69
|
+
if (rest < n) {
|
|
70
|
+
const properties = tile.properties[rest];
|
|
71
|
+
return tile.features?.[rest] ?? { type: 'Feature', properties, geometry: null };
|
|
72
|
+
}
|
|
73
|
+
rest -= n;
|
|
74
|
+
}
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
/** The tile and the index in it of a picked index */
|
|
78
|
+
tileAt(index) {
|
|
79
|
+
let rest = index;
|
|
80
|
+
for (const [key, tile] of [...this.tiles].sort((a, b) => a[1].batch - b[1].batch)) {
|
|
81
|
+
if (rest < tile.properties.length)
|
|
82
|
+
return { key: key === DATA ? undefined : key, index: rest };
|
|
83
|
+
rest -= tile.properties.length;
|
|
84
|
+
}
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
87
|
+
setStyle(style) {
|
|
88
|
+
this.style = { ...this.style, ...style };
|
|
89
|
+
const engine = this.map.engine;
|
|
90
|
+
for (const tile of this.tiles.values())
|
|
91
|
+
this.sendStyle(engine, this.id, tile);
|
|
92
|
+
this.sendLayer(engine, this.id);
|
|
93
|
+
this.map.requestRender();
|
|
94
|
+
}
|
|
95
|
+
setVisible(visible) {
|
|
96
|
+
this.setStyle({ visible });
|
|
97
|
+
}
|
|
98
|
+
setFilter(filter) {
|
|
99
|
+
this.style = { ...this.style, filter };
|
|
100
|
+
for (const tile of this.tiles.values())
|
|
101
|
+
this.map.engine.setFeatureMask(this.id, this.mask(tile), tile.batch);
|
|
102
|
+
this.map.requestRender();
|
|
103
|
+
}
|
|
104
|
+
/** One value per building of a tile (or of the data), colored through the ramp; NaN keeps a building's color */
|
|
105
|
+
setValues(values, key) {
|
|
106
|
+
const tile = this.tiles.get(key ?? DATA);
|
|
107
|
+
if (!tile)
|
|
108
|
+
return;
|
|
109
|
+
tile.values = values;
|
|
110
|
+
this.map.engine.setFeatureValues(this.id, values ?? new Float32Array(tile.properties.length).fill(Number.NaN), tile.batch);
|
|
111
|
+
this.map.requestRender();
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Colors the buildings by the node values of a network layer, recolored with it: each building takes min(value(A)
|
|
115
|
+
* + cost from A, value(B) + cost from B) from its anchors (`setAnchors`), through the ramp, up to `max` (above, or
|
|
116
|
+
* unreached: its own color). Undefined: back to the buildings' own values.
|
|
117
|
+
*/
|
|
118
|
+
linkNetwork(network, max = Number.POSITIVE_INFINITY) {
|
|
119
|
+
this.network = network && { layer: network, max };
|
|
120
|
+
this.sendLink(this.map.engine, this.id);
|
|
121
|
+
this.map.requestRender();
|
|
122
|
+
}
|
|
123
|
+
/** Per building of a tile (or of the data): node A, node B, cost from A, cost from B (4 floats each) */
|
|
124
|
+
setAnchors(anchors, key) {
|
|
125
|
+
const tile = this.tiles.get(key ?? DATA);
|
|
126
|
+
if (!tile)
|
|
127
|
+
return;
|
|
128
|
+
tile.anchors = anchors;
|
|
129
|
+
this.map.engine.setExtrusionAnchors(this.id, tile.batch, anchors);
|
|
130
|
+
this.map.requestRender();
|
|
131
|
+
}
|
|
132
|
+
put(key, properties, features, arrays) {
|
|
133
|
+
const old = this.tiles.get(key);
|
|
134
|
+
const tile = {
|
|
135
|
+
batch: old?.batch ?? (key === DATA ? 0 : this.nextBatch++),
|
|
136
|
+
properties,
|
|
137
|
+
features,
|
|
138
|
+
arrays,
|
|
139
|
+
values: undefined,
|
|
140
|
+
anchors: undefined,
|
|
141
|
+
};
|
|
142
|
+
this.tiles.set(key, tile);
|
|
143
|
+
this.sendTile(this.map.engine, this.id, tile);
|
|
144
|
+
this.map.requestRender();
|
|
145
|
+
}
|
|
146
|
+
mask(tile) {
|
|
147
|
+
const { filter } = this.style;
|
|
148
|
+
const mask = new Uint8Array(tile.properties.length).fill(255);
|
|
149
|
+
if (filter)
|
|
150
|
+
tile.properties.forEach((p, i) => {
|
|
151
|
+
if (!filter(p, i))
|
|
152
|
+
mask[i] = 0;
|
|
153
|
+
});
|
|
154
|
+
return mask;
|
|
155
|
+
}
|
|
156
|
+
sendTile(engine, id, tile) {
|
|
157
|
+
const g = tile.arrays ?? polygonGeometry(tile.features ?? []);
|
|
158
|
+
const n = tile.properties.length;
|
|
159
|
+
engine.setExtrusionBatch(id, tile.batch, g.points, g.ringStart, g.polygonStart, g.feature, n);
|
|
160
|
+
this.sendStyle(engine, id, tile);
|
|
161
|
+
if (tile.values)
|
|
162
|
+
engine.setFeatureValues(id, tile.values, tile.batch);
|
|
163
|
+
if (tile.anchors)
|
|
164
|
+
engine.setExtrusionAnchors(id, tile.batch, tile.anchors);
|
|
165
|
+
}
|
|
166
|
+
sendStyle(engine, id, tile) {
|
|
167
|
+
const { color, height, base } = this.style;
|
|
168
|
+
const out = new Float32Array(tile.properties.length * FLOATS);
|
|
169
|
+
// Constant colors parsed once: a tile has thousands of buildings
|
|
170
|
+
const fixed = typeof color === 'string' ? parseColor(color) : undefined;
|
|
171
|
+
tile.properties.forEach((p, i) => {
|
|
172
|
+
const o = i * FLOATS;
|
|
173
|
+
out.set(fixed ?? parseColor(evaluate(color, p, i)), o);
|
|
174
|
+
out.set(stops(evaluate(height, p, i)), o + 8);
|
|
175
|
+
out.set(stops(evaluate(base, p, i)), o + 16);
|
|
176
|
+
});
|
|
177
|
+
engine.setFeatureStyle(id, out, tile.batch);
|
|
178
|
+
engine.setFeatureMask(id, this.mask(tile), tile.batch);
|
|
179
|
+
}
|
|
180
|
+
sendLayer(engine, id) {
|
|
181
|
+
const { ramp, opacity, minZoom, maxZoom, visible } = this.style;
|
|
182
|
+
const r = ramp && rampTexels(ramp);
|
|
183
|
+
engine.setFeatureRamp(id, r?.rgba, r?.min ?? 0, r?.max ?? 1);
|
|
184
|
+
engine.setFeatureLayer(id, Float32Array.from(stops(opacity)), minZoom, maxZoom, visible);
|
|
185
|
+
}
|
|
186
|
+
sendLink(engine, id) {
|
|
187
|
+
const n = this.network;
|
|
188
|
+
// A network removed (or not created) links to nothing
|
|
189
|
+
const network = n && n.layer.id >= 0 ? n.layer.id : undefined;
|
|
190
|
+
engine.linkExtrusionNetwork(id, network, n ? Math.min(n.max, 1e29) : 0);
|
|
191
|
+
}
|
|
192
|
+
/** @internal */
|
|
193
|
+
create(engine) {
|
|
194
|
+
const id = engine.addExtrusions();
|
|
195
|
+
this.sendLayer(engine, id);
|
|
196
|
+
for (const tile of this.tiles.values())
|
|
197
|
+
this.sendTile(engine, id, tile);
|
|
198
|
+
// A network created before (on a new engine, the map relinks once every layer is there)
|
|
199
|
+
if (!this.network || this.network.layer.id >= 0)
|
|
200
|
+
this.sendLink(engine, id);
|
|
201
|
+
return id;
|
|
202
|
+
}
|
|
203
|
+
/** @internal On a new engine, the network may come after the buildings: linked once both are there */
|
|
204
|
+
relink(engine) {
|
|
205
|
+
if (this.network)
|
|
206
|
+
this.sendLink(engine, this.id);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
export function extrusionStyle(options) {
|
|
210
|
+
const { data: _, before: __, ...rest } = options;
|
|
211
|
+
return {
|
|
212
|
+
opacity: 1,
|
|
213
|
+
minZoom: 0,
|
|
214
|
+
maxZoom: 24,
|
|
215
|
+
visible: true,
|
|
216
|
+
filter: undefined,
|
|
217
|
+
ramp: undefined,
|
|
218
|
+
color: '#e4e1dc',
|
|
219
|
+
height: 6,
|
|
220
|
+
base: 0,
|
|
221
|
+
...rest,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
//# sourceMappingURL=extrusions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extrusions.js","sourceRoot":"","sources":["../src/extrusions.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Extruded polygons (buildings), styled per feature like the other feature layers, coming in tiles (or one set): an\n * application loading a tiled source (BD TOPO vector tiles…) hands each tile's buildings to `setTile` and drops them\n * with `removeTile`. Colored by their color, by a value per building through the ramp (water at a building), or by\n * the node values of a network layer (`linkNetwork`: the travel time to each building, recolored with the network).\n */\nimport {\n evaluate,\n type Feature,\n type FeatureData,\n type FeatureLayerStyle,\n FLOATS,\n featuresOf,\n type Placement,\n type Properties,\n parseColor,\n polygonGeometry,\n rampTexels,\n type Styled,\n stops,\n} from './features.ts';\nimport { Layer, type NetworkLayer } from './layers.ts';\nimport type { LayerHost } from './types.ts';\nimport type { Engine } from './wasm/isard.js';\n\nexport interface ExtrusionStyle<P = Properties> extends FeatureLayerStyle<P> {\n /** CSS color (default `#e4e1dc`) */\n color: Styled<string, P>;\n /** Height of the roof over the ground, metres (default 6) */\n height: Styled<number, P>;\n /** Height of the foot of the walls, metres (default 0) */\n base: Styled<number, P>;\n}\n\nexport interface ExtrusionOptions<P = Properties> extends Partial<ExtrusionStyle<P>>, Placement {\n /** Polygon and MultiPolygon features, as one set (tiles come with `setTile`) */\n data?: FeatureData<P>;\n}\n\n/** Key of a tile of extrusions (see `ExtrusionLayer.setTile`) */\nexport type TileKey = string | number;\n\n/**\n * Polygons as typed arrays, for tiles decoded in a worker (no GeoJSON in between): points in Mercator [0, 1] (x, y\n * interleaved, y towards the south), the first point of each ring and the first ring of each polygon (one more entry\n * than rings and polygons; the first ring of a polygon is its outline), the feature of each polygon\n */\nexport interface PolygonArrays {\n points: Float64Array;\n ringStart: Uint32Array;\n polygonStart: Uint32Array;\n feature: Uint32Array;\n}\n\ninterface Tile<P> {\n /** Batch number in the engine: tiles are drawn, and their features numbered for picking, in this order */\n batch: number;\n /** The properties of the features, which the style reads */\n properties: P[];\n features: Feature<P>[] | undefined;\n arrays: PolygonArrays | undefined;\n values: Float32Array | undefined;\n anchors: Float32Array | undefined;\n}\n\n/** TileKey of the set given as `data` */\nconst DATA = Symbol('data');\n\nexport class ExtrusionLayer<P = Properties> extends Layer {\n private readonly tiles = new Map<TileKey | typeof DATA, Tile<P>>();\n private nextBatch = 1;\n private network: { layer: NetworkLayer; max: number } | undefined;\n\n /** @internal */\n constructor(\n map: LayerHost,\n data: FeatureData<P> | undefined,\n private style: ExtrusionStyle<P>,\n ) {\n super(map);\n if (data) {\n const features = featuresOf(data);\n const properties = features.map((f) => f.properties);\n this.tiles.set(DATA, {\n batch: 0,\n properties,\n features,\n arrays: undefined,\n values: undefined,\n anchors: undefined,\n });\n }\n }\n\n /** Replaces the set given as `data` */\n setData(data: FeatureData<P>): void {\n const features = featuresOf(data);\n this.put(\n DATA,\n features.map((f) => f.properties),\n features,\n undefined,\n );\n }\n\n /** Adds or replaces the buildings of a tile: GeoJSON, or typed arrays with the properties of each feature */\n setTile(key: TileKey, data: FeatureData<P> | { arrays: PolygonArrays; properties: P[] }): void {\n if ('arrays' in data) this.put(key, data.properties, undefined, data.arrays);\n else {\n const features = featuresOf(data);\n this.put(\n key,\n features.map((f) => f.properties),\n features,\n undefined,\n );\n }\n }\n\n removeTile(key: TileKey): void {\n const tile = this.tiles.get(key);\n if (!tile) return;\n this.tiles.delete(key);\n this.map.engine.removeExtrusionBatch(this.id, tile.batch);\n this.map.requestRender();\n }\n\n /** Keys of the tiles held */\n get tileKeys(): TileKey[] {\n return [...this.tiles.keys()].filter((k): k is TileKey => k !== DATA);\n }\n\n /** The properties of the features of a tile (or of the data) */\n properties(key?: TileKey): readonly P[] {\n return this.tiles.get(key ?? DATA)?.properties ?? [];\n }\n\n /** The feature of a picked index (its geometry if given as GeoJSON): tiles are numbered one after the other, in the\n * order they were added */\n featureAt(index: number): Feature<P> | undefined {\n let rest = index;\n for (const tile of [...this.tiles.values()].sort((a, b) => a.batch - b.batch)) {\n const n = tile.properties.length;\n if (rest < n) {\n const properties = tile.properties[rest] as P;\n return tile.features?.[rest] ?? { type: 'Feature', properties, geometry: null };\n }\n rest -= n;\n }\n return undefined;\n }\n\n /** The tile and the index in it of a picked index */\n tileAt(index: number): { key: TileKey | undefined; index: number } | undefined {\n let rest = index;\n for (const [key, tile] of [...this.tiles].sort((a, b) => a[1].batch - b[1].batch)) {\n if (rest < tile.properties.length) return { key: key === DATA ? undefined : key, index: rest };\n rest -= tile.properties.length;\n }\n return undefined;\n }\n\n setStyle(style: Partial<ExtrusionStyle<P>>): void {\n this.style = { ...this.style, ...style };\n const engine = this.map.engine;\n for (const tile of this.tiles.values()) this.sendStyle(engine, this.id, tile);\n this.sendLayer(engine, this.id);\n this.map.requestRender();\n }\n\n setVisible(visible: boolean): void {\n this.setStyle({ visible } as Partial<ExtrusionStyle<P>>);\n }\n\n setFilter(filter: ((properties: P, index: number) => boolean) | undefined): void {\n this.style = { ...this.style, filter };\n for (const tile of this.tiles.values()) this.map.engine.setFeatureMask(this.id, this.mask(tile), tile.batch);\n this.map.requestRender();\n }\n\n /** One value per building of a tile (or of the data), colored through the ramp; NaN keeps a building's color */\n setValues(values: Float32Array | undefined, key?: TileKey): void {\n const tile = this.tiles.get(key ?? DATA);\n if (!tile) return;\n tile.values = values;\n this.map.engine.setFeatureValues(\n this.id,\n values ?? new Float32Array(tile.properties.length).fill(Number.NaN),\n tile.batch,\n );\n this.map.requestRender();\n }\n\n /**\n * Colors the buildings by the node values of a network layer, recolored with it: each building takes min(value(A)\n * + cost from A, value(B) + cost from B) from its anchors (`setAnchors`), through the ramp, up to `max` (above, or\n * unreached: its own color). Undefined: back to the buildings' own values.\n */\n linkNetwork(network: NetworkLayer | undefined, max = Number.POSITIVE_INFINITY): void {\n this.network = network && { layer: network, max };\n this.sendLink(this.map.engine, this.id);\n this.map.requestRender();\n }\n\n /** Per building of a tile (or of the data): node A, node B, cost from A, cost from B (4 floats each) */\n setAnchors(anchors: Float32Array, key?: TileKey): void {\n const tile = this.tiles.get(key ?? DATA);\n if (!tile) return;\n tile.anchors = anchors;\n this.map.engine.setExtrusionAnchors(this.id, tile.batch, anchors);\n this.map.requestRender();\n }\n\n private put(\n key: TileKey | typeof DATA,\n properties: P[],\n features: Feature<P>[] | undefined,\n arrays: PolygonArrays | undefined,\n ): void {\n const old = this.tiles.get(key);\n const tile: Tile<P> = {\n batch: old?.batch ?? (key === DATA ? 0 : this.nextBatch++),\n properties,\n features,\n arrays,\n values: undefined,\n anchors: undefined,\n };\n this.tiles.set(key, tile);\n this.sendTile(this.map.engine, this.id, tile);\n this.map.requestRender();\n }\n\n private mask(tile: Tile<P>): Uint8Array {\n const { filter } = this.style;\n const mask = new Uint8Array(tile.properties.length).fill(255);\n if (filter)\n tile.properties.forEach((p, i) => {\n if (!filter(p, i)) mask[i] = 0;\n });\n return mask;\n }\n\n private sendTile(engine: Engine, id: number, tile: Tile<P>): void {\n const g = tile.arrays ?? polygonGeometry(tile.features ?? []);\n const n = tile.properties.length;\n engine.setExtrusionBatch(id, tile.batch, g.points, g.ringStart, g.polygonStart, g.feature, n);\n this.sendStyle(engine, id, tile);\n if (tile.values) engine.setFeatureValues(id, tile.values, tile.batch);\n if (tile.anchors) engine.setExtrusionAnchors(id, tile.batch, tile.anchors);\n }\n\n private sendStyle(engine: Engine, id: number, tile: Tile<P>): void {\n const { color, height, base } = this.style;\n const out = new Float32Array(tile.properties.length * FLOATS);\n // Constant colors parsed once: a tile has thousands of buildings\n const fixed = typeof color === 'string' ? parseColor(color) : undefined;\n tile.properties.forEach((p, i) => {\n const o = i * FLOATS;\n out.set(fixed ?? parseColor(evaluate(color, p, i)), o);\n out.set(stops(evaluate(height, p, i)), o + 8);\n out.set(stops(evaluate(base, p, i)), o + 16);\n });\n engine.setFeatureStyle(id, out, tile.batch);\n engine.setFeatureMask(id, this.mask(tile), tile.batch);\n }\n\n private sendLayer(engine: Engine, id: number): void {\n const { ramp, opacity, minZoom, maxZoom, visible } = this.style;\n const r = ramp && rampTexels(ramp);\n engine.setFeatureRamp(id, r?.rgba, r?.min ?? 0, r?.max ?? 1);\n engine.setFeatureLayer(id, Float32Array.from(stops(opacity)), minZoom, maxZoom, visible);\n }\n\n private sendLink(engine: Engine, id: number): void {\n const n = this.network;\n // A network removed (or not created) links to nothing\n const network = n && n.layer.id >= 0 ? n.layer.id : undefined;\n engine.linkExtrusionNetwork(id, network, n ? Math.min(n.max, 1e29) : 0);\n }\n\n /** @internal */\n protected create(engine: Engine): number {\n const id = engine.addExtrusions();\n this.sendLayer(engine, id);\n for (const tile of this.tiles.values()) this.sendTile(engine, id, tile);\n // A network created before (on a new engine, the map relinks once every layer is there)\n if (!this.network || this.network.layer.id >= 0) this.sendLink(engine, id);\n return id;\n }\n\n /** @internal On a new engine, the network may come after the buildings: linked once both are there */\n override relink(engine: Engine): void {\n if (this.network) this.sendLink(engine, this.id);\n }\n}\n\nexport function extrusionStyle<P>(options: ExtrusionOptions<P>): ExtrusionStyle<P> {\n const { data: _, before: __, ...rest } = options;\n return {\n opacity: 1,\n minZoom: 0,\n maxZoom: 24,\n visible: true,\n filter: undefined,\n ramp: undefined,\n color: '#e4e1dc',\n height: 6,\n base: 0,\n ...rest,\n };\n}\n"]}
|