@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.
Files changed (36) hide show
  1. package/README.md +24 -23
  2. package/THIRD_PARTY_NOTICES.md +100 -6
  3. package/dist/browser.d.ts +758 -388
  4. package/dist/browser.js +4598 -1169
  5. package/dist/build.d.ts +9 -5
  6. package/dist/build.js +2892 -1410
  7. package/dist/capture.js +1 -0
  8. package/dist/config-reference.json +3780 -2153
  9. package/dist/{icon-composition-DqoZWQuz.d.ts → icon-composition-sN3JkXdb.d.ts} +380 -96
  10. package/dist/index.d.ts +426 -13
  11. package/dist/index.js +4999 -1841
  12. package/dist/{manifest-types-hmzy2bBX.d.ts → manifest-types-jrBdBrES.d.ts} +71 -9
  13. package/dist/manifest.d.ts +2 -2
  14. package/dist/manifest.js +80 -2
  15. package/dist/native-profile.js +1 -0
  16. package/dist/native.d.ts +13 -3
  17. package/dist/native.js +714 -318
  18. package/dist/runtime.d.ts +1 -1
  19. package/dist/runtime.js +124 -6
  20. package/dist/worker/buildings-3d-worker.global.js +1 -0
  21. package/docs/authoring-model.md +12 -3
  22. package/docs/boundary-areas-and-choropleths.md +217 -0
  23. package/docs/buildings-3d.md +264 -34
  24. package/docs/cartographic-defaults.md +142 -0
  25. package/docs/compiled-style-performance.md +14 -3
  26. package/docs/data-is-separate-from-design.md +191 -7
  27. package/docs/hosted-session-authorization.md +2 -4
  28. package/docs/map-and-terrain-examples.md +10 -3
  29. package/docs/native-artifact-profile.md +5 -0
  30. package/docs/native-resource-urls.md +5 -3
  31. package/docs/street-lights.md +22 -6
  32. package/docs/sun-and-light.md +42 -2
  33. package/docs/theme-transitions-and-blends.md +50 -0
  34. package/docs/themes-and-module-styles.md +66 -0
  35. package/docs/trees-3d.md +85 -13
  36. package/package.json +5 -4
package/dist/runtime.d.ts CHANGED
@@ -1 +1 @@
1
- export { z as TileflowAnalytics, $ as TileflowManifestFetchOptions, a0 as TileflowManifestLoadOptions, a1 as TileflowMapMode, a2 as TileflowMapModeOptions, ae as TileflowResolvedRuntimeTheme, aC as TileflowRuntimeCenterLike, a as TileflowRuntimeColorScheme, T as TileflowRuntimeManifest, aD as TileflowRuntimeManifestMap, b as TileflowRuntimeManifestMapEntry, c as TileflowRuntimeManifestTheme, aE as TileflowRuntimeSource, aF as TileflowRuntimeSourceValidation, aG as TileflowRuntimeStyle, aH as TileflowRuntimeStyleOptions, d as TileflowRuntimeSystemThemes, aI as TileflowRuntimeViewOptions, aJ as TileflowSessionController, aK as TileflowSessionGrantResponse, aN as TileflowStyleFontFace, b6 as TileflowThemeBlendSelection, bd as TileflowThemeSelection, bl as assertValidTileflowRuntimeSource, bm as clearTileflowManifestCache, bo as createTileflowSessionController, bp as createTileflowSessionId, bq as defaultTileflowManifestUrl, br as defaultTileflowRuntimeView, bt as getTileflowAnalyticsApiUrl, bu as getTileflowStyleFontFaces, bv as inferTileflowAnalyticsFromStyleUrl, bw as loadTileflowManifest, bx as mergeTileflowAnalytics, by as nearestTileflowBlendTheme, bz as normalizeTileflowRuntimeCenter, bA as normalizeTileflowStaticImageSize, bY as normalizeTileflowSurfaceId, bB as normalizeTileflowUrl, bC as resolveTileflowAnalyticsRequestUrl, bD as resolveTileflowManifestMap, bE as resolveTileflowMapMode, bF as resolveTileflowRuntimeStyle, bG as resolveTileflowRuntimeTheme, bH as resolveTileflowRuntimeThemeBlend, bI as resolveTileflowRuntimeView, bJ as resolveTileflowStaticImageUrl, bK as shouldLoadTileflowManifest, bL as startTileflowSession, bN as tileflowStyleFontFaceLimits, bO as tileflowStyleFontFacesMetadataKey, bP as tileflowThemeBlendLimits, bS as validateTileflowRuntimeSource, bT as validateTileflowThemeBlendSelection, bU as validateTileflowThemeSelection } from './manifest-types-hmzy2bBX.js';
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.literal(tileflowRuntimeManifestVersion)
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(input.manifestMap, input.theme, input.colorScheme);
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)})}};})();
@@ -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`, `ferraris`, `harad`, `soundings`, and `blueprint` maps declare their glyph
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. The `water.bathymetry` fill does the same for the
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.