@tileflow/core 0.1.0-alpha.35 → 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.
@@ -1,10 +1,3 @@
1
- /** Artistic presets; selecting one never starts a clock or moves the real sun. */
2
- declare const tileflowTimesOfDay: readonly ["day", "dawn", "dusk", "night"];
3
- type TileflowTimeOfDay = (typeof tileflowTimesOfDay)[number];
4
- /** A named theme family points to complete, already compiled themes. */
5
- type TileflowTimeOfDayVariants = Readonly<Record<string, Readonly<Partial<Record<TileflowTimeOfDay, string>>>>>;
6
- declare function isTileflowTimeOfDay(value: unknown): value is TileflowTimeOfDay;
7
-
8
1
  declare const expressionBrand: unique symbol;
9
2
  declare const themeValueBrand: unique symbol;
10
3
  declare const zoomBrand: unique symbol;
@@ -396,6 +389,13 @@ type TileflowTerrainContoursConfig = {
396
389
  thresholds: Readonly<Record<number, readonly [number, number]>>;
397
390
  };
398
391
  type TileflowTerrainConfig = {
392
+ /** Explicit DEM tile templates, mutually exclusive with the TileJSON url. */
393
+ tiles?: readonly string[];
394
+ /** Native DEM resolution limits, independent of hillshade visibility. */
395
+ minZoom?: number;
396
+ maxZoom?: number;
397
+ /** Raster tile width in pixels. Defaults to the renderer default when omitted. */
398
+ tileSize?: 256 | 512;
399
399
  attribution?: string;
400
400
  contours?: TileflowTerrainContoursConfig;
401
401
  encoding?: TileflowTerrainEncoding;
@@ -448,7 +448,11 @@ type TileflowRoadAreaStyle = {
448
448
  };
449
449
  type TileflowRoadCrossingStyle = Omit<TileflowIconStyle, 'image'> & Required<Pick<TileflowIconStyle, 'image'>>;
450
450
  type TileflowRoadRoundaboutStyle = {
451
+ /** Extra casing width in pixels, split equally across both edges; defaults to the road border. */
452
+ borderWidth?: TileflowNumberStyleValue;
451
453
  casing?: TileflowCircleStyle;
454
+ /** Road width in pixels when metric radii are absent; defaults to the effective surface fill width. */
455
+ fallbackWidth?: TileflowNumberStyleValue;
452
456
  fill?: TileflowCircleStyle;
453
457
  };
454
458
  /**
@@ -458,7 +462,7 @@ type TileflowRoadRoundaboutStyle = {
458
462
  type TileflowRoadStreetLightStyle = {
459
463
  /** The light of lamps whose data gives no colour; a warm white by default. */
460
464
  color?: TileflowThemeColorValue;
461
- /** Whether the map has street lights; defaults to true where the data binds street furniture. */
465
+ /** Whether the map has street lights; defaults to false. Set true to opt in. */
462
466
  enabled?: boolean;
463
467
  /**
464
468
  * Where light is estimated where the data maps no lamps: `lit` (the default) along streets and
@@ -486,6 +490,8 @@ type TileflowRoadsModuleConfig = {
486
490
  mountainBike?: Partial<Record<TileflowMountainBikeScale, TileflowRoadTreatmentStyle>>;
487
491
  oneWayMarkers?: boolean;
488
492
  outline?: TileflowRoadOutline;
493
+ /** Draw ramp fills below receiving roads by default; `by-class` keeps their class's order. */
494
+ rampOrder?: 'below-roads' | 'by-class';
489
495
  restrictions?: Partial<Record<TileflowRoadRestriction, TileflowRoadTreatmentStyle>>;
490
496
  roundabouts?: TileflowRoadRoundaboutStyle;
491
497
  serviceTypes?: Partial<Record<TileflowRoadServiceType, TileflowRoadTreatmentStyle>>;
@@ -531,6 +537,14 @@ type TileflowLabelsModuleConfig = {
531
537
  language?: TileflowLabelLanguage;
532
538
  places?: TileflowLabelDetail;
533
539
  roadClasses?: readonly TileflowRoadClass[];
540
+ /**
541
+ * Where street names draw. `foreground` (the default) keeps them above buildings and
542
+ * vegetation. `ground` places them below buildings, with map-aligned text by default.
543
+ * Ground names retain their road-class collision order, but foreground symbols are offered
544
+ * first by the renderer; `collisionPriority` cannot make ground names outrank those symbols.
545
+ * Shields and junction numbers keep their foreground placement.
546
+ */
547
+ roadPlacement?: 'ground' | 'foreground';
534
548
  roads?: TileflowRoadLabelDetail;
535
549
  shields?: TileflowRoadShieldDetail;
536
550
  styles?: TileflowLabelStyles;
@@ -627,6 +641,13 @@ type ValidationResult = {
627
641
  messages: ValidationMessage[];
628
642
  };
629
643
 
644
+ /** Artistic presets; selecting one never starts a clock or moves the real sun. */
645
+ declare const tileflowTimesOfDay: readonly ["day", "dawn", "dusk", "night"];
646
+ type TileflowTimeOfDay = (typeof tileflowTimesOfDay)[number];
647
+ /** A named theme family points to complete, already compiled themes. */
648
+ type TileflowTimeOfDayVariants = Readonly<Record<string, Readonly<Partial<Record<TileflowTimeOfDay, string>>>>>;
649
+ declare function isTileflowTimeOfDay(value: unknown): value is TileflowTimeOfDay;
650
+
630
651
  declare const defaultTileflowManifestUrl = "/tileflow/manifest.json";
631
652
  declare const defaultTileflowRuntimeView: Readonly<{
632
653
  bearing: 0;
@@ -1,5 +1,5 @@
1
- import { T as TileflowRuntimeManifest } from './manifest-types-Bzxon3dx.js';
2
- export { a as TileflowRuntimeColorScheme, b as TileflowRuntimeManifestMapEntry, c as TileflowRuntimeManifestTheme, d as TileflowRuntimeSystemThemes, e as TileflowTimeOfDay, f as TileflowTimeOfDayVariants, t as tileflowRuntimeManifestLimits, g as tileflowRuntimeManifestVersion, h as tileflowTimeOfDayManifestVersion } from './manifest-types-Bzxon3dx.js';
1
+ import { T as TileflowRuntimeManifest } from './manifest-types-jrBdBrES.js';
2
+ export { a as TileflowRuntimeColorScheme, b as TileflowRuntimeManifestMapEntry, c as TileflowRuntimeManifestTheme, d as TileflowRuntimeSystemThemes, e as TileflowTimeOfDay, f as TileflowTimeOfDayVariants, t as tileflowRuntimeManifestLimits, g as tileflowRuntimeManifestVersion, h as tileflowTimeOfDayManifestVersion } from './manifest-types-jrBdBrES.js';
3
3
  import { z } from 'zod';
4
4
 
5
5
  declare const tileflowRuntimeManifestSchema: z.ZodType<TileflowRuntimeManifest>;
package/dist/native.js CHANGED
@@ -3769,63 +3769,6 @@ var init_runtime_view_schema = __esm({
3769
3769
  }
3770
3770
  });
3771
3771
 
3772
- // src/time-of-day.ts
3773
- function isTileflowTimeOfDay(value) {
3774
- return typeof value === "string" && tileflowTimesOfDay.includes(value);
3775
- }
3776
- function validateTileflowTimeOfDayVariants(variants, themes) {
3777
- const issues = [];
3778
- const owners = /* @__PURE__ */ new Map();
3779
- const families = Object.entries(variants);
3780
- for (const [family, moments] of families) {
3781
- if (!isTileflowThemeName(family) || !Object.hasOwn(themes, family)) {
3782
- issues.push({ path: [family], message: "Time-of-day family must name an existing theme" });
3783
- }
3784
- if (!Object.values(moments).includes(family)) {
3785
- issues.push({ path: [family], message: "A family must include its own theme in its presets" });
3786
- }
3787
- for (const [moment, name] of Object.entries(moments)) {
3788
- if (name === void 0) continue;
3789
- if (!isTileflowTimeOfDay(moment) || !isTileflowThemeName(name) || !Object.hasOwn(themes, name)) {
3790
- issues.push({
3791
- path: [family, moment],
3792
- message: "Expected a supported moment and an existing theme"
3793
- });
3794
- }
3795
- const owner = owners.get(name);
3796
- if (owner !== void 0 && owner !== family) {
3797
- issues.push({
3798
- path: [family, moment],
3799
- message: `Theme "${name}" already belongs to family "${owner}"`
3800
- });
3801
- }
3802
- owners.set(name, family);
3803
- }
3804
- }
3805
- return issues;
3806
- }
3807
- function resolveTileflowTimeOfDayTheme(variants, theme, timeOfDay) {
3808
- if (timeOfDay === void 0 || variants === void 0) return theme;
3809
- if (!isTileflowTimeOfDay(timeOfDay)) throw new TypeError("Expected day, dawn, dusk, or night");
3810
- const family = Object.hasOwn(variants, theme) ? variants[theme] : Object.values(variants).find((moments) => Object.values(moments).includes(theme));
3811
- if (!family) return theme;
3812
- const result = family[timeOfDay];
3813
- if (result === void 0) {
3814
- throw new Error(
3815
- `Theme "${theme}" does not offer timeOfDay="${timeOfDay}". Available: ${Object.keys(family).join(", ")}.`
3816
- );
3817
- }
3818
- return result;
3819
- }
3820
- var tileflowTimesOfDay;
3821
- var init_time_of_day = __esm({
3822
- "src/time-of-day.ts"() {
3823
- "use strict";
3824
- init_portable_identity_rules();
3825
- tileflowTimesOfDay = ["day", "dawn", "dusk", "night"];
3826
- }
3827
- });
3828
-
3829
3772
  // src/theme-blend.ts
3830
3773
  function planTileflowThemeBlend(styles, options = {}) {
3831
3774
  if (styles.length < 2)
@@ -4507,6 +4450,63 @@ var init_theme_blend = __esm({
4507
4450
  }
4508
4451
  });
4509
4452
 
4453
+ // src/time-of-day.ts
4454
+ function isTileflowTimeOfDay(value) {
4455
+ return typeof value === "string" && tileflowTimesOfDay.includes(value);
4456
+ }
4457
+ function validateTileflowTimeOfDayVariants(variants, themes) {
4458
+ const issues = [];
4459
+ const owners = /* @__PURE__ */ new Map();
4460
+ const families = Object.entries(variants);
4461
+ for (const [family, moments] of families) {
4462
+ if (!isTileflowThemeName(family) || !Object.hasOwn(themes, family)) {
4463
+ issues.push({ path: [family], message: "Time-of-day family must name an existing theme" });
4464
+ }
4465
+ if (!Object.values(moments).includes(family)) {
4466
+ issues.push({ path: [family], message: "A family must include its own theme in its presets" });
4467
+ }
4468
+ for (const [moment, name] of Object.entries(moments)) {
4469
+ if (name === void 0) continue;
4470
+ if (!isTileflowTimeOfDay(moment) || !isTileflowThemeName(name) || !Object.hasOwn(themes, name)) {
4471
+ issues.push({
4472
+ path: [family, moment],
4473
+ message: "Expected a supported moment and an existing theme"
4474
+ });
4475
+ }
4476
+ const owner = owners.get(name);
4477
+ if (owner !== void 0 && owner !== family) {
4478
+ issues.push({
4479
+ path: [family, moment],
4480
+ message: `Theme "${name}" already belongs to family "${owner}"`
4481
+ });
4482
+ }
4483
+ owners.set(name, family);
4484
+ }
4485
+ }
4486
+ return issues;
4487
+ }
4488
+ function resolveTileflowTimeOfDayTheme(variants, theme, timeOfDay) {
4489
+ if (timeOfDay === void 0 || variants === void 0) return theme;
4490
+ if (!isTileflowTimeOfDay(timeOfDay)) throw new TypeError("Expected day, dawn, dusk, or night");
4491
+ const family = Object.hasOwn(variants, theme) ? variants[theme] : Object.values(variants).find((moments) => Object.values(moments).includes(theme));
4492
+ if (!family) return theme;
4493
+ const result = family[timeOfDay];
4494
+ if (result === void 0) {
4495
+ throw new Error(
4496
+ `Theme "${theme}" does not offer timeOfDay="${timeOfDay}". Available: ${Object.keys(family).join(", ")}.`
4497
+ );
4498
+ }
4499
+ return result;
4500
+ }
4501
+ var tileflowTimesOfDay;
4502
+ var init_time_of_day = __esm({
4503
+ "src/time-of-day.ts"() {
4504
+ "use strict";
4505
+ init_portable_identity_rules();
4506
+ tileflowTimesOfDay = ["day", "dawn", "dusk", "night"];
4507
+ }
4508
+ });
4509
+
4510
4510
  // src/themes/model.ts
4511
4511
  var tileflowThemeLimits, themeTypographyStyleKeys, themeTypographyKeys;
4512
4512
  var init_model = __esm({
package/dist/runtime.d.ts CHANGED
@@ -1 +1 @@
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-Bzxon3dx.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';
@@ -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,7 +208,10 @@ 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
@@ -219,6 +222,8 @@ An explicitly selected Bathymetry source can instead declare `vectorLayers.conto
219
222
  real contour lines and their `depth` values. Its optional named seafloor and water features have
220
223
  separate `water.bathymetryLandforms` and `water.bathymetryWaterNames` styles, with the language
221
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.
222
227
 
223
228
  `addresses` renders the standard OpenMapTiles `housenumber` points at detailed zooms and exposes a
224
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.