@tileflow/core 0.1.0-alpha.35 → 0.1.0-alpha.37
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -28
- package/dist/browser.d.ts +766 -419
- package/dist/browser.js +3176 -495
- package/dist/build.d.ts +3 -3
- package/dist/build.js +2947 -1618
- package/dist/config-reference.json +7165 -3899
- package/dist/{icon-composition-DSjIKmrK.d.ts → icon-composition-C5X3Q9Ya.d.ts} +622 -98
- package/dist/index.d.ts +322 -129
- package/dist/index.js +4368 -2142
- package/dist/{manifest-types-Bzxon3dx.d.ts → manifest-types-jrBdBrES.d.ts} +29 -8
- package/dist/manifest.d.ts +2 -2
- package/dist/native.js +57 -57
- package/dist/runtime.d.ts +1 -1
- package/dist/worker/buildings-3d-worker.global.js +1 -0
- package/docs/authoring-model.md +8 -3
- package/docs/boundary-areas-and-choropleths.md +217 -0
- package/docs/buildings-3d.md +203 -30
- package/docs/cartographic-defaults.md +142 -0
- package/docs/data-is-separate-from-design.md +66 -33
- package/docs/hosted-session-authorization.md +2 -4
- package/docs/map-and-terrain-examples.md +10 -3
- package/docs/nautical-marks.md +200 -0
- package/docs/public-api-and-browser-subpath.md +4 -1
- package/docs/street-lights.md +22 -6
- package/docs/sun-and-light.md +42 -2
- package/docs/themes-and-module-styles.md +66 -0
- package/docs/trees-3d.md +22 -9
- package/package.json +2 -2
|
@@ -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
|
|
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;
|
package/dist/manifest.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { T as TileflowRuntimeManifest } from './manifest-types-
|
|
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-
|
|
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-
|
|
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 Re=new Set,De=!1;function Ue(e,n,t=2){let s=n&&n.length,o=s?n[0]*t:e.length;Re.size&&Re.clear();let r=bn(e,0,o,t,!0),u=[];if(!r||r.next===r.prev)return u;let i=0,l=0,c=0;if(s&&(r=Pn(e,n,r,t)),e.length>80*t){i=e[0],l=e[1];let a=i,f=l;for(let h=t;h<o;h+=t){let m=e[h],b=e[h+1];m<i&&(i=m),b<l&&(l=b),m>a&&(a=m),b>f&&(f=b)}c=Math.max(a-i,f-l),c=c!==0?32767/c:0}return We(r,u,i,l,c),u}function bn(e,n,t,s,o){let r=null;if(o===Xn(e,n,t,s)>0)for(let u=n;u<t;u+=s)r=mn(u/s|0,e[u],e[u+1],r);else for(let u=t-s;u>=n;u-=s)r=mn(u/s|0,e[u],e[u+1],r);return r&&Ae(r,r.next)&&(Te(r),r=r.next),r}function ae(e,n=e){let t=n===e,s=e,o;do o=!1,s!==s.next&&(Re.size===0||!Re.has(s))&&(Ae(s,s.next)||P(s.prev,s,s.next)===0)?((t||s===n)&&(n=s.prev),De=!0,Te(s),s=s.prev,o=!0):(t||s!==n)&&(s=s.next,o=!t);while(o||s!==n);return n}function We(e,n,t,s,o){o&&jn(e,t,s,o);let r=e,u=!1;for(;e.prev!==e.next;){let i=e.prev,l=e.next;if(P(i,e,l)<0&&(o?Bn(e,t,s,o):kn(e))){n.push(i.i,e.i,l.i),Te(e),e=l,r=l;continue}if(e=l,e===r){if(De=!1,e=ae(e),De){r=e;continue}if(!u){e=Fn(e,n),r=e,u=!0;continue}_n(e,n,t,s,o);break}}}function kn(e){let n=e.prev,t=e,s=e.next,o=n.x,r=t.x,u=s.x,i=n.y,l=t.y,c=s.y,a=Math.min(o,r,u),f=Math.min(i,l,c),h=Math.max(o,r,u),m=Math.max(i,l,c),b=s.next;for(;b!==n;){if(b.x>=a&&b.x<=h&&b.y>=f&&b.y<=m&&!(o===b.x&&i===b.y)&&Ne(o,i,r,l,u,c,b.x,b.y)&&P(b.prev,b,b.next)>=0)return!1;b=b.next}return!0}function Bn(e,n,t,s){let o=e.prev,r=e,u=e.next,i=o.x,l=r.x,c=u.x,a=o.y,f=r.y,h=u.y,m=Math.min(i,l,c),b=Math.min(a,f,h),d=Math.max(i,l,c),x=Math.max(a,f,h),U=$e(m,b,n,t,s),k=$e(d,x,n,t,s),M=e.prevZ;for(;M&&M.z>=U;){if(M.x>=m&&M.x<=d&&M.y>=b&&M.y<=x&&M!==u&&!(i===M.x&&a===M.y)&&Ne(i,a,l,f,c,h,M.x,M.y)&&P(M.prev,M,M.next)>=0)return!1;M=M.prevZ}let y=e.nextZ;for(;y&&y.z<=k;){if(y.x>=m&&y.x<=d&&y.y>=b&&y.y<=x&&y!==u&&!(i===y.x&&a===y.y)&&Ne(i,a,l,f,c,h,y.x,y.y)&&P(y.prev,y,y.next)>=0)return!1;y=y.nextZ}return!0}function Fn(e,n){let t=e,s=!1;do{let o=t.prev,r=t.next.next;yn(o,t,t.next,r,!1)&&Ie(o,r)&&Ie(r,o)&&(n.push(o.i,t.i,r.i),Te(t),Te(t.next),t=e=r,s=!0),t=t.next}while(t!==e);return s?ae(t):t}function _n(e,n,t,s,o){let r=e;do{let u=r.next.next;for(;u!==r.prev;){if(r.i!==u.i&&Nn(r,u)){let i=pn(r,u);r=ae(r,r.next),i=ae(i,i.next),We(r,n,t,s,o),We(i,n,t,s,o);return}u=u.next}r=r.next}while(r!==e)}var Ke=!1;function Pn(e,n,t,s){let o=[];for(let r=0,u=n.length;r<u;r++){let i=n[r]*s,l=r<u-1?n[r+1]*s:e.length,c=bn(e,i,l,s,!1);c===c.next&&Re.add(c),o.push(Hn(c))}o.sort(On),zn(e.length/s,n.length),dn(t,t),Ke=!0;for(let r=0;r<o.length;r++)t=Ln(o[r],t);return Ke=!1,ae(t)}function On(e,n){return e.x-n.x||e.y-n.y||(e.next.y-e.y)/(e.next.x-e.x)-(n.next.y-n.y)/(n.next.x-n.x)}function Ln(e,n){let t=Sn(e,n);if(!t)return n;let s=pn(t,e),o=s.next;return dn(t,o.next),ae(s,s.next),ae(t,t.next)}var xn=16,_=new Float64Array(0),He=0,Je=[],Qe=[];function zn(e,n){let t=Math.ceil((e+2*n)/xn)+n+2;_.length<t*4&&(_=new Float64Array(t*4)),He=0}function dn(e,n){let t=e;do{let s=He++;Je[s]=t;let o=t.x,r=t.y,u=t.x,i=t.y,l=0;do{let a=t.next;t.z=s,a.x<o&&(o=a.x),a.x>u&&(u=a.x),a.y<r&&(r=a.y),a.y>i&&(i=a.y),t=a}while(++l<xn&&t!==n);Qe[s]=t;let c=s*4;_[c]=o,_[c+1]=r,_[c+2]=u,_[c+3]=i}while(t!==n)}function Cn(e,n){let t=e.z*4;n.x<_[t]&&(_[t]=n.x),n.y<_[t+1]&&(_[t+1]=n.y),n.x>_[t+2]&&(_[t+2]=n.x),n.y>_[t+3]&&(_[t+3]=n.y)}function fn(e){let n=Qe[e];for(;n.prev.next!==n;)n=n.next;return Qe[e]=n,n}function hn(e){let n=Je[e];for(;n.prev.next!==n;)n=n.next;return Je[e]=n,n}function Sn(e,n){let t=n,s=e.x,o=e.y,r=-1/0,u;if(Ae(e,t))return t;for(let h=0,m=0;h<He;h++,m+=4){if(o<_[m+1]||o>_[m+3]||_[m]>s||_[m+2]<=r)continue;let b=fn(h);t=hn(h);do{if(t.prev.next===t){if(Ae(e,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,u=t.x<t.next.x?t:t.next,d===s))return u}}t=t.next}while(t!==b)}if(!u)return null;let i=u.x,l=u.y,c=Math.min(o,l),a=Math.max(o,l),f=1/0;for(let h=0,m=0;h<He;h++,m+=4){if(_[m+2]<i||_[m]>s||_[m+3]<c||_[m+1]>a)continue;let b=fn(h);t=hn(h);do{if(t.prev.next===t&&s>=t.x&&t.x>=i&&s!==t.x&&Ne(o<l?s:r,o,i,l,o<l?r:s,o,t.x,t.y)){let d=Math.abs(o-t.y)/(s-t.x);(Ie(t,e)||t.y===o&&t.next.y===o&&t.next.x>s)&&(d<f||d===f&&(t.x>u.x||t.x===u.x&&Zn(u,t)))&&(u=t,f=d)}t=t.next}while(t!==b)}return u}function Zn(e,n){return P(e.prev,e,n.prev)<0&&P(n.next,e,e.next)<0}var J=[],de=[],ce=new Uint32Array(0),we=new Uint32Array(0),ve=new Uint32Array(256);function jn(e,n,t,s){let o=e,r=0;do o.z=$e(o.x,o.y,n,t,s),J[r++]=o,o=o.next;while(o!==e);En(r);let u=null;for(let i=0;i<r;i++){let l=J[i];l.prevZ=u,u&&(u.nextZ=l),u=l}u.nextZ=null,J.fill(null,0,r),de.fill(null,0,r)}function En(e){if(e<=32){for(let n=1;n<e;n++){let t=J[n],s=t.z,o=n-1;for(;o>=0&&J[o].z>s;)J[o+1]=J[o],o--;J[o+1]=t}return}ce.length<e&&(ce=new Uint32Array(e),we=new Uint32Array(e),de=new Array(e));for(let n=0;n<e;n++)ce[n]=J[n].z;je(e,J,ce,de,we,0),je(e,de,we,J,ce,8),je(e,J,ce,de,we,16),je(e,de,we,J,ce,24)}function je(e,n,t,s,o,r){ve.fill(0);for(let i=0;i<e;i++)ve[t[i]>>>r&255]++;let u=0;for(let i=0;i<256;i++){let l=ve[i];ve[i]=u,u+=l}for(let i=0;i<e;i++){let l=t[i],c=ve[l>>>r&255]++;s[c]=n[i],o[c]=l}}function $e(e,n,t,s,o){return e=(e-t)*o|0,n=(n-s)*o|0,e=(e|e<<8)&16711935,e=(e|e<<4)&252645135,e=(e|e<<2)&858993459,e=(e|e<<1)&1431655765,n=(n|n<<8)&16711935,n=(n|n<<4)&252645135,n=(n|n<<2)&858993459,n=(n|n<<1)&1431655765,e|n<<1}function Hn(e){let n=e,t=e;do(n.x<t.x||n.x===t.x&&n.y<t.y)&&(t=n),n=n.next;while(n!==e);return t}function Ne(e,n,t,s,o,r,u,i){return(o-u)*(n-i)>=(e-u)*(r-i)&&(e-u)*(s-i)>=(t-u)*(n-i)&&(t-u)*(r-i)>=(o-u)*(s-i)}function Nn(e,n){let t=Ae(e,n)&&P(e.prev,e,e.next)>0&&P(n.prev,n,n.next)>0;return e.next.i!==n.i&&(t||Ie(e,n)&&Ie(n,e)&&(P(e.prev,e,n.prev)!==0||P(e,n.prev,n)!==0))&&!Vn(e,n)&&(t||Yn(e,n))}function P(e,n,t){return(n.y-e.y)*(t.x-n.x)-(n.x-e.x)*(t.y-n.y)}function Ae(e,n){return e.x===n.x&&e.y===n.y}function yn(e,n,t,s,o=!0){let r=P(e,n,t),u=P(e,n,s),i=P(t,s,e),l=P(t,s,n);return(r>0&&u<0||r<0&&u>0)&&(i>0&&l<0||i<0&&l>0)?!0:o?!!(r===0&&Ee(e,t,n)||u===0&&Ee(e,s,n)||i===0&&Ee(t,e,s)||l===0&&Ee(t,n,s)):!1}function Ee(e,n,t){return n.x<=Math.max(e.x,t.x)&&n.x>=Math.min(e.x,t.x)&&n.y<=Math.max(e.y,t.y)&&n.y>=Math.min(e.y,t.y)}function Vn(e,n){let t=Math.min(e.x,n.x),s=Math.max(e.x,n.x),o=Math.min(e.y,n.y),r=Math.max(e.y,n.y),u=e;do{let i=u.next;if(u.x>s&&i.x>s||u.x<t&&i.x<t||u.y>r&&i.y>r||u.y<o&&i.y<o){u=i;continue}if(u.i!==e.i&&i.i!==e.i&&u.i!==n.i&&i.i!==n.i&&yn(u,i,e,n))return!0;u=i}while(u!==e);return!1}function Ie(e,n){return P(e.prev,e,e.next)<0?P(e,n,e.next)>=0&&P(e,e.prev,n)>=0:P(e,n,e.prev)<0||P(e,e.next,n)<0}function Yn(e,n){let t=e,s=!1,o=(e.x+n.x)/2,r=(e.y+n.y)/2;do{let u=t.next;t.y>r!=u.y>r&&o<(u.x-t.x)*(r-t.y)/(u.y-t.y)+t.x&&(s=!s),t=u}while(t!==e);return s}function pn(e,n){let t=qe(e.i,e.x,e.y),s=qe(n.i,n.x,n.y),o=e.next,r=n.prev;return e.next=n,n.prev=e,t.next=o,o.prev=t,s.next=t,t.prev=s,r.next=s,s.prev=r,s}function mn(e,n,t,s){let o=qe(e,n,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 Te(e){e.next.prev=e.prev,e.prev.next=e.next,e.prevZ&&(e.prevZ.nextZ=e.nextZ),e.nextZ&&(e.nextZ.prevZ=e.prevZ),Ke&&Cn(e.prev,e.next)}function qe(e,n,t){return{i:e,x:n,y:t,prev:null,next:null,z:0,prevZ:null,nextZ:null}}function Xn(e,n,t,s){let o=0;for(let r=n,u=t-s;r<t;r+=s)o+=(e[u]-e[r])*(e[r+1]+e[u+1]),u=r;return o}var Gn=.7,Dn=Math.tan(Math.PI/6),Wn=Math.tan(35*Math.PI/180),Kn=Math.tan(Math.PI/18),ke=.65,Be=.4;function vn(e,n,t,s,o,r,u){let i=String(t.roof_shape??"");if(!i||i==="flat"||i==="sawtooth"||et(e,u))return;let l=$n(e);if(!l||Math.abs(en(e))<Gn*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=wn(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")&&i!=="skillion"&&([c,a,f,h]=[a,c,h,f]);let x=Qn(i,f,h,b&&[Mn(b,c),Mn(b,a)]);if(!x)return;let U=Math.max(0,o-s),k=wn(t.roof_height),M,y;if(k!==void 0&&k>0)y=o,M=Math.max(s+Math.min(2,U/2),o-k);else{let p=Math.min(Jn(i,h),U*.6,30);t.height_source==="height"?(y=o,M=o-p):(M=o,y=o+p)}let I=y-M;if(!(I>=.3))return;let[R,V]=l.centre,B=(p,A)=>{let w=(p-R)*r,g=(A-V)*r;return[w*c[0]+g*c[1],w*a[0]+g*a[1]]},j=(p,A)=>[R+(p*c[0]+A*a[0])/r,V+(p*c[1]+A*a[1])/r],Q=(p,A)=>{let w=1/0;for(let[g,N,E]of x)w=Math.min(w,g*p+N*A+E);return Math.max(0,w)},$=(p,A)=>M+I*Q(...B(p,A)),L=[];for(let p=0;p+1<n.length;p+=2)L.push(...B(n[p],n[p+1]));en(L)<0&&nt(L);let z=1.01,q=[-f*z,-h*z,f*z,-h*z,f*z,h*z,-f*z,h*z],Y=[];for(let[p,A]of x.entries()){let w=q;for(let[F,H]of x.entries()){if(F===p)continue;let C=A[0]-H[0],X=A[1]-H[1],ne=A[2]-H[2];if(Math.abs(C)+Math.abs(X)+Math.abs(ne)<1e-9){F<p&&(w=[]);continue}if(w=gn(w,C,X,ne),w.length<6)break}if(w.length<6)continue;let g=L,N=w.length/2;for(let F=0;F<N&&g.length>=6;F+=1){let[H,C]=[w[F*2],w[F*2+1]],[X,ne]=[w[(F+1)%N*2],w[(F+1)%N*2+1]];g=gn(g,ne-C,H-X,-(ne-C)*H-(H-X)*C)}if(g.length<6||Math.abs(en(g))<1e-6)continue;let E=[];for(let F=0;F<g.length;F+=2)E.push(...j(g[F],g[F+1]));let se=Ue(E);if(se.length===0)continue;let ee=I*A[0],oe=I*A[1],T=ee*c[0]+oe*a[0],v=ee*c[1]+oe*a[1],O=Math.hypot(T,v,1);Y.push({ring:E,triangles:se,normal:[-T/O,-v/O]})}if(Y.length!==0)return{eaves:M,ridge:y,faces:Y,heightAt:$,breaks(p,A,w,g){let[N,E]=B(p,A),[se,ee]=B(w,g),oe=[];for(let T=0;T<x.length;T+=1)for(let v=T+1;v<x.length;v+=1){let O=(he,S)=>(x[T][0]-x[v][0])*he+(x[T][1]-x[v][1])*S+(x[T][2]-x[v][2]),F=O(N,E),H=O(se,ee);if(F===H||F*H>0)continue;let C=F/(F-H);if(C<=1e-4||C>=1-1e-4)continue;let X=N+(se-N)*C,ne=E+(ee-E)*C;x[T][0]*X+x[T][1]*ne+x[T][2]<=Q(X,ne)+1e-6&&oe.push(C)}return[...new Set(oe.map(T=>Math.round(T*1e6)/1e6))].sort((T,v)=>T-v)}}}function Jn(e,n){switch(e){case"skillion":return 2*n*Kn;case"pyramidal":return n*Wn;case"gambrel":case"mansard":return n*.8;case"dome":case"onion":case"round":return n;default:return n*Dn}}function Qn(e,n,t,s){let o=n,r=t,u=[[0,-1/r,1],[0,1/r,1]],i=c=>c.flatMap(a=>[1,-1].flatMap(f=>{let h=a==="v"?r:o,m=ke/(Be*r)*f,b=(1-ke)/((1-Be)*r)*f,d=x=>a==="v"?[0,-x]:[-x,0];return[[...d(m),ke/(Be*r)*h],[...d(b),ke+(1-ke)/((1-Be)*r)*(h-Be*r)]]})),l=[.99,.8,.5,.2];switch(e){case"gabled":return u;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[...u,[-1/r,0,o/r],[1/r,0,o/r]];case"half_hipped":return[...u,[-1/r,0,.5+o/r],[1/r,0,.5+o/r]];case"pyramidal":return[...u,[-1/o,0,1],[1/o,0,1]];case"gambrel":return i(["v"]);case"mansard":return i(["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 gn(e,n,t,s){let o=e.length/2,r=[];for(let u=0;u<o;u+=1){let i=e[u*2],l=e[u*2+1],c=e[(u+1)%o*2],a=e[(u+1)%o*2+1],f=n*i+t*l+s,h=n*c+t*a+s;if(f<=0&&r.push(i,l),f<0&&h>0||f>0&&h<0){let m=f/(f-h);r.push(i+(c-i)*m,l+(a-l)*m)}}return r}function $n(e){let n=qn(e);if(n.length<3)return;let t,s=1/0;for(let o=0;o<n.length;o+=1){let[r,u]=n[o],[i,l]=n[(o+1)%n.length],c=Math.hypot(i-r,l-u);if(c===0)continue;let a=[(i-r)/c,(l-u)/c],f=[-a[1],a[0]],[h,m,b,d]=[1/0,-1/0,1/0,-1/0];for(let[U,k]of n){let M=U*a[0]+k*a[1],y=U*f[0]+k*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 U=(h+m)/2,k=(b+d)/2;t={centre:[U*a[0]+k*f[0],U*a[1]+k*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 qn(e){let n=[];for(let r=0;r+1<e.length;r+=2)n.push([e[r],e[r+1]]);n.sort((r,u)=>r[0]-u[0]||r[1]-u[1]);let t=(r,u,i)=>(u[0]-r[0])*(i[1]-r[1])-(u[1]-r[1])*(i[0]-r[0]),s=[];for(let r of n){for(;s.length>=2&&t(s.at(-2),s.at(-1),r)<=0;)s.pop();s.push(r)}let o=[];for(let r of n.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 et(e,n){let[t,s,o,r]=[1/0,-1/0,1/0,-1/0];for(let i=0;i+1<e.length;i+=2)t=Math.min(t,e[i]),s=Math.max(s,e[i]),o=Math.min(o,e[i+1]),r=Math.max(r,e[i+1]);let u=e.length/2;for(let i=0;i<u;i+=1){let l=e[i*2],c=e[i*2+1],a=e[(i+1)%u*2],f=e[(i+1)%u*2+1];if(t<0&&l===t&&a===t||s>n&&l===s&&a===s||o<0&&c===o&&f===o||r>n&&c===r&&f===r)return!0}return!1}function en(e){let n=0,t=e.length/2;for(let s=0;s<t;s+=1){let o=(s+1)%t;n+=e[s*2]*e[o*2+1]-e[o*2]*e[s*2+1]}return n/2}function nt(e){let n=[];for(let t=0;t<e.length;t+=2)n.push([e[t],e[t+1]]);n.reverse(),e.splice(0,e.length,...n.flat())}function Mn(e,n){return e[0]*n[0]+e[1]*n[1]}function wn(e){let n=typeof e=="string"?Number(e):e;return typeof n=="number"&&Number.isFinite(n)?n:void 0}var Mt=Object.freeze({surface:0,secondary:1,glass:2,accent:3,roof:4}),wt=Object.freeze({unclassified:0,wall:1,door:2,roof:3,window:4,lamp:5,logo:6}),vt=Object.freeze([1,1,4,1,3]),Rt=Object.freeze({levels:3,vertices:2e5,triangles:2e5,footprintPoints:4096,radius:2e3,height:1e3,models:512,bytes:16*1024*1024});function nn(e,n,t){for(let s of e){let o=!1;for(let r=0,u=s.length-2;r<s.length;u=r,r+=2){let i=s[r],l=s[r+1],c=s[u],a=s[u+1];l>t!=a>t&&n<(c-i)*(t-l)/(a-l)+i&&(o=!o)}if(o)return!0}return!1}function In(e,n,t){for(let s of n){let[o,r,u,i]=An(s);for(let l of e){let[c,a,f,h]=An(l);if(!(c>u-t||f<o+t||a>i-t||h<r+t)&&(Rn(l,s,t)||Rn(s,l,t)))return!0}}return!1}function Rn(e,n,t){let s=(o,r)=>nn([n],o,r)&&ot(n,o,r)>t;for(let o=0;o+1<e.length;o+=2)if(s(e[o],e[o+1]))return!0;for(let o=0,r=e.length-2;o<e.length;r=o,o+=2){let[u,i,l,c]=[e[r],e[r+1],e[o],e[o+1]],a=[0,1];for(let f=0,h=n.length-2;f<n.length;h=f,f+=2){let m=tt(u,i,l,c,n[h],n[h+1],n[f],n[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(u+(l-u)*h,i+(c-i)*h))return!0}}}return!1}function tt(e,n,t,s,o,r,u,i){let l=t-e,c=s-n,a=u-o,f=i-r,h=l*f-c*a;if(h===0)return;let m=((o-e)*f-(r-n)*a)/h,b=((o-e)*c-(r-n)*l)/h;return m>0&&m<1&&b>=0&&b<=1?m:void 0}function ot(e,n,t){let s=1/0;for(let o=0,r=e.length-2;o<e.length;r=o,o+=2){let[u,i,l,c]=[e[r],e[r+1],e[o],e[o+1]],a=(l-u)**2+(c-i)**2,f=a>0?Math.max(0,Math.min(1,((n-u)*(l-u)+(t-i)*(c-i))/a)):0;s=Math.min(s,Math.hypot(n-(u+(l-u)*f),t-(i+(c-i)*f)))}return s}function An(e){let[n,t,s,o]=[1/0,1/0,-1/0,-1/0];for(let r=0;r+1<e.length;r+=2)n=Math.min(n,e[r]),s=Math.max(s,e[r]),t=Math.min(t,e[r+1]),o=Math.max(o,e[r+1]);return[n,t,s,o]}var te=8192,tn=28,rt={commercial:1,active:2,destination:3},st={residential:1,...Object.fromEntries(["civic","commercial","education","entertainment","medical","transportation"].map(e=>[e,2])),...Object.fromEntries(["agricultural","industrial","military","outbuilding","religious","service"].map(e=>[e,3]))},it={dining:1,entertainment:2,nightlife:3},on={...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(e=>[e,3])),...Object.fromEntries(["arts_centre","bowling_alley","cinema","comedy_club","concert_hall","movie_theater","opera","performing_arts_venue","theater","theatre","theatre_venue"].map(e=>[e,2])),...Object.fromEntries(["casual_eatery","fast_food","fast_food_restaurant","food_court","restaurant"].map(e=>[e,1]))},ut=6,lt=.5,ct=.5;function Un(e,n){let t=te/e.extent,s=n.tileMetres/te,o=n.defaultHeight??5,r=Number.isFinite(n.edgeRadius)?Math.max(0,Math.min(1,n.edgeRadius))/s:0,u=new sn,i=new Ve,l=new Ve,c=[],a=[],f=0,h=mt(e,n);for(let[y,I]of e.features.entries()){if(I.type!=="Polygon"||c.length>=65535)continue;let R=I.properties,V=R.hide_3d;if(V===!0||V===1||V==="1"||V==="true")continue;let B=Math.min(6e3,Tn(R.render_height)??o),j=Math.min(B,Tn(R.render_min_height)??0),Q=rt[String(R.building_tone??"")]??0,$=st[String(R.class??"")]??0,L=h.get(y)??0,z=c.length,q=!1;for(let Y of bt(I.geometry,t)){let p=Y.map(dt).filter(v=>v.length>=6);if(p.length===0||Ye(p[0])===0)continue;let[A,w]=xt(p[0]);if(n.masks?.length&&(nn(n.masks,A,w)||In(p,n.masks,ct/s)))continue;let g={cell:Math.min(7,Math.max(0,Math.floor(w/1024)))*8+Math.min(7,Math.max(0,Math.floor(A/1024))),bounds:[1/0,1/0,-1/0,-1/0,B+.1],roofsFrom:i.length,roofsCount:0,wallsFrom:l.length,wallsCount:0};for(let v of p)for(let O=0;O<v.length;O+=2)g.bounds[0]=Math.min(g.bounds[0],v[O]-1),g.bounds[1]=Math.min(g.bounds[1],v[O+1]-1),g.bounds[2]=Math.max(g.bounds[2],v[O]+1),g.bounds[3]=Math.max(g.bounds[3],v[O+1]+1);q=!0,f=Math.max(f,B);let N=Number.isSafeInteger(I.id),E=rn(N?I.id*.0731:A*.0731+w*.19137+z*1.618),se=!N&&p[0].some(v=>v<=0||v>=te)?rn(B*7.31+j*3.17+Q):E,ee={base:j,top:B,jitter:se,seed:E,tone:Q,group:$,use:L,cx:A,cy:w,index:z},oe=p.map((v,O)=>ht(v,O,r,n.cornerAngle));p=oe.map(({ring:v})=>v);let T=Y.length===1&&p.length===1?vn(Y[0],p[0],R,j,B,s,te):void 0;T?(f=Math.max(f,T.ridge),g.bounds[4]=Math.max(g.bounds[4],T.ridge+.1),ft(T,ee,u,i)):at(p,ee,u,i);for(let[v,O]of p.entries()){let F=Ye(O)>0,H=v===0?F?-1:1:F?1:-1,C=O.length/2,X=S=>{let Z=(S%C+C)%C;return[O[Z*2],O[Z*2+1]]},ne=S=>{let[Z,W]=X(S-1),[G,D]=X(S),[ie,me]=X(S+1);if(fe(Z,W,G,D)||fe(G,D,ie,me))return!1;let be=Math.hypot(G-Z,D-W),ue=Math.hypot(ie-G,me-D);if(!be||!ue)return!1;let Fe=-(D-W)/be*H,_e=(G-Z)/be*H;return(Fe*(ie-G)+_e*(me-D))/ue>lt},Xe=(S,Z)=>T?T.heightAt(S,Z):B,he=0;for(let S=0;S<C;S+=1){let[Z,W]=X(S),[G,D]=X(S+1),ie=Math.hypot(G-Z,D-W);if(ie===0||fe(Z,W,G,D))continue;let me=-(D-W)/ie*H,be=(G-Z)/ie*H,ue=oe[v].normals[S]??[me,be],Fe=oe[v].normals[(S+1)%C]??[me,be],_e=K=>{let pe=ue[0]+(Fe[0]-ue[0])*K,ge=ue[1]+(Fe[1]-ue[1])*K,le=Math.hypot(pe,ge)||1;return[pe/le,ge/le]},re=[0,...T?T.breaks(Z,W,G,D):[],1];for(let K=0;K+1<re.length;K+=1){let pe=K===0,ge=K+2===re.length,le=Z+(G-Z)*re[K],Pe=W+(D-W)*re[K],Oe=Z+(G-Z)*re[K+1],Le=W+(D-W)*re[K+1],Me=Math.hypot(Oe-le,Le-Pe)/te*n.tileMetres,ze=(pe&&ne(S)?1:0)|(ge&&ne(S+1)?2:0)|(v>0?4:0),Ce=(pe&&un(Z,W)?1:0)|(ge&&un(G,D)?2:0),Ge=he+Me,Se=_e(re[K]),Ze=_e(re[K+1]),cn=Xe(le,Pe),an=Xe(Oe,Le),xe=u.write(ee,le,Pe,0,Se[0],Se[1],1,he,ze,Me,0,Ce,cn);u.write(ee,Oe,Le,0,Ze[0],Ze[1],1,Ge,ze,Me,1,Ce,an),u.write(ee,le,Pe,1,Se[0],Se[1],1,he,ze,Me,0,Ce,cn),u.write(ee,Oe,Le,1,Ze[0],Ze[1],1,Ge,ze,Me,1,Ce,an),l.push(xe,xe+1,xe+2,xe+1,xe+3,xe+2),he=Ge}}}g.roofsCount=i.length-g.roofsFrom,g.wallsCount=l.length-g.wallsFrom,a.push(g)}q&&c.push(R)}if(u.count===0)return;let m=new Uint32Array(i.length+l.length),b=[],d=0,x=i.length,U=i.values(),k=l.values();a.sort((y,I)=>y.cell-I.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 I=b.at(-1);for(let R=0;R<5;R+=1)I.bounds[R]=R<2?Math.min(I.bounds[R],y.bounds[R]):Math.max(I.bounds[R],y.bounds[R]);m.set(U.subarray(y.roofsFrom,y.roofsFrom+y.roofsCount),d),m.set(k.subarray(y.wallsFrom,y.wallsFrom+y.wallsCount),x),d+=y.roofsCount,x+=y.wallsCount,I.roofsCount+=y.roofsCount,I.wallsCount+=y.wallsCount}return{groups:b,vertices:u.bytes(),indices:m,wallsFrom:i.length,top:f,features:c}}function at(e,n,t,s){let o=[],r=[];for(let l of e)r.push(o.length/2),o.push(...l);r.push(o.length/2);let u=(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:!fe(o[l*2],o[l*2+1],o[c*2],o[c*2+1])},i=Ue(o,r.slice(1,-1),2);for(let l=0;l+2<i.length;l+=3){let c=[i[l],i[l+1],i[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())s.push(t.write(n,o[h*2],o[h*2+1],1,0,0,0,0,0,0,f,a))}}function ft(e,n,t,s){for(let o of e.faces){let r=o.ring,u=r.length/2,i=(l,c)=>{let[a,f]=l<c?[l,c]:[c,l];return f-a!==1&&!(a===0&&f===u-1)?!1:!fe(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)i(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(n,m,b,1,o.normal[0],o.normal[1],0,0,0,0,f,a,e.heightAt(m,b)))}}}}function ht(e,n,t,s=Math.PI/6){if(t<1)return{ring:e,normals:[]};let o=e.length/2,r=Ye(e)>0,u=n===0?r?-1:1:r?1:-1,i=[],l=[],c=(a,f,h)=>{a=ye(a),f=ye(f),!(i.at(-2)===a&&i.at(-1)===f)&&(i.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]=[e[f*2],e[f*2+1]],[d,x]=[e[a*2],e[a*2+1]],[U,k]=[e[h*2],e[h*2+1]],M=Math.hypot(d-m,x-b),y=Math.hypot(U-d,k-x);if(!M||!y||un(d,x)||fe(m,b,d,x)||fe(d,x,U,k)){c(d,x);continue}let[I,R]=[(d-m)/M,(x-b)/M],[V,B]=[(U-d)/y,(k-x)/y],j=Math.atan2(I*B-R*V,I*V+R*B);if(j*u>=-.08||Math.abs(j)>Math.PI*.85){c(d,x);continue}let Q=Math.tan(Math.abs(j)/2),$=Math.min(t*Q,M*.2,y*.2),L=$/Q;if(L<1){c(d,x);continue}let[z,q]=[-R*u,I*u],[Y,p]=[d-I*$-z*L,x-R*$-q*L],A=Math.atan2(q,z),w=Math.max(s<=Math.PI/6?2:1,Math.ceil(Math.abs(j)/Math.max(Math.PI/6,Math.min(Math.PI/3,s))));for(let g=0;g<=w;g+=1){let N=A+j*g/w,E=[Math.cos(N),Math.sin(N)];c(Y+E[0]*L,p+E[1]*L,E)}}return i[0]===i.at(-2)&&i[1]===i.at(-1)&&(i.splice(-2),l.pop()),i.length>=6?{ring:i,normals:l}:{ring:e,normals:[]}}var sn=class{#e=new ArrayBuffer(4096*tn);#n=new DataView(this.#e);count=0;write(n,t,s,o,r,u,i,l,c,a,f=0,h=0,m=n.top){let b=tn;if((this.count+1)*b>this.#e.byteLength){let U=new ArrayBuffer(this.#e.byteLength*2);new Uint8Array(U).set(new Uint8Array(this.#e)),this.#e=U,this.#n=new DataView(U)}let d=this.#n,x=this.count*b;return d.setUint16(x,ye(t),!0),d.setUint16(x+2,ye(s),!0),d.setUint16(x+4,Math.min(65535,Math.round(n.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(u*127)),d.setUint8(x+10,o),d.setUint8(x+11,i),d.setUint16(x+12,Math.round(l*10)%65536,!0),d.setUint8(x+14,Math.round(n.jitter*255)),d.setUint8(x+15,Math.round(n.seed*255)),d.setUint16(x+16,ye(n.cx),!0),d.setUint16(x+18,ye(n.cy),!0),d.setUint8(x+20,n.tone+4*n.group),d.setUint8(x+21,n.use),d.setUint8(x+22,c),d.setUint8(x+23,Math.min(255,Math.round(a))),d.setUint16(x+24,n.index,!0),d.setUint8(x+26,f),d.setUint8(x+27,h),this.count+=1,this.count-1}bytes(){return new Uint8Array(this.#e.slice(0,this.count*tn))}},Ve=class{#e=new Uint32Array(8192);length=0;push(...n){if(this.length+n.length>this.#e.length){let t=new Uint32Array(Math.max(this.length+n.length,this.#e.length*2));t.set(this.#e),this.#e=t}for(let t of n)this.#e[this.length++]=t}values(){return this.#e.subarray(0,this.length)}};function mt(e,n){let t=new Map;if(e.features.some(i=>i.properties.night_use!==void 0)){for(let[i,l]of e.features.entries()){let c=it[String(l.properties.night_use??"")];c&&t.set(i,c)}return t}let s=n.places;if(!s||s.features.length===0)return t;let o=ut/n.tileMetres*e.extent,r=[];for(let[i,l]of e.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:i,rings:l.geometry,box:a})}let u=e.extent/s.extent;for(let i of s.features){if(i.type!=="Point")continue;let l=i.properties,c=on[String(l.type??"")]??on[String(l.subclass??"")]??on[String(l.class??"")];if(c)for(let a of i.geometry)for(let f=0;f+1<a.length;f+=2){let h=a[f]*u,m=a[f+1]*u,b,d,x=o;for(let k of r){let[M,y,I,R]=k.box;if(h<M-o||h>I+o||m<y-o||m>R+o)continue;let V=!1;for(let B of k.rings)for(let j=0,Q=B.length-2;j+1<B.length;Q=j,j+=2){let $=B[Q],L=B[Q+1],z=B[j],q=B[j+1];q>m!=L>m&&h<($-z)*(m-q)/(L-q)+z&&(V=!V);let Y=z-$,p=q-L,A=Y*Y+p*p,w=A?Math.min(1,Math.max(0,((h-$)*Y+(m-L)*p)/A)):0,g=Math.hypot(h-$-w*Y,m-L-w*p);g<x&&(x=g,d=k.index)}if(V){b=k.index;break}}let U=b??d;U!==void 0&&t.set(U,Math.max(t.get(U)??0,c))}}return t}function ye(e){return Math.min(te,Math.max(0,Math.round(e)))}function Tn(e){let n=Number(e);return Number.isFinite(n)&&n>0?n:void 0}function bt(e,n){let t=[],s;for(let o of e){let r=[];for(let i=0;i+1<o.length;i+=2){let l=o[i]*n,c=o[i+1]*n;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 u=Math.sign(Ye(r));u&&(s??=u,u===s||t.length===0?t.push([r]):t.at(-1).push(r))}return t}function Ye(e){let n=0,t=e.length/2;for(let s=0;s<t;s+=1){let o=(s+1)%t;n+=e[s*2]*e[o*2+1]-e[o*2]*e[s*2+1]}return n}function xt(e){let n=0,t=0,s=e.length/2;for(let o=0;o<s;o+=1)n+=e[o*2],t+=e[o*2+1];return[n/s,t/s]}function un(e,n){return e<=0||n<=0||e>=te||n>=te}function fe(e,n,t,s){let o=te;return e<=0&&t<=0||e>=o&&t>=o||n<=0&&s<=0||n>=o&&s>=o}function dt(e){let n=te,t=[...e],s=[[0,0,o=>o>=0],[0,n,o=>o<=n],[1,0,o=>o>=0],[1,n,o=>o<=n]];for(let[o,r,u]of s){let i=[],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=u(a[o]),m=u(f[o]);if(h&&i.push(a[0],a[1]),h!==m){let b=(r-a[o])/(f[o]-a[o]);i.push(a[0]+(f[0]-a[0])*b,a[1]+(f[1]-a[1])*b)}}if(t=i,t.length<6)return[]}return t}function rn(e){let n=Math.sin(e*12.9898+78.233)*43758.5453;return n-Math.floor(n)}var ln=globalThis;ln.onmessage=({data:e})=>{try{let n=Un(e.layer,e.options);ln.postMessage({mesh:n},n?[n.vertices.buffer,n.indices.buffer]:[])}catch(n){ln.postMessage({error:String(n)})}};})();
|
package/docs/authoring-model.md
CHANGED
|
@@ -126,8 +126,8 @@ export default defineMap({
|
|
|
126
126
|
|
|
127
127
|
The standalone map above is complete as written. A map always owns its complete text provider; Core does not
|
|
128
128
|
obtain fonts or sprites from World and never invents a fallback URL. The URL-backed first-party
|
|
129
|
-
`streets`, `
|
|
130
|
-
providers directly. `baedeker`, `civica`, and `siegfried`
|
|
129
|
+
`streets`, `soundings`, and `blueprint` maps declare their glyph
|
|
130
|
+
providers directly. `baedeker`, `civica`, `map1777`, and `siegfried`
|
|
131
131
|
declare packaged fonts, so ordinary imports and derived maps compile without out-of-band release
|
|
132
132
|
metadata.
|
|
133
133
|
|
|
@@ -208,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.
|
|
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.
|