@tileflow/core 0.1.0-alpha.34 → 0.1.0-alpha.36
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/README.md +24 -23
- package/THIRD_PARTY_NOTICES.md +100 -6
- package/dist/browser.d.ts +758 -388
- package/dist/browser.js +4598 -1169
- package/dist/build.d.ts +9 -5
- package/dist/build.js +2892 -1410
- package/dist/capture.js +1 -0
- package/dist/config-reference.json +3780 -2153
- package/dist/{icon-composition-DqoZWQuz.d.ts → icon-composition-sN3JkXdb.d.ts} +380 -96
- package/dist/index.d.ts +426 -13
- package/dist/index.js +4999 -1841
- package/dist/{manifest-types-hmzy2bBX.d.ts → manifest-types-jrBdBrES.d.ts} +71 -9
- package/dist/manifest.d.ts +2 -2
- package/dist/manifest.js +80 -2
- package/dist/native-profile.js +1 -0
- package/dist/native.d.ts +13 -3
- package/dist/native.js +714 -318
- package/dist/runtime.d.ts +1 -1
- package/dist/runtime.js +124 -6
- package/dist/worker/buildings-3d-worker.global.js +1 -0
- package/docs/authoring-model.md +12 -3
- package/docs/boundary-areas-and-choropleths.md +217 -0
- package/docs/buildings-3d.md +264 -34
- package/docs/cartographic-defaults.md +142 -0
- package/docs/compiled-style-performance.md +14 -3
- package/docs/data-is-separate-from-design.md +191 -7
- package/docs/hosted-session-authorization.md +2 -4
- package/docs/map-and-terrain-examples.md +10 -3
- package/docs/native-artifact-profile.md +5 -0
- package/docs/native-resource-urls.md +5 -3
- package/docs/street-lights.md +22 -6
- package/docs/sun-and-light.md +42 -2
- package/docs/theme-transitions-and-blends.md +50 -0
- package/docs/themes-and-module-styles.md +66 -0
- package/docs/trees-3d.md +85 -13
- package/package.json +5 -4
package/dist/runtime.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { C as TileflowAnalytics, a2 as TileflowManifestFetchOptions, a3 as TileflowManifestLoadOptions, a4 as TileflowMapMode, a5 as TileflowMapModeOptions, ai as TileflowResolvedRuntimeTheme, aG as TileflowRuntimeCenterLike, a as TileflowRuntimeColorScheme, T as TileflowRuntimeManifest, aH as TileflowRuntimeManifestMap, b as TileflowRuntimeManifestMapEntry, c as TileflowRuntimeManifestTheme, aI as TileflowRuntimeSource, aJ as TileflowRuntimeSourceValidation, aK as TileflowRuntimeStyle, aL as TileflowRuntimeStyleOptions, d as TileflowRuntimeSystemThemes, aM as TileflowRuntimeViewOptions, aN as TileflowSessionController, aO as TileflowSessionGrantResponse, aR as TileflowStyleFontFace, ba as TileflowThemeBlendSelection, bh as TileflowThemeSelection, e as TileflowTimeOfDay, f as TileflowTimeOfDayVariants, bp as assertValidTileflowRuntimeSource, bq as clearTileflowManifestCache, bs as createTileflowSessionController, bt as createTileflowSessionId, bu as defaultTileflowManifestUrl, bv as defaultTileflowRuntimeView, bx as getTileflowAnalyticsApiUrl, by as getTileflowStyleFontFaces, bz as inferTileflowAnalyticsFromStyleUrl, bA as isTileflowTimeOfDay, bB as loadTileflowManifest, bC as mergeTileflowAnalytics, bD as nearestTileflowBlendTheme, bE as normalizeTileflowRuntimeCenter, bF as normalizeTileflowStaticImageSize, c2 as normalizeTileflowSurfaceId, bG as normalizeTileflowUrl, bH as resolveTileflowAnalyticsRequestUrl, bI as resolveTileflowManifestMap, bJ as resolveTileflowMapMode, bK as resolveTileflowRuntimeStyle, bL as resolveTileflowRuntimeTheme, bM as resolveTileflowRuntimeThemeBlend, bN as resolveTileflowRuntimeView, bO as resolveTileflowStaticImageUrl, bP as shouldLoadTileflowManifest, bQ as startTileflowSession, bS as tileflowStyleFontFaceLimits, bT as tileflowStyleFontFacesMetadataKey, bU as tileflowThemeBlendLimits, bW as tileflowTimesOfDay, bY as validateTileflowRuntimeSource, bZ as validateTileflowThemeBlendSelection, b_ as validateTileflowThemeSelection } from './manifest-types-jrBdBrES.js';
|
package/dist/runtime.js
CHANGED
|
@@ -29,12 +29,70 @@ var init_portable_identity_rules = __esm({
|
|
|
29
29
|
}
|
|
30
30
|
});
|
|
31
31
|
|
|
32
|
+
// src/time-of-day.ts
|
|
33
|
+
function isTileflowTimeOfDay(value) {
|
|
34
|
+
return typeof value === "string" && tileflowTimesOfDay.includes(value);
|
|
35
|
+
}
|
|
36
|
+
function validateTileflowTimeOfDayVariants(variants, themes) {
|
|
37
|
+
const issues = [];
|
|
38
|
+
const owners = /* @__PURE__ */ new Map();
|
|
39
|
+
const families = Object.entries(variants);
|
|
40
|
+
for (const [family, moments] of families) {
|
|
41
|
+
if (!isTileflowThemeName(family) || !Object.hasOwn(themes, family)) {
|
|
42
|
+
issues.push({ path: [family], message: "Time-of-day family must name an existing theme" });
|
|
43
|
+
}
|
|
44
|
+
if (!Object.values(moments).includes(family)) {
|
|
45
|
+
issues.push({ path: [family], message: "A family must include its own theme in its presets" });
|
|
46
|
+
}
|
|
47
|
+
for (const [moment, name] of Object.entries(moments)) {
|
|
48
|
+
if (name === void 0) continue;
|
|
49
|
+
if (!isTileflowTimeOfDay(moment) || !isTileflowThemeName(name) || !Object.hasOwn(themes, name)) {
|
|
50
|
+
issues.push({
|
|
51
|
+
path: [family, moment],
|
|
52
|
+
message: "Expected a supported moment and an existing theme"
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
const owner = owners.get(name);
|
|
56
|
+
if (owner !== void 0 && owner !== family) {
|
|
57
|
+
issues.push({
|
|
58
|
+
path: [family, moment],
|
|
59
|
+
message: `Theme "${name}" already belongs to family "${owner}"`
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
owners.set(name, family);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return issues;
|
|
66
|
+
}
|
|
67
|
+
function resolveTileflowTimeOfDayTheme(variants, theme, timeOfDay) {
|
|
68
|
+
if (timeOfDay === void 0 || variants === void 0) return theme;
|
|
69
|
+
if (!isTileflowTimeOfDay(timeOfDay)) throw new TypeError("Expected day, dawn, dusk, or night");
|
|
70
|
+
const family = Object.hasOwn(variants, theme) ? variants[theme] : Object.values(variants).find((moments) => Object.values(moments).includes(theme));
|
|
71
|
+
if (!family) return theme;
|
|
72
|
+
const result = family[timeOfDay];
|
|
73
|
+
if (result === void 0) {
|
|
74
|
+
throw new Error(
|
|
75
|
+
`Theme "${theme}" does not offer timeOfDay="${timeOfDay}". Available: ${Object.keys(family).join(", ")}.`
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
return result;
|
|
79
|
+
}
|
|
80
|
+
var tileflowTimesOfDay;
|
|
81
|
+
var init_time_of_day = __esm({
|
|
82
|
+
"src/time-of-day.ts"() {
|
|
83
|
+
"use strict";
|
|
84
|
+
init_portable_identity_rules();
|
|
85
|
+
tileflowTimesOfDay = ["day", "dawn", "dusk", "night"];
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
|
|
32
89
|
// src/manifest-types.ts
|
|
33
|
-
var tileflowRuntimeManifestVersion, tileflowRuntimeManifestLimits;
|
|
90
|
+
var tileflowRuntimeManifestVersion, tileflowTimeOfDayManifestVersion, tileflowRuntimeManifestLimits;
|
|
34
91
|
var init_manifest_types = __esm({
|
|
35
92
|
"src/manifest-types.ts"() {
|
|
36
93
|
"use strict";
|
|
37
94
|
tileflowRuntimeManifestVersion = 1;
|
|
95
|
+
tileflowTimeOfDayManifestVersion = 2;
|
|
38
96
|
tileflowRuntimeManifestLimits = Object.freeze({ maximumBytes: 1024 * 1024 });
|
|
39
97
|
}
|
|
40
98
|
});
|
|
@@ -103,6 +161,24 @@ var init_model = __esm({
|
|
|
103
161
|
}
|
|
104
162
|
});
|
|
105
163
|
|
|
164
|
+
// src/time-of-day-schema.ts
|
|
165
|
+
function createTileflowTimeOfDaySchema(operations, themeName) {
|
|
166
|
+
return operations.record(
|
|
167
|
+
themeName,
|
|
168
|
+
operations.object({
|
|
169
|
+
day: operations.optional(themeName),
|
|
170
|
+
dawn: operations.optional(themeName),
|
|
171
|
+
dusk: operations.optional(themeName),
|
|
172
|
+
night: operations.optional(themeName)
|
|
173
|
+
})
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
var init_time_of_day_schema = __esm({
|
|
177
|
+
"src/time-of-day-schema.ts"() {
|
|
178
|
+
"use strict";
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
|
|
106
182
|
// src/manifest-schema.ts
|
|
107
183
|
function createTileflowRuntimeManifestSchema(primitives, operations, identities = createPortableIdentitySchemas(operations)) {
|
|
108
184
|
const { tileflowPortableIdSchema: tileflowPortableIdSchema2, tileflowThemeNameSchema: tileflowThemeNameSchema2 } = identities;
|
|
@@ -158,12 +234,24 @@ function createTileflowRuntimeManifestSchema(primitives, operations, identities
|
|
|
158
234
|
})
|
|
159
235
|
),
|
|
160
236
|
themes: operations.record(tileflowThemeNameSchema2, manifestThemeSchema),
|
|
237
|
+
timeOfDay: operations.optional(
|
|
238
|
+
createTileflowTimeOfDaySchema(operations, tileflowThemeNameSchema2)
|
|
239
|
+
),
|
|
161
240
|
usageMode: operations.optional(operations.literal("session")),
|
|
162
241
|
view: operations.optional(viewSchema),
|
|
163
242
|
worldGeneration: operations.optional(operations.literal("v1"))
|
|
164
243
|
}),
|
|
165
244
|
(entry, context) => {
|
|
166
245
|
const names = Object.keys(entry.themes);
|
|
246
|
+
if (entry.timeOfDay) {
|
|
247
|
+
for (const issue of validateTileflowTimeOfDayVariants(entry.timeOfDay, entry.themes)) {
|
|
248
|
+
context.addIssue({
|
|
249
|
+
code: "custom",
|
|
250
|
+
message: issue.message,
|
|
251
|
+
path: ["timeOfDay", ...issue.path]
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
}
|
|
167
255
|
if (names.length === 0) {
|
|
168
256
|
context.addIssue({
|
|
169
257
|
code: "custom",
|
|
@@ -217,10 +305,23 @@ function createTileflowRuntimeManifestSchema(primitives, operations, identities
|
|
|
217
305
|
operations.object({
|
|
218
306
|
apiUrl: operations.optional(apiOriginSchema),
|
|
219
307
|
maps: operations.record(tileflowPortableIdSchema2, manifestMapSchema),
|
|
220
|
-
version: operations.
|
|
308
|
+
version: operations.union(
|
|
309
|
+
operations.literal(tileflowRuntimeManifestVersion),
|
|
310
|
+
operations.literal(2)
|
|
311
|
+
)
|
|
221
312
|
}),
|
|
222
313
|
(manifest, context) => {
|
|
223
314
|
const mapCount = Object.keys(manifest.maps).length;
|
|
315
|
+
if (manifest.version === 1) {
|
|
316
|
+
for (const [name, map] of Object.entries(manifest.maps)) {
|
|
317
|
+
if (map.timeOfDay !== void 0)
|
|
318
|
+
context.addIssue({
|
|
319
|
+
code: "custom",
|
|
320
|
+
message: "timeOfDay requires manifest version 2",
|
|
321
|
+
path: ["maps", name, "timeOfDay"]
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
}
|
|
224
325
|
if (mapCount === 0) {
|
|
225
326
|
context.addIssue({ code: "custom", message: "Expected at least one map", path: ["maps"] });
|
|
226
327
|
}
|
|
@@ -333,6 +434,8 @@ var init_manifest_schema = __esm({
|
|
|
333
434
|
init_portable_identity_schema();
|
|
334
435
|
init_runtime_view_schema();
|
|
335
436
|
init_model();
|
|
437
|
+
init_time_of_day();
|
|
438
|
+
init_time_of_day_schema();
|
|
336
439
|
}
|
|
337
440
|
});
|
|
338
441
|
|
|
@@ -387,6 +490,7 @@ var init_manifest_schema_classic = __esm({
|
|
|
387
490
|
return z2.enum(values);
|
|
388
491
|
},
|
|
389
492
|
literal: (value) => z2.literal(value),
|
|
493
|
+
union: (left, right) => z2.union([classic(left), classic(right)]),
|
|
390
494
|
tuplePair(left, right) {
|
|
391
495
|
return z2.tuple([classic(left), classic(right)]);
|
|
392
496
|
},
|
|
@@ -422,7 +526,8 @@ __export(manifest_exports, {
|
|
|
422
526
|
safeParseTileflowRuntimeManifest: () => safeParseTileflowRuntimeManifest,
|
|
423
527
|
tileflowRuntimeManifestLimits: () => tileflowRuntimeManifestLimits,
|
|
424
528
|
tileflowRuntimeManifestSchema: () => tileflowRuntimeManifestSchema,
|
|
425
|
-
tileflowRuntimeManifestVersion: () => tileflowRuntimeManifestVersion
|
|
529
|
+
tileflowRuntimeManifestVersion: () => tileflowRuntimeManifestVersion,
|
|
530
|
+
tileflowTimeOfDayManifestVersion: () => tileflowTimeOfDayManifestVersion
|
|
426
531
|
});
|
|
427
532
|
function parseTileflowRuntimeManifest(input) {
|
|
428
533
|
return tileflowRuntimeManifestSchema.parse(input);
|
|
@@ -451,6 +556,8 @@ var init_manifest = __esm({
|
|
|
451
556
|
|
|
452
557
|
// src/runtime.ts
|
|
453
558
|
init_portable_identity_rules();
|
|
559
|
+
init_time_of_day();
|
|
560
|
+
init_time_of_day();
|
|
454
561
|
var defaultTileflowManifestUrl = "/tileflow/manifest.json";
|
|
455
562
|
var defaultTileflowRuntimeView = Object.freeze({
|
|
456
563
|
bearing: 0,
|
|
@@ -532,7 +639,8 @@ function resolveTileflowRuntimeStyle(options) {
|
|
|
532
639
|
const theme = resolveTileflowRuntimeTheme(
|
|
533
640
|
options.manifestMap,
|
|
534
641
|
options.theme,
|
|
535
|
-
options.colorScheme
|
|
642
|
+
options.colorScheme,
|
|
643
|
+
options.timeOfDay
|
|
536
644
|
);
|
|
537
645
|
return {
|
|
538
646
|
analytics: {
|
|
@@ -549,7 +657,7 @@ function resolveTileflowRuntimeStyle(options) {
|
|
|
549
657
|
}
|
|
550
658
|
return null;
|
|
551
659
|
}
|
|
552
|
-
function resolveTileflowRuntimeTheme(map, selection, colorScheme) {
|
|
660
|
+
function resolveTileflowRuntimeTheme(map, selection, colorScheme, timeOfDay) {
|
|
553
661
|
let name;
|
|
554
662
|
if (selection === void 0) {
|
|
555
663
|
name = map.defaultTheme;
|
|
@@ -571,6 +679,8 @@ function resolveTileflowRuntimeTheme(map, selection, colorScheme) {
|
|
|
571
679
|
`Tileflow resolved invalid theme ${JSON.stringify(name)} for map "${map.name}"; expected a concrete portable theme name.`
|
|
572
680
|
);
|
|
573
681
|
}
|
|
682
|
+
if (Object.hasOwn(map.themes, name))
|
|
683
|
+
name = resolveTileflowTimeOfDayTheme(map.timeOfDay, name, timeOfDay);
|
|
574
684
|
const theme = Object.hasOwn(map.themes, name) ? map.themes[name] : void 0;
|
|
575
685
|
if (!theme) {
|
|
576
686
|
const available = Object.keys(map.themes).sort().join(", ");
|
|
@@ -1106,6 +1216,7 @@ function resolveTileflowManifestMap(manifest, mapName) {
|
|
|
1106
1216
|
defaultTheme: entry.defaultTheme,
|
|
1107
1217
|
...entry.environment ? { environment: entry.environment } : {},
|
|
1108
1218
|
...entry.systemThemes ? { systemThemes: { ...entry.systemThemes } } : {},
|
|
1219
|
+
...entry.timeOfDay ? { timeOfDay: entry.timeOfDay } : {},
|
|
1109
1220
|
themes: entry.themes,
|
|
1110
1221
|
usageMode: entry.usageMode,
|
|
1111
1222
|
view: entry.view,
|
|
@@ -1123,7 +1234,12 @@ function resolveTileflowStaticImageUrl(input) {
|
|
|
1123
1234
|
const apiUrl = normalizeTileflowUrl(input.manifestMap.apiUrl ?? "https://api.tileflow.dev");
|
|
1124
1235
|
const mapId = encodeURIComponent(input.manifestMap.mapId);
|
|
1125
1236
|
const url = new URL(`/maps/${mapId}/static.png`, apiUrl);
|
|
1126
|
-
const theme = resolveTileflowRuntimeTheme(
|
|
1237
|
+
const theme = resolveTileflowRuntimeTheme(
|
|
1238
|
+
input.manifestMap,
|
|
1239
|
+
input.theme,
|
|
1240
|
+
input.colorScheme,
|
|
1241
|
+
input.timeOfDay
|
|
1242
|
+
);
|
|
1127
1243
|
url.searchParams.set("center", input.center.join(","));
|
|
1128
1244
|
url.searchParams.set("theme", theme.name);
|
|
1129
1245
|
url.searchParams.set("zoom", String(input.zoom));
|
|
@@ -1291,6 +1407,7 @@ export {
|
|
|
1291
1407
|
getTileflowAnalyticsApiUrl,
|
|
1292
1408
|
getTileflowStyleFontFaces,
|
|
1293
1409
|
inferTileflowAnalyticsFromStyleUrl,
|
|
1410
|
+
isTileflowTimeOfDay,
|
|
1294
1411
|
loadTileflowManifest,
|
|
1295
1412
|
mergeTileflowAnalytics,
|
|
1296
1413
|
nearestTileflowBlendTheme,
|
|
@@ -1311,6 +1428,7 @@ export {
|
|
|
1311
1428
|
tileflowStyleFontFaceLimits,
|
|
1312
1429
|
tileflowStyleFontFacesMetadataKey,
|
|
1313
1430
|
tileflowThemeBlendLimits,
|
|
1431
|
+
tileflowTimesOfDay,
|
|
1314
1432
|
validateTileflowRuntimeSource,
|
|
1315
1433
|
validateTileflowThemeBlendSelection,
|
|
1316
1434
|
validateTileflowThemeSelection
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";(()=>{var An=new Set,Dn=!1;function kn(n,e,t=2){let s=e&&e.length,o=s?e[0]*t:n.length;An.size&&An.clear();let r=he(n,0,o,t,!0),i=[];if(!r||r.next===r.prev)return i;let u=0,l=0,c=0;if(s&&(r=_e(n,e,r,t)),n.length>80*t){u=n[0],l=n[1];let a=u,f=l;for(let h=t;h<o;h+=t){let m=n[h],b=n[h+1];m<u&&(u=m),b<l&&(l=b),m>a&&(a=m),b>f&&(f=b)}c=Math.max(a-u,f-l),c=c!==0?32767/c:0}return Wn(r,i,u,l,c),i}function he(n,e,t,s,o){let r=null;if(o===Ye(n,e,t,s)>0)for(let i=e;i<t;i+=s)r=fe(i/s|0,n[i],n[i+1],r);else for(let i=t-s;i>=e;i-=s)r=fe(i/s|0,n[i],n[i+1],r);return r&&In(r,r.next)&&(Tn(r),r=r.next),r}function fn(n,e=n){let t=e===n,s=n,o;do o=!1,s!==s.next&&(An.size===0||!An.has(s))&&(In(s,s.next)||O(s.prev,s,s.next)===0)?((t||s===e)&&(e=s.prev),Dn=!0,Tn(s),s=s.prev,o=!0):(t||s!==e)&&(s=s.next,o=!t);while(o||s!==e);return e}function Wn(n,e,t,s,o){o&&Se(n,t,s,o);let r=n,i=!1;for(;n.prev!==n.next;){let u=n.prev,l=n.next;if(O(u,n,l)<0&&(o?ke(n,t,s,o):Te(n))){e.push(u.i,n.i,l.i),Tn(n),n=l,r=l;continue}if(n=l,n===r){if(Dn=!1,n=fn(n),Dn){r=n;continue}if(!i){n=Be(n,e),r=n,i=!0;continue}Fe(n,e,t,s,o);break}}}function Te(n){let e=n.prev,t=n,s=n.next,o=e.x,r=t.x,i=s.x,u=e.y,l=t.y,c=s.y,a=Math.min(o,r,i),f=Math.min(u,l,c),h=Math.max(o,r,i),m=Math.max(u,l,c),b=s.next;for(;b!==e;){if(b.x>=a&&b.x<=h&&b.y>=f&&b.y<=m&&!(o===b.x&&u===b.y)&&Vn(o,u,r,l,i,c,b.x,b.y)&&O(b.prev,b,b.next)>=0)return!1;b=b.next}return!0}function ke(n,e,t,s){let o=n.prev,r=n,i=n.next,u=o.x,l=r.x,c=i.x,a=o.y,f=r.y,h=i.y,m=Math.min(u,l,c),b=Math.min(a,f,h),d=Math.max(u,l,c),x=Math.max(a,f,h),I=$n(m,b,e,t,s),B=$n(d,x,e,t,s),M=n.prevZ;for(;M&&M.z>=I;){if(M.x>=m&&M.x<=d&&M.y>=b&&M.y<=x&&M!==i&&!(u===M.x&&a===M.y)&&Vn(u,a,l,f,c,h,M.x,M.y)&&O(M.prev,M,M.next)>=0)return!1;M=M.prevZ}let y=n.nextZ;for(;y&&y.z<=B;){if(y.x>=m&&y.x<=d&&y.y>=b&&y.y<=x&&y!==i&&!(u===y.x&&a===y.y)&&Vn(u,a,l,f,c,h,y.x,y.y)&&O(y.prev,y,y.next)>=0)return!1;y=y.nextZ}return!0}function Be(n,e){let t=n,s=!1;do{let o=t.prev,r=t.next.next;xe(o,t,t.next,r,!1)&&Un(o,r)&&Un(r,o)&&(e.push(o.i,t.i,r.i),Tn(t),Tn(t.next),t=n=r,s=!0),t=t.next}while(t!==n);return s?fn(t):t}function Fe(n,e,t,s,o){let r=n;do{let i=r.next.next;for(;i!==r.prev;){if(r.i!==i.i&&He(r,i)){let u=de(r,i);r=fn(r,r.next),u=fn(u,u.next),Wn(r,e,t,s,o),Wn(u,e,t,s,o);return}i=i.next}r=r.next}while(r!==n)}var Kn=!1;function _e(n,e,t,s){let o=[];for(let r=0,i=e.length;r<i;r++){let u=e[r]*s,l=r<i-1?e[r+1]*s:n.length,c=he(n,u,l,s,!1);c===c.next&&An.add(c),o.push(Ee(c))}o.sort(Pe),Le(n.length/s,e.length),be(t,t),Kn=!0;for(let r=0;r<o.length;r++)t=Oe(o[r],t);return Kn=!1,fn(t)}function Pe(n,e){return n.x-e.x||n.y-e.y||(n.next.y-n.y)/(n.next.x-n.x)-(e.next.y-e.y)/(e.next.x-e.x)}function Oe(n,e){let t=ze(n,e);if(!t)return e;let s=de(t,n),o=s.next;return be(t,o.next),fn(s,s.next),fn(t,t.next)}var me=16,_=new Float64Array(0),Nn=0,Jn=[],Qn=[];function Le(n,e){let t=Math.ceil((n+2*e)/me)+e+2;_.length<t*4&&(_=new Float64Array(t*4)),Nn=0}function be(n,e){let t=n;do{let s=Nn++;Jn[s]=t;let o=t.x,r=t.y,i=t.x,u=t.y,l=0;do{let a=t.next;t.z=s,a.x<o&&(o=a.x),a.x>i&&(i=a.x),a.y<r&&(r=a.y),a.y>u&&(u=a.y),t=a}while(++l<me&&t!==e);Qn[s]=t;let c=s*4;_[c]=o,_[c+1]=r,_[c+2]=i,_[c+3]=u}while(t!==e)}function Ce(n,e){let t=n.z*4;e.x<_[t]&&(_[t]=e.x),e.y<_[t+1]&&(_[t+1]=e.y),e.x>_[t+2]&&(_[t+2]=e.x),e.y>_[t+3]&&(_[t+3]=e.y)}function ce(n){let e=Qn[n];for(;e.prev.next!==e;)e=e.next;return Qn[n]=e,e}function ae(n){let e=Jn[n];for(;e.prev.next!==e;)e=e.next;return Jn[n]=e,e}function ze(n,e){let t=e,s=n.x,o=n.y,r=-1/0,i;if(In(n,t))return t;for(let h=0,m=0;h<Nn;h++,m+=4){if(o<_[m+1]||o>_[m+3]||_[m]>s||_[m+2]<=r)continue;let b=ce(h);t=ae(h);do{if(t.prev.next===t){if(In(n,t.next))return t.next;if(o<=t.y&&o>=t.next.y&&t.next.y!==t.y){let d=t.x+(o-t.y)*(t.next.x-t.x)/(t.next.y-t.y);if(d<=s&&d>r&&(r=d,i=t.x<t.next.x?t:t.next,d===s))return i}}t=t.next}while(t!==b)}if(!i)return null;let u=i.x,l=i.y,c=Math.min(o,l),a=Math.max(o,l),f=1/0;for(let h=0,m=0;h<Nn;h++,m+=4){if(_[m+2]<u||_[m]>s||_[m+3]<c||_[m+1]>a)continue;let b=ce(h);t=ae(h);do{if(t.prev.next===t&&s>=t.x&&t.x>=u&&s!==t.x&&Vn(o<l?s:r,o,u,l,o<l?r:s,o,t.x,t.y)){let d=Math.abs(o-t.y)/(s-t.x);(Un(t,n)||t.y===o&&t.next.y===o&&t.next.x>s)&&(d<f||d===f&&(t.x>i.x||t.x===i.x&&Ze(i,t)))&&(i=t,f=d)}t=t.next}while(t!==b)}return i}function Ze(n,e){return O(n.prev,n,e.prev)<0&&O(e.next,n,n.next)<0}var K=[],dn=[],an=new Uint32Array(0),vn=new Uint32Array(0),Rn=new Uint32Array(256);function Se(n,e,t,s){let o=n,r=0;do o.z=$n(o.x,o.y,e,t,s),K[r++]=o,o=o.next;while(o!==n);je(r);let i=null;for(let u=0;u<r;u++){let l=K[u];l.prevZ=i,i&&(i.nextZ=l),i=l}i.nextZ=null,K.fill(null,0,r),dn.fill(null,0,r)}function je(n){if(n<=32){for(let e=1;e<n;e++){let t=K[e],s=t.z,o=e-1;for(;o>=0&&K[o].z>s;)K[o+1]=K[o],o--;K[o+1]=t}return}an.length<n&&(an=new Uint32Array(n),vn=new Uint32Array(n),dn=new Array(n));for(let e=0;e<n;e++)an[e]=K[e].z;En(n,K,an,dn,vn,0),En(n,dn,vn,K,an,8),En(n,K,an,dn,vn,16),En(n,dn,vn,K,an,24)}function En(n,e,t,s,o,r){Rn.fill(0);for(let u=0;u<n;u++)Rn[t[u]>>>r&255]++;let i=0;for(let u=0;u<256;u++){let l=Rn[u];Rn[u]=i,i+=l}for(let u=0;u<n;u++){let l=t[u],c=Rn[l>>>r&255]++;s[c]=e[u],o[c]=l}}function $n(n,e,t,s,o){return n=(n-t)*o|0,e=(e-s)*o|0,n=(n|n<<8)&16711935,n=(n|n<<4)&252645135,n=(n|n<<2)&858993459,n=(n|n<<1)&1431655765,e=(e|e<<8)&16711935,e=(e|e<<4)&252645135,e=(e|e<<2)&858993459,e=(e|e<<1)&1431655765,n|e<<1}function Ee(n){let e=n,t=n;do(e.x<t.x||e.x===t.x&&e.y<t.y)&&(t=e),e=e.next;while(e!==n);return t}function Vn(n,e,t,s,o,r,i,u){return(o-i)*(e-u)>=(n-i)*(r-u)&&(n-i)*(s-u)>=(t-i)*(e-u)&&(t-i)*(r-u)>=(o-i)*(s-u)}function He(n,e){let t=In(n,e)&&O(n.prev,n,n.next)>0&&O(e.prev,e,e.next)>0;return n.next.i!==e.i&&(t||Un(n,e)&&Un(e,n)&&(O(n.prev,n,e.prev)!==0||O(n,e.prev,e)!==0))&&!Ne(n,e)&&(t||Ve(n,e))}function O(n,e,t){return(e.y-n.y)*(t.x-e.x)-(e.x-n.x)*(t.y-e.y)}function In(n,e){return n.x===e.x&&n.y===e.y}function xe(n,e,t,s,o=!0){let r=O(n,e,t),i=O(n,e,s),u=O(t,s,n),l=O(t,s,e);return(r>0&&i<0||r<0&&i>0)&&(u>0&&l<0||u<0&&l>0)?!0:o?!!(r===0&&Hn(n,t,e)||i===0&&Hn(n,s,e)||u===0&&Hn(t,n,s)||l===0&&Hn(t,e,s)):!1}function Hn(n,e,t){return e.x<=Math.max(n.x,t.x)&&e.x>=Math.min(n.x,t.x)&&e.y<=Math.max(n.y,t.y)&&e.y>=Math.min(n.y,t.y)}function Ne(n,e){let t=Math.min(n.x,e.x),s=Math.max(n.x,e.x),o=Math.min(n.y,e.y),r=Math.max(n.y,e.y),i=n;do{let u=i.next;if(i.x>s&&u.x>s||i.x<t&&u.x<t||i.y>r&&u.y>r||i.y<o&&u.y<o){i=u;continue}if(i.i!==n.i&&u.i!==n.i&&i.i!==e.i&&u.i!==e.i&&xe(i,u,n,e))return!0;i=u}while(i!==n);return!1}function Un(n,e){return O(n.prev,n,n.next)<0?O(n,e,n.next)>=0&&O(n,n.prev,e)>=0:O(n,e,n.prev)<0||O(n,n.next,e)<0}function Ve(n,e){let t=n,s=!1,o=(n.x+e.x)/2,r=(n.y+e.y)/2;do{let i=t.next;t.y>r!=i.y>r&&o<(i.x-t.x)*(r-t.y)/(i.y-t.y)+t.x&&(s=!s),t=i}while(t!==n);return s}function de(n,e){let t=qn(n.i,n.x,n.y),s=qn(e.i,e.x,e.y),o=n.next,r=e.prev;return n.next=e,e.prev=n,t.next=o,o.prev=t,s.next=t,t.prev=s,r.next=s,s.prev=r,s}function fe(n,e,t,s){let o=qn(n,e,t);return s?(o.next=s.next,o.prev=s,s.next.prev=o,s.next=o):(o.prev=o,o.next=o),o}function Tn(n){n.next.prev=n.prev,n.prev.next=n.next,n.prevZ&&(n.prevZ.nextZ=n.nextZ),n.nextZ&&(n.nextZ.prevZ=n.prevZ),Kn&&Ce(n.prev,n.next)}function qn(n,e,t){return{i:n,x:e,y:t,prev:null,next:null,z:0,prevZ:null,nextZ:null}}function Ye(n,e,t,s){let o=0;for(let r=e,i=t-s;r<t;r+=s)o+=(n[i]-n[r])*(n[r+1]+n[i+1]),i=r;return o}var Xe=.7,Ge=Math.tan(Math.PI/6),De=Math.tan(35*Math.PI/180),We=Math.tan(Math.PI/18),Bn=.65,Fn=.4;function Me(n,e,t,s,o,r,i){let u=String(t.roof_shape??"");if(!u||u==="flat"||u==="sawtooth"||qe(n,i))return;let l=Qe(n);if(!l||Math.abs(ne(n))<Xe*4*l.halfU*l.halfV)return;let c=l.u,a=l.v,f=l.halfU*r,h=l.halfV*r;f<h&&([c,a,f,h]=[a,c,h,f]);let m=ge(t.roof_direction),b=m===void 0?void 0:[Math.sin(m*Math.PI/180),-Math.cos(m*Math.PI/180)];(b!==void 0?Math.abs(b[0]*c[0]+b[1]*c[1])>Math.abs(b[0]*a[0]+b[1]*a[1]):t.roof_orientation==="across")&&u!=="skillion"&&([c,a,f,h]=[a,c,h,f]);let x=Je(u,f,h,b&&[pe(b,c),pe(b,a)]);if(!x)return;let I=Math.max(0,o-s),B=ge(t.roof_height),M,y;if(B!==void 0&&B>0)y=o,M=Math.max(s+Math.min(2,I/2),o-B);else{let p=Math.min(Ke(u,h),I*.6,30);t.height_source==="height"?(y=o,M=o-p):(M=o,y=o+p)}let k=y-M;if(!(k>=.3))return;let[R,N]=l.centre,F=(p,A)=>{let v=(p-R)*r,g=(A-N)*r;return[v*c[0]+g*c[1],v*a[0]+g*a[1]]},S=(p,A)=>[R+(p*c[0]+A*a[0])/r,N+(p*c[1]+A*a[1])/r],J=(p,A)=>{let v=1/0;for(let[g,E,H]of x)v=Math.min(v,g*p+E*A+H);return Math.max(0,v)},Q=(p,A)=>M+k*J(...F(p,A)),L=[];for(let p=0;p+1<e.length;p+=2)L.push(...F(e[p],e[p+1]));ne(L)<0&&nt(L);let C=1.01,$=[-f*C,-h*C,f*C,-h*C,f*C,h*C,-f*C,h*C],V=[];for(let[p,A]of x.entries()){let v=$;for(let[T,j]of x.entries()){if(T===p)continue;let z=A[0]-j[0],nn=A[1]-j[1],en=A[2]-j[2];if(Math.abs(z)+Math.abs(nn)+Math.abs(en)<1e-9){T<p&&(v=[]);continue}if(v=ye(v,z,nn,en),v.length<6)break}if(v.length<6)continue;let g=L,E=v.length/2;for(let T=0;T<E&&g.length>=6;T+=1){let[j,z]=[v[T*2],v[T*2+1]],[nn,en]=[v[(T+1)%E*2],v[(T+1)%E*2+1]];g=ye(g,en-z,j-nn,-(en-z)*j-(j-nn)*z)}if(g.length<6||Math.abs(ne(g))<1e-6)continue;let H=[];for(let T=0;T<g.length;T+=2)H.push(...S(g[T],g[T+1]));let q=kn(H);if(q.length===0)continue;let on=k*A[0],Y=k*A[1],w=on*c[0]+Y*a[0],U=on*c[1]+Y*a[1],rn=Math.hypot(w,U,1);V.push({ring:H,triangles:q,normal:[-w/rn,-U/rn]})}if(V.length!==0)return{eaves:M,ridge:y,faces:V,heightAt:Q,breaks(p,A,v,g){let[E,H]=F(p,A),[q,on]=F(v,g),Y=[];for(let w=0;w<x.length;w+=1)for(let U=w+1;U<x.length;U+=1){let rn=(Z,P)=>(x[w][0]-x[U][0])*Z+(x[w][1]-x[U][1])*P+(x[w][2]-x[U][2]),T=rn(E,H),j=rn(q,on);if(T===j||T*j>0)continue;let z=T/(T-j);if(z<=1e-4||z>=1-1e-4)continue;let nn=E+(q-E)*z,en=H+(on-H)*z;x[w][0]*nn+x[w][1]*en+x[w][2]<=J(nn,en)+1e-6&&Y.push(z)}return[...new Set(Y.map(w=>Math.round(w*1e6)/1e6))].sort((w,U)=>w-U)}}}function Ke(n,e){switch(n){case"skillion":return 2*e*We;case"pyramidal":return e*De;case"gambrel":case"mansard":return e*.8;case"dome":case"onion":case"round":return e;default:return e*Ge}}function Je(n,e,t,s){let o=e,r=t,i=[[0,-1/r,1],[0,1/r,1]],u=c=>c.flatMap(a=>[1,-1].flatMap(f=>{let h=a==="v"?r:o,m=Bn/(Fn*r)*f,b=(1-Bn)/((1-Fn)*r)*f,d=x=>a==="v"?[0,-x]:[-x,0];return[[...d(m),Bn/(Fn*r)*h],[...d(b),Bn+(1-Bn)/((1-Fn)*r)*(h-Fn*r)]]})),l=[.99,.8,.5,.2];switch(n){case"gabled":return i;case"saltbox":{let c=r/3;return[[0,-1/(r-c),1+c/(r-c)],[0,1/(r+c),1-c/(r+c)]]}case"hipped":return[...i,[-1/r,0,o/r],[1/r,0,o/r]];case"half_hipped":return[...i,[-1/r,0,.5+o/r],[1/r,0,.5+o/r]];case"pyramidal":return[...i,[-1/o,0,1],[1/o,0,1]];case"gambrel":return u(["v"]);case"mansard":return u(["v","u"]);case"skillion":{let[c,a]=s??[0,1],f=Math.abs(c)*o+Math.abs(a)*r;return f>0?[[-c/(2*f),-a/(2*f),.5]]:void 0}case"round":return[[0,0,1],...l.flatMap(c=>{let a=Math.sqrt(1-c*c);return[1,-1].map(f=>[0,-f*c/(r*a),1/a])})];case"dome":case"onion":return[[0,0,1],...l.flatMap(c=>{let a=Math.sqrt(1-c*c);return Array.from({length:12},(f,h)=>{let m=h*Math.PI/6;return[-c*Math.cos(m)/(o*a),-c*Math.sin(m)/(r*a),1/a]})})];default:return}}function ye(n,e,t,s){let o=n.length/2,r=[];for(let i=0;i<o;i+=1){let u=n[i*2],l=n[i*2+1],c=n[(i+1)%o*2],a=n[(i+1)%o*2+1],f=e*u+t*l+s,h=e*c+t*a+s;if(f<=0&&r.push(u,l),f<0&&h>0||f>0&&h<0){let m=f/(f-h);r.push(u+(c-u)*m,l+(a-l)*m)}}return r}function Qe(n){let e=$e(n);if(e.length<3)return;let t,s=1/0;for(let o=0;o<e.length;o+=1){let[r,i]=e[o],[u,l]=e[(o+1)%e.length],c=Math.hypot(u-r,l-i);if(c===0)continue;let a=[(u-r)/c,(l-i)/c],f=[-a[1],a[0]],[h,m,b,d]=[1/0,-1/0,1/0,-1/0];for(let[I,B]of e){let M=I*a[0]+B*a[1],y=I*f[0]+B*f[1];h=Math.min(h,M),m=Math.max(m,M),b=Math.min(b,y),d=Math.max(d,y)}let x=(m-h)*(d-b);if(x<s){s=x;let I=(h+m)/2,B=(b+d)/2;t={centre:[I*a[0]+B*f[0],I*a[1]+B*f[1]],u:a,v:f,halfU:(m-h)/2,halfV:(d-b)/2}}}return t&&t.halfU>0&&t.halfV>0?t:void 0}function $e(n){let e=[];for(let r=0;r+1<n.length;r+=2)e.push([n[r],n[r+1]]);e.sort((r,i)=>r[0]-i[0]||r[1]-i[1]);let t=(r,i,u)=>(i[0]-r[0])*(u[1]-r[1])-(i[1]-r[1])*(u[0]-r[0]),s=[];for(let r of e){for(;s.length>=2&&t(s.at(-2),s.at(-1),r)<=0;)s.pop();s.push(r)}let o=[];for(let r of e.reverse()){for(;o.length>=2&&t(o.at(-2),o.at(-1),r)<=0;)o.pop();o.push(r)}return[...s.slice(0,-1),...o.slice(0,-1)]}function qe(n,e){let[t,s,o,r]=[1/0,-1/0,1/0,-1/0];for(let u=0;u+1<n.length;u+=2)t=Math.min(t,n[u]),s=Math.max(s,n[u]),o=Math.min(o,n[u+1]),r=Math.max(r,n[u+1]);let i=n.length/2;for(let u=0;u<i;u+=1){let l=n[u*2],c=n[u*2+1],a=n[(u+1)%i*2],f=n[(u+1)%i*2+1];if(t<0&&l===t&&a===t||s>e&&l===s&&a===s||o<0&&c===o&&f===o||r>e&&c===r&&f===r)return!0}return!1}function ne(n){let e=0,t=n.length/2;for(let s=0;s<t;s+=1){let o=(s+1)%t;e+=n[s*2]*n[o*2+1]-n[o*2]*n[s*2+1]}return e/2}function nt(n){let e=[];for(let t=0;t<n.length;t+=2)e.push([n[t],n[t+1]]);e.reverse(),n.splice(0,n.length,...e.flat())}function pe(n,e){return n[0]*e[0]+n[1]*e[1]}function ge(n){let e=typeof n=="string"?Number(n):n;return typeof e=="number"&&Number.isFinite(e)?e:void 0}var gt=Object.freeze({surface:0,secondary:1,glass:2,accent:3,roof:4}),Mt=Object.freeze({levels:3,vertices:2e5,triangles:2e5,footprintPoints:4096,radius:2e3,height:1e3,models:512,bytes:16*1024*1024});function ee(n,e,t){for(let s of n){let o=!1;for(let r=0,i=s.length-2;r<s.length;i=r,r+=2){let u=s[r],l=s[r+1],c=s[i],a=s[i+1];l>t!=a>t&&e<(c-u)*(t-l)/(a-l)+u&&(o=!o)}if(o)return!0}return!1}function Re(n,e,t){for(let s of e){let[o,r,i,u]=ve(s);for(let l of n){let[c,a,f,h]=ve(l);if(!(c>i-t||f<o+t||a>u-t||h<r+t)&&(we(l,s,t)||we(s,l,t)))return!0}}return!1}function we(n,e,t){let s=(o,r)=>ee([e],o,r)&&tt(e,o,r)>t;for(let o=0;o+1<n.length;o+=2)if(s(n[o],n[o+1]))return!0;for(let o=0,r=n.length-2;o<n.length;r=o,o+=2){let[i,u,l,c]=[n[r],n[r+1],n[o],n[o+1]],a=[0,1];for(let f=0,h=e.length-2;f<e.length;h=f,f+=2){let m=et(i,u,l,c,e[h],e[h+1],e[f],e[f+1]);m!==void 0&&a.push(m)}if(a.length!==2){a.sort((f,h)=>f-h);for(let f=1;f<a.length;f+=1){let h=(a[f-1]+a[f])/2;if(s(i+(l-i)*h,u+(c-u)*h))return!0}}}return!1}function et(n,e,t,s,o,r,i,u){let l=t-n,c=s-e,a=i-o,f=u-r,h=l*f-c*a;if(h===0)return;let m=((o-n)*f-(r-e)*a)/h,b=((o-n)*c-(r-e)*l)/h;return m>0&&m<1&&b>=0&&b<=1?m:void 0}function tt(n,e,t){let s=1/0;for(let o=0,r=n.length-2;o<n.length;r=o,o+=2){let[i,u,l,c]=[n[r],n[r+1],n[o],n[o+1]],a=(l-i)**2+(c-u)**2,f=a>0?Math.max(0,Math.min(1,((e-i)*(l-i)+(t-u)*(c-u))/a)):0;s=Math.min(s,Math.hypot(e-(i+(l-i)*f),t-(u+(c-u)*f)))}return s}function ve(n){let[e,t,s,o]=[1/0,1/0,-1/0,-1/0];for(let r=0;r+1<n.length;r+=2)e=Math.min(e,n[r]),s=Math.max(s,n[r]),t=Math.min(t,n[r+1]),o=Math.max(o,n[r+1]);return[e,t,s,o]}var tn=8192,te=28,ot={commercial:1,active:2,destination:3},rt={residential:1,...Object.fromEntries(["civic","commercial","education","entertainment","medical","transportation"].map(n=>[n,2])),...Object.fromEntries(["agricultural","industrial","military","outbuilding","religious","service"].map(n=>[n,3]))},st={dining:1,entertainment:2,nightlife:3},oe={...Object.fromEntries(["alcoholic_beverage_venue","bar","beer","biergarten","brewery","casino","cocktail_bar","dance_club","karaoke","lounge","music_venue","night_club","nightclub","pub","wine_bar"].map(n=>[n,3])),...Object.fromEntries(["arts_centre","bowling_alley","cinema","comedy_club","concert_hall","movie_theater","opera","performing_arts_venue","theater","theatre","theatre_venue"].map(n=>[n,2])),...Object.fromEntries(["casual_eatery","fast_food","fast_food_restaurant","food_court","restaurant"].map(n=>[n,1]))},ut=6,it=.5,lt=.5;function Ue(n,e){let t=tn/n.extent,s=e.tileMetres/tn,o=e.defaultHeight??5,r=Number.isFinite(e.edgeRadius)?Math.max(0,Math.min(1,e.edgeRadius))/s:0,i=new re,u=new Yn,l=new Yn,c=[],a=[],f=0,h=ht(n,e);for(let[y,k]of n.features.entries()){if(k.type!=="Polygon"||c.length>=65535)continue;let R=k.properties,N=R.hide_3d;if(N===!0||N===1||N==="1"||N==="true")continue;let F=Math.min(6e3,Ae(R.render_height)??o),S=Math.min(F,Ae(R.render_min_height)??0),J=ot[String(R.building_tone??"")]??0,Q=rt[String(R.class??"")]??0,L=h.get(y)??0,C=c.length,$=!1;for(let V of mt(k.geometry,t)){let p=V.map(xt).filter(w=>w.length>=6);if(p.length===0||Xn(p[0])===0)continue;let[A,v]=bt(p[0]);if(e.masks?.length&&(ee(e.masks,A,v)||Re(p,e.masks,lt/s)))continue;let g={cell:Math.min(7,Math.max(0,Math.floor(v/1024)))*8+Math.min(7,Math.max(0,Math.floor(A/1024))),bounds:[1/0,1/0,-1/0,-1/0,F+.1],roofsFrom:u.length,roofsCount:0,wallsFrom:l.length,wallsCount:0};for(let w of p)for(let U=0;U<w.length;U+=2)g.bounds[0]=Math.min(g.bounds[0],w[U]-1),g.bounds[1]=Math.min(g.bounds[1],w[U+1]-1),g.bounds[2]=Math.max(g.bounds[2],w[U]+1),g.bounds[3]=Math.max(g.bounds[3],w[U+1]+1);$=!0,f=Math.max(f,F);let E=Ie(A*.0731+v*.19137+C*1.618),H=p[0].some(w=>w<=0||w>=tn)?Ie(F*7.31+S*3.17+J):E,q={base:S,top:F,jitter:H,seed:E,tone:J,group:Q,use:L,cx:A,cy:v,index:C},on=p.map((w,U)=>ft(w,U,r,e.cornerAngle));p=on.map(({ring:w})=>w);let Y=V.length===1&&p.length===1?Me(V[0],p[0],R,S,F,s,tn):void 0;Y?(f=Math.max(f,Y.ridge),g.bounds[4]=Math.max(g.bounds[4],Y.ridge+.1),at(Y,q,i,u)):ct(p,q,i,u);for(let[w,U]of p.entries()){let rn=Xn(U)>0,T=w===0?rn?-1:1:rn?1:-1,j=U.length/2,z=Z=>{let P=(Z%j+j)%j;return[U[P*2],U[P*2+1]]},nn=Z=>{let[P,D]=z(Z-1),[X,G]=z(Z),[un,mn]=z(Z+1);if(hn(P,D,X,G)||hn(X,G,un,mn))return!1;let bn=Math.hypot(X-P,G-D),ln=Math.hypot(un-X,mn-G);if(!bn||!ln)return!1;let _n=-(G-D)/bn*T,Pn=(X-P)/bn*T;return(_n*(un-X)+Pn*(mn-G))/ln>it},en=(Z,P)=>Y?Y.heightAt(Z,P):F,pn=0;for(let Z=0;Z<j;Z+=1){let[P,D]=z(Z),[X,G]=z(Z+1),un=Math.hypot(X-P,G-D);if(un===0||hn(P,D,X,G))continue;let mn=-(G-D)/un*T,bn=(X-P)/un*T,ln=on[w].normals[Z]??[mn,bn],_n=on[w].normals[(Z+1)%j]??[mn,bn],Pn=W=>{let gn=ln[0]+(_n[0]-ln[0])*W,Mn=ln[1]+(_n[1]-ln[1])*W,cn=Math.hypot(gn,Mn)||1;return[gn/cn,Mn/cn]},sn=[0,...Y?Y.breaks(P,D,X,G):[],1];for(let W=0;W+1<sn.length;W+=1){let gn=W===0,Mn=W+2===sn.length,cn=P+(X-P)*sn[W],On=D+(G-D)*sn[W],Ln=P+(X-P)*sn[W+1],Cn=D+(G-D)*sn[W+1],wn=Math.hypot(Ln-cn,Cn-On)/tn*e.tileMetres,zn=(gn&&nn(Z)?1:0)|(Mn&&nn(Z+1)?2:0)|(w>0?4:0),Zn=(gn&&se(P,D)?1:0)|(Mn&&se(X,G)?2:0),Gn=pn+wn,Sn=Pn(sn[W]),jn=Pn(sn[W+1]),ie=en(cn,On),le=en(Ln,Cn),xn=i.write(q,cn,On,0,Sn[0],Sn[1],1,pn,zn,wn,0,Zn,ie);i.write(q,Ln,Cn,0,jn[0],jn[1],1,Gn,zn,wn,1,Zn,le),i.write(q,cn,On,1,Sn[0],Sn[1],1,pn,zn,wn,0,Zn,ie),i.write(q,Ln,Cn,1,jn[0],jn[1],1,Gn,zn,wn,1,Zn,le),l.push(xn,xn+1,xn+2,xn+1,xn+3,xn+2),pn=Gn}}}g.roofsCount=u.length-g.roofsFrom,g.wallsCount=l.length-g.wallsFrom,a.push(g)}$&&c.push(R)}if(i.count===0)return;let m=new Uint32Array(u.length+l.length),b=[],d=0,x=u.length,I=u.values(),B=l.values();a.sort((y,k)=>y.cell-k.cell);let M=-1;for(let y of a){y.cell!==M&&(M=y.cell,b.push({bounds:[...y.bounds],roofsFrom:d,roofsCount:0,wallsFrom:x,wallsCount:0}));let k=b.at(-1);for(let R=0;R<5;R+=1)k.bounds[R]=R<2?Math.min(k.bounds[R],y.bounds[R]):Math.max(k.bounds[R],y.bounds[R]);m.set(I.subarray(y.roofsFrom,y.roofsFrom+y.roofsCount),d),m.set(B.subarray(y.wallsFrom,y.wallsFrom+y.wallsCount),x),d+=y.roofsCount,x+=y.wallsCount,k.roofsCount+=y.roofsCount,k.wallsCount+=y.wallsCount}return{groups:b,vertices:i.bytes(),indices:m,wallsFrom:u.length,top:f,features:c}}function ct(n,e,t,s){let o=[],r=[];for(let l of n)r.push(o.length/2),o.push(...l);r.push(o.length/2);let i=(l,c)=>{let a=r.findIndex((d,x)=>l>=d&&l<r[x+1]),[f,h]=[r[a],r[a+1]-1];if(c<f||c>h)return!1;let[m,b]=l<c?[l,c]:[c,l];return b-m!==1&&!(m===f&&b===h)?!1:!hn(o[l*2],o[l*2+1],o[c*2],o[c*2+1])},u=kn(o,r.slice(1,-1),2);for(let l=0;l+2<u.length;l+=3){let c=[u[l],u[l+1],u[l+2]],a=0;for(let f=0;f<3;f+=1)i(c[(f+1)%3],c[(f+2)%3])&&(a|=1<<f);for(let[f,h]of c.entries())s.push(t.write(e,o[h*2],o[h*2+1],1,0,0,0,0,0,0,f,a))}}function at(n,e,t,s){for(let o of n.faces){let r=o.ring,i=r.length/2,u=(l,c)=>{let[a,f]=l<c?[l,c]:[c,l];return f-a!==1&&!(a===0&&f===i-1)?!1:!hn(r[l*2],r[l*2+1],r[c*2],r[c*2+1])};for(let l=0;l+2<o.triangles.length;l+=3){let c=[o.triangles[l],o.triangles[l+1],o.triangles[l+2]],a=0;for(let f=0;f<3;f+=1)u(c[(f+1)%3],c[(f+2)%3])&&(a|=1<<f);for(let[f,h]of c.entries()){let m=r[h*2],b=r[h*2+1];s.push(t.write(e,m,b,1,o.normal[0],o.normal[1],0,0,0,0,f,a,n.heightAt(m,b)))}}}}function ft(n,e,t,s=Math.PI/6){if(t<1)return{ring:n,normals:[]};let o=n.length/2,r=Xn(n)>0,i=e===0?r?-1:1:r?1:-1,u=[],l=[],c=(a,f,h)=>{a=yn(a),f=yn(f),!(u.at(-2)===a&&u.at(-1)===f)&&(u.push(a,f),l.push(h))};for(let a=0;a<o;a+=1){let f=(a+o-1)%o,h=(a+1)%o,[m,b]=[n[f*2],n[f*2+1]],[d,x]=[n[a*2],n[a*2+1]],[I,B]=[n[h*2],n[h*2+1]],M=Math.hypot(d-m,x-b),y=Math.hypot(I-d,B-x);if(!M||!y||se(d,x)||hn(m,b,d,x)||hn(d,x,I,B)){c(d,x);continue}let[k,R]=[(d-m)/M,(x-b)/M],[N,F]=[(I-d)/y,(B-x)/y],S=Math.atan2(k*F-R*N,k*N+R*F);if(S*i>=-.08||Math.abs(S)>Math.PI*.85){c(d,x);continue}let J=Math.tan(Math.abs(S)/2),Q=Math.min(t*J,M*.2,y*.2),L=Q/J;if(L<1){c(d,x);continue}let[C,$]=[-R*i,k*i],[V,p]=[d-k*Q-C*L,x-R*Q-$*L],A=Math.atan2($,C),v=Math.max(s<=Math.PI/6?2:1,Math.ceil(Math.abs(S)/Math.max(Math.PI/6,Math.min(Math.PI/3,s))));for(let g=0;g<=v;g+=1){let E=A+S*g/v,H=[Math.cos(E),Math.sin(E)];c(V+H[0]*L,p+H[1]*L,H)}}return u[0]===u.at(-2)&&u[1]===u.at(-1)&&(u.splice(-2),l.pop()),u.length>=6?{ring:u,normals:l}:{ring:n,normals:[]}}var re=class{#n=new ArrayBuffer(4096*te);#e=new DataView(this.#n);count=0;write(e,t,s,o,r,i,u,l,c,a,f=0,h=0,m=e.top){let b=te;if((this.count+1)*b>this.#n.byteLength){let I=new ArrayBuffer(this.#n.byteLength*2);new Uint8Array(I).set(new Uint8Array(this.#n)),this.#n=I,this.#e=new DataView(I)}let d=this.#e,x=this.count*b;return d.setUint16(x,yn(t),!0),d.setUint16(x+2,yn(s),!0),d.setUint16(x+4,Math.min(65535,Math.round(e.base*10)),!0),d.setUint16(x+6,Math.min(65535,Math.max(0,Math.round(m*10))),!0),d.setInt8(x+8,Math.round(r*127)),d.setInt8(x+9,Math.round(i*127)),d.setUint8(x+10,o),d.setUint8(x+11,u),d.setUint16(x+12,Math.round(l*10)%65536,!0),d.setUint8(x+14,Math.round(e.jitter*255)),d.setUint8(x+15,Math.round(e.seed*255)),d.setUint16(x+16,yn(e.cx),!0),d.setUint16(x+18,yn(e.cy),!0),d.setUint8(x+20,e.tone+4*e.group),d.setUint8(x+21,e.use),d.setUint8(x+22,c),d.setUint8(x+23,Math.min(255,Math.round(a))),d.setUint16(x+24,e.index,!0),d.setUint8(x+26,f),d.setUint8(x+27,h),this.count+=1,this.count-1}bytes(){return new Uint8Array(this.#n.slice(0,this.count*te))}},Yn=class{#n=new Uint32Array(8192);length=0;push(...e){if(this.length+e.length>this.#n.length){let t=new Uint32Array(Math.max(this.length+e.length,this.#n.length*2));t.set(this.#n),this.#n=t}for(let t of e)this.#n[this.length++]=t}values(){return this.#n.subarray(0,this.length)}};function ht(n,e){let t=new Map;if(n.features.some(u=>u.properties.night_use!==void 0)){for(let[u,l]of n.features.entries()){let c=st[String(l.properties.night_use??"")];c&&t.set(u,c)}return t}let s=e.places;if(!s||s.features.length===0)return t;let o=ut/e.tileMetres*n.extent,r=[];for(let[u,l]of n.features.entries()){if(l.type!=="Polygon")continue;let c=l.properties.hide_3d;if(c===!0||c===1||c==="1"||c==="true")continue;let a=[1/0,1/0,-1/0,-1/0];for(let f of l.geometry)for(let h=0;h+1<f.length;h+=2)a[0]=Math.min(a[0],f[h]),a[1]=Math.min(a[1],f[h+1]),a[2]=Math.max(a[2],f[h]),a[3]=Math.max(a[3],f[h+1]);r.push({index:u,rings:l.geometry,box:a})}let i=n.extent/s.extent;for(let u of s.features){if(u.type!=="Point")continue;let l=u.properties,c=oe[String(l.type??"")]??oe[String(l.subclass??"")]??oe[String(l.class??"")];if(c)for(let a of u.geometry)for(let f=0;f+1<a.length;f+=2){let h=a[f]*i,m=a[f+1]*i,b,d,x=o;for(let B of r){let[M,y,k,R]=B.box;if(h<M-o||h>k+o||m<y-o||m>R+o)continue;let N=!1;for(let F of B.rings)for(let S=0,J=F.length-2;S+1<F.length;J=S,S+=2){let Q=F[J],L=F[J+1],C=F[S],$=F[S+1];$>m!=L>m&&h<(Q-C)*(m-$)/(L-$)+C&&(N=!N);let V=C-Q,p=$-L,A=V*V+p*p,v=A?Math.min(1,Math.max(0,((h-Q)*V+(m-L)*p)/A)):0,g=Math.hypot(h-Q-v*V,m-L-v*p);g<x&&(x=g,d=B.index)}if(N){b=B.index;break}}let I=b??d;I!==void 0&&t.set(I,Math.max(t.get(I)??0,c))}}return t}function yn(n){return Math.min(tn,Math.max(0,Math.round(n)))}function Ae(n){let e=Number(n);return Number.isFinite(e)&&e>0?e:void 0}function mt(n,e){let t=[],s;for(let o of n){let r=[];for(let u=0;u+1<o.length;u+=2){let l=o[u]*e,c=o[u+1]*e;r.length&&l===r.at(-2)&&c===r.at(-1)||r.push(l,c)}if(r.length>=6&&r[0]===r.at(-2)&&r[1]===r.at(-1)&&(r.length-=2),r.length<6)continue;let i=Math.sign(Xn(r));i&&(s??=i,i===s||t.length===0?t.push([r]):t.at(-1).push(r))}return t}function Xn(n){let e=0,t=n.length/2;for(let s=0;s<t;s+=1){let o=(s+1)%t;e+=n[s*2]*n[o*2+1]-n[o*2]*n[s*2+1]}return e}function bt(n){let e=0,t=0,s=n.length/2;for(let o=0;o<s;o+=1)e+=n[o*2],t+=n[o*2+1];return[e/s,t/s]}function se(n,e){return n<=0||e<=0||n>=tn||e>=tn}function hn(n,e,t,s){let o=tn;return n<=0&&t<=0||n>=o&&t>=o||e<=0&&s<=0||e>=o&&s>=o}function xt(n){let e=tn,t=[...n],s=[[0,0,o=>o>=0],[0,e,o=>o<=e],[1,0,o=>o>=0],[1,e,o=>o<=e]];for(let[o,r,i]of s){let u=[],l=t.length/2;for(let c=0;c<l;c+=1){let a=[t[c*2],t[c*2+1]],f=[t[(c+1)%l*2],t[(c+1)%l*2+1]],h=i(a[o]),m=i(f[o]);if(h&&u.push(a[0],a[1]),h!==m){let b=(r-a[o])/(f[o]-a[o]);u.push(a[0]+(f[0]-a[0])*b,a[1]+(f[1]-a[1])*b)}}if(t=u,t.length<6)return[]}return t}function Ie(n){let e=Math.sin(n*12.9898+78.233)*43758.5453;return e-Math.floor(e)}var ue=globalThis;ue.onmessage=({data:n})=>{try{let e=Ue(n.layer,n.options);ue.postMessage({mesh:e},e?[e.vertices.buffer,e.indices.buffer]:[])}catch(e){ue.postMessage({error:String(e)})}};})();
|
package/docs/authoring-model.md
CHANGED
|
@@ -126,8 +126,8 @@ export default defineMap({
|
|
|
126
126
|
|
|
127
127
|
The standalone map above is complete as written. A map always owns its complete text provider; Core does not
|
|
128
128
|
obtain fonts or sprites from World and never invents a fallback URL. The URL-backed first-party
|
|
129
|
-
`streets`, `
|
|
130
|
-
providers directly. `baedeker`, `civica`, and `siegfried`
|
|
129
|
+
`streets`, `soundings`, and `blueprint` maps declare their glyph
|
|
130
|
+
providers directly. `baedeker`, `civica`, `map1777`, and `siegfried`
|
|
131
131
|
declare packaged fonts, so ordinary imports and derived maps compile without out-of-band release
|
|
132
132
|
metadata.
|
|
133
133
|
|
|
@@ -208,13 +208,22 @@ receive two opaque green fills. Parking areas are polygons from the configured l
|
|
|
208
208
|
parking access aisles remain road features under `roads.serviceTypes.parkingAisle`. The
|
|
209
209
|
`commercial` target recognizes ordinary `commercial` and `retail` values plus Tileflow's derived
|
|
210
210
|
`business_area` ground class. Its `globalLandcover` fill styles the optional low-zoom global
|
|
211
|
-
land-cover extension without a raw layer patch.
|
|
211
|
+
land-cover extension without a raw layer patch. Its `barriers` draw `hedge`, `fence` and `wall`
|
|
212
|
+
from the data's barrier layer (Tileflow World V1's `traffic_control`) at the top of the land band:
|
|
213
|
+
a `line`, a `shadow` line under it, and an `area` fill for the hedges the data maps as areas.
|
|
214
|
+
Nothing is drawn for a barrier without a style, nor for data without the layer. The `water.bathymetry` fill does the same for the
|
|
212
215
|
typed Tileflow World V1 depth bands. An explicit `water({bathymetryContours: {}})` traces the
|
|
213
216
|
submerged edges between those discrete polygon bands; it is an approximate visual contour, not a
|
|
214
217
|
surveyed isoline. `water({bathymetryLabels: {}})` separately opts into numeric metre values derived
|
|
215
218
|
from each polygon's absolute minimum band depth. Existing maps gain neither detail by default, and
|
|
216
219
|
sources without bathymetry bindings omit both requested layers. These labels describe coarse band
|
|
217
220
|
floors, not measured survey soundings, and must not be presented as hydrographic sounding data.
|
|
221
|
+
An explicitly selected Bathymetry source can instead declare `vectorLayers.contours: true` to use
|
|
222
|
+
real contour lines and their `depth` values. Its optional named seafloor and water features have
|
|
223
|
+
separate `water.bathymetryLandforms` and `water.bathymetryWaterNames` styles, with the language
|
|
224
|
+
selected by `labels(...)`. See [the marine source contract](data-is-separate-from-design.md#declare-contour-and-name-layers).
|
|
225
|
+
Those name styles can explicitly select placement for each geometry family with
|
|
226
|
+
`placementByGeometry`; omitting it preserves scalar placement.
|
|
218
227
|
|
|
219
228
|
`addresses` renders the standard OpenMapTiles `housenumber` points at detailed zooms and exposes a
|
|
220
229
|
single semantic `labels` style. `landforms` renders the standard `mountain_peak` classes (`peak`,
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
# Boundary areas and choropleths
|
|
2
|
+
|
|
3
|
+
Tileflow's Boundaries tileset holds administrative and neighbourhood areas worldwide, from
|
|
4
|
+
countries down to districts and neighbourhoods. A map declares it with `boundaryAreas`, and
|
|
5
|
+
`boundaryChoropleth()` colors its areas from an application's values: unemployment by
|
|
6
|
+
municipality, sales by county, election results by district.
|
|
7
|
+
|
|
8
|
+
Boundaries is a separate product from World. World's border lines stay in the `boundaries` module;
|
|
9
|
+
`boundaryAreas` adds the areas themselves, each with a stable ID and official codes to join data
|
|
10
|
+
to.
|
|
11
|
+
|
|
12
|
+
## What the tileset contains
|
|
13
|
+
|
|
14
|
+
The `boundaries` family comes from OpenStreetMap under the
|
|
15
|
+
[Open Database License](https://opendatacommons.org/licenses/odbl/1-0/). Its source carries the
|
|
16
|
+
attribution `© OpenStreetMap contributors`, which MapLibre shows whenever a layer drawn from it is
|
|
17
|
+
on screen. The attribution is fixed by the family and cannot be replaced.
|
|
18
|
+
|
|
19
|
+
It has two layers, from zoom 0 to 12:
|
|
20
|
+
|
|
21
|
+
- `boundary_area`: one polygon per area and class. Its outline is the boundary line.
|
|
22
|
+
- `boundary_label`: one point per area and class, with `name` and `name:<language>`.
|
|
23
|
+
|
|
24
|
+
Polygons carry the properties needed to filter and join them:
|
|
25
|
+
|
|
26
|
+
| Property | Content |
|
|
27
|
+
| ------------- | --------------------------------------------------------------------------------------------------------------------------------- |
|
|
28
|
+
| feature ID | The area's registry ID. It survives releases while the area exists. |
|
|
29
|
+
| `class` | `country`, `dependency`, `region`, `province_or_county`, `municipality`, `district` or `neighbourhood`. |
|
|
30
|
+
| `class_tier` | `primary` for the level that covers a country with that class, otherwise `secondary`. |
|
|
31
|
+
| `admin_level` | OpenStreetMap's `admin_level`. |
|
|
32
|
+
| `admin_depth` | Depth in the country's administrative chain, from 0 for the country. Neighbourhoods have none. |
|
|
33
|
+
| `ref` | The official code with its scheme, such as `ES:INE:28079` or `US:GEOID:36061`; otherwise ISO 3166-2, or ISO 3166-1 for countries. |
|
|
34
|
+
| `iso_3166_1` | The country's ISO 3166-1 alpha-2 code. |
|
|
35
|
+
|
|
36
|
+
Labels and the release's lookup table also carry `iso_3166_2`, `nuts`, `wikidata` and names.
|
|
37
|
+
|
|
38
|
+
One area can serve several classes. Berlin, for example, is a region, a district and a
|
|
39
|
+
municipality: the tileset draws it once per class, with the same ID and the `ref` of each class.
|
|
40
|
+
Filter a choropleth by class so that only one of those features is visible.
|
|
41
|
+
|
|
42
|
+
Each class appears from a fixed zoom: countries and dependencies from 0, regions from 3, provinces
|
|
43
|
+
and counties from 6 (secondary ones from 7), municipalities from 8, districts from 10 and
|
|
44
|
+
neighbourhoods from 11. `tileflowBoundaryAreasV1Schema.classMinzoom` lists them.
|
|
45
|
+
|
|
46
|
+
## Declare the source
|
|
47
|
+
|
|
48
|
+
<!-- docs:check -->
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
import {defineMap} from '@tileflow/core';
|
|
52
|
+
import {streets} from '@tileflow/maps';
|
|
53
|
+
|
|
54
|
+
export default defineMap({
|
|
55
|
+
id: 'municipal-indicators',
|
|
56
|
+
version: 1,
|
|
57
|
+
extends: streets,
|
|
58
|
+
boundaryAreas: {family: 'boundaries'},
|
|
59
|
+
});
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
The compiled style gains the vector source `tileflow-boundaries`, which reads
|
|
63
|
+
`https://api.tileflow.dev/tiles/boundaries/tiles.json` (or the same path under the API base URL
|
|
64
|
+
passed to the compiler). `url` replaces it with a compatible TileJSON or a `pmtiles://` archive,
|
|
65
|
+
such as a local copy for offline development; the attribution stays the same. `false` removes
|
|
66
|
+
Boundaries inherited from a parent map.
|
|
67
|
+
|
|
68
|
+
## Color areas from values in the style
|
|
69
|
+
|
|
70
|
+
`values` compiles rows into the style. Rows join by `id` (the registry ID), `ref` or `iso_3166_1`,
|
|
71
|
+
the keys the polygons carry. This example colors municipalities in the Community of Madrid by
|
|
72
|
+
their INE code, the code Spanish statistics publish:
|
|
73
|
+
|
|
74
|
+
<!-- docs:check -->
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
import {boundaryChoropleth, choroplethScale, defineMap} from '@tileflow/core';
|
|
78
|
+
import {streets} from '@tileflow/maps';
|
|
79
|
+
|
|
80
|
+
// Rows as a statistics office publishes them: an INE municipality code and a rate.
|
|
81
|
+
const unemployment = [
|
|
82
|
+
{ine: '28079', rate: 9.8},
|
|
83
|
+
{ine: '28005', rate: 12.4},
|
|
84
|
+
{ine: '28006', rate: 6.1},
|
|
85
|
+
{ine: '28058', rate: 13.7},
|
|
86
|
+
];
|
|
87
|
+
|
|
88
|
+
export default defineMap({
|
|
89
|
+
id: 'madrid-unemployment',
|
|
90
|
+
version: 1,
|
|
91
|
+
extends: streets,
|
|
92
|
+
boundaryAreas: {family: 'boundaries'},
|
|
93
|
+
overlays: {
|
|
94
|
+
unemployment: boundaryChoropleth({
|
|
95
|
+
id: 'unemployment',
|
|
96
|
+
classes: ['municipality'],
|
|
97
|
+
countries: ['ES'],
|
|
98
|
+
scale: choroplethScale.step('#fef0d9', [
|
|
99
|
+
[8, '#fdcc8a'],
|
|
100
|
+
[10, '#fc8d59'],
|
|
101
|
+
[12, '#d7301f'],
|
|
102
|
+
]),
|
|
103
|
+
missingColor: '#d9d9d9',
|
|
104
|
+
outline: {color: '#ffffff', width: 0.75},
|
|
105
|
+
values: {
|
|
106
|
+
joinBy: 'ref',
|
|
107
|
+
rows: unemployment.map(({ine, rate}) => ({key: `ES:INE:${ine}`, value: rate})),
|
|
108
|
+
},
|
|
109
|
+
}),
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Municipalities under 8 % are pale, those from 12 % dark red, and municipalities without a row
|
|
115
|
+
gray. The overlay adds the layers `unemployment-fill` and `unemployment-outline` below the
|
|
116
|
+
basemap's labels. Their minimum zoom defaults to the first zoom of the selected classes, here 8.
|
|
117
|
+
|
|
118
|
+
A choropleth accepts these options:
|
|
119
|
+
|
|
120
|
+
- `classes` (required), `tiers` and `countries` filter the areas;
|
|
121
|
+
- `minzoom` and `maxzoom` limit the zooms;
|
|
122
|
+
- `scale` sets the colors, `missingColor` the color of areas without a value (transparent by
|
|
123
|
+
default) and `opacity` the fill opacity (0.8 by default);
|
|
124
|
+
- `outline` draws the boundary lines;
|
|
125
|
+
- `placement` positions the layers among the basemap's like any overlay (`below-labels` by
|
|
126
|
+
default).
|
|
127
|
+
|
|
128
|
+
## Choose a color scale
|
|
129
|
+
|
|
130
|
+
`choroplethScale` builds validated scales:
|
|
131
|
+
|
|
132
|
+
- `step(base, stops)`: `base` below the first threshold, then one color from each threshold on;
|
|
133
|
+
- `linear(stops)`: colors interpolated between stops and clamped beyond them;
|
|
134
|
+
- `categories({value: color})`: one color per string value.
|
|
135
|
+
|
|
136
|
+
Stops are `[value, color]` pairs in increasing order, at most 64. A numeric scale gives
|
|
137
|
+
`missingColor` to any area whose value is not a number; a category scale gives it to unknown
|
|
138
|
+
categories. `choroplethColorExpression(scale, input, missingColor)` returns the same MapLibre
|
|
139
|
+
expression for any input, for use in layers of your own.
|
|
140
|
+
|
|
141
|
+
## Update values at run time with feature-state
|
|
142
|
+
|
|
143
|
+
Omit `values` and the choropleth reads the feature-state key `value` (or `state`). Set it from the
|
|
144
|
+
application with `createBoundaryFeatureState` from `@tileflow/core/browser` and the map instance:
|
|
145
|
+
|
|
146
|
+
<!-- docs:check -->
|
|
147
|
+
|
|
148
|
+
```ts
|
|
149
|
+
import {createBoundaryFeatureState, type TileflowFeatureStateMap} from '@tileflow/core/browser';
|
|
150
|
+
|
|
151
|
+
// A MapLibre map, such as the one `<Map onLoad>` passes in React.
|
|
152
|
+
export function showRates(map: TileflowFeatureStateMap, rates: ReadonlyMap<number, number>) {
|
|
153
|
+
const state = createBoundaryFeatureState(map);
|
|
154
|
+
state.set({joinBy: 'id', rows: [...rates].map(([key, value]) => ({key, value}))});
|
|
155
|
+
return state;
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
`set` replaces the previous values and removes those no longer present. Values survive a style
|
|
160
|
+
reload, such as a theme change: the controller sets them again once the source is back. `clear`
|
|
161
|
+
removes them, and `dispose` also stops following the map. `labels: true` also sets the values on
|
|
162
|
+
`boundary_label`, for labels styled by value.
|
|
163
|
+
|
|
164
|
+
Feature-state is keyed by the source's feature ID, the registry ID by default. To key it by
|
|
165
|
+
official code instead, declare `boundaryAreas: {family: 'boundaries', promoteId: 'ref'}` and set
|
|
166
|
+
rows with `joinBy: 'ref'`. A map that promotes `ref` cannot also join values by `id`; compilation
|
|
167
|
+
fails with `TF_BOUNDARY_CHOROPLETH_JOIN`.
|
|
168
|
+
|
|
169
|
+
In React, `useBoundaryFeatureState(map, values, options)` from `@tileflow/react` keeps
|
|
170
|
+
feature-state equal to `values`; see the
|
|
171
|
+
[React README](https://github.com/tileflow/tileflow-sdk/blob/main/packages/react/README.md).
|
|
172
|
+
|
|
173
|
+
## Join by codes the polygons do not carry
|
|
174
|
+
|
|
175
|
+
Rows keyed by `iso_3166_2`, `nuts` or `wikidata`, or keyed by `ref` while feature-state uses
|
|
176
|
+
registry IDs, go through the release's lookup table, `boundaries-lookup.csv.gz`, with one row per
|
|
177
|
+
area and class. Decompress it and parse it with `parseBoundaryLookupCsv`; `joinBoundaryRows`
|
|
178
|
+
then re-keys the rows:
|
|
179
|
+
|
|
180
|
+
<!-- docs:check -->
|
|
181
|
+
|
|
182
|
+
```ts
|
|
183
|
+
import {createBoundaryLookup, joinBoundaryRows, parseBoundaryLookupCsv} from '@tileflow/core';
|
|
184
|
+
|
|
185
|
+
export async function regionsByNuts(csvGzipUrl: string, rows: {nuts: string; value: number}[]) {
|
|
186
|
+
const response = await fetch(csvGzipUrl);
|
|
187
|
+
if (!response.ok || !response.body) throw new Error(`Lookup table failed: ${response.status}`);
|
|
188
|
+
const text = await new Response(
|
|
189
|
+
response.body.pipeThrough(new DecompressionStream('gzip')),
|
|
190
|
+
).text();
|
|
191
|
+
const lookup = createBoundaryLookup(parseBoundaryLookupCsv(text));
|
|
192
|
+
const joined = joinBoundaryRows(
|
|
193
|
+
rows.map(({nuts, value}) => ({key: nuts, value})),
|
|
194
|
+
{joinBy: 'nuts', lookup, classes: ['region']},
|
|
195
|
+
);
|
|
196
|
+
if (joined.unmatched.length > 0) console.warn('Unmatched NUTS codes', joined.unmatched);
|
|
197
|
+
return joined;
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
The result has the same `{joinBy, rows}` shape that `values` and `set` accept, keyed by `id` or,
|
|
202
|
+
with `to: 'ref'`, by `ref`. It also lists the keys it could not use:
|
|
203
|
+
|
|
204
|
+
- `unmatched`: keys no area has;
|
|
205
|
+
- `ambiguous`: keys shared by more than one area, which are left out;
|
|
206
|
+
- `duplicates`: keys repeated in the rows, where the last row wins.
|
|
207
|
+
|
|
208
|
+
ISO 3166, NUTS and Wikidata codes are compared upper-cased; `ref` is compared as written, so a
|
|
209
|
+
customer's bare INE code needs its `ES:INE:` prefix.
|
|
210
|
+
|
|
211
|
+
## Limits
|
|
212
|
+
|
|
213
|
+
- Values compiled into the style grow it with every row. Use feature-state for large or changing
|
|
214
|
+
data.
|
|
215
|
+
- The `boundaries-official` family, with one official source per country and level, is not yet
|
|
216
|
+
available.
|
|
217
|
+
- Tile URLs and the lookup table do not establish redistribution rights beyond the ODbL.
|