@tileflow/core 0.1.0-alpha.34 → 0.1.0-alpha.35
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 +13 -11
- package/THIRD_PARTY_NOTICES.md +100 -6
- package/dist/browser.d.ts +218 -21
- package/dist/browser.js +3049 -822
- package/dist/build.d.ts +9 -5
- package/dist/build.js +1284 -443
- package/dist/capture.js +1 -0
- package/dist/config-reference.json +1500 -868
- package/dist/{icon-composition-DqoZWQuz.d.ts → icon-composition-DSjIKmrK.d.ts} +98 -12
- package/dist/index.d.ts +231 -12
- package/dist/index.js +2542 -566
- package/dist/{manifest-types-hmzy2bBX.d.ts → manifest-types-Bzxon3dx.d.ts} +49 -8
- package/dist/manifest.d.ts +2 -2
- package/dist/manifest.js +80 -2
- package/dist/native-profile.js +1 -0
- package/dist/native.d.ts +13 -3
- package/dist/native.js +714 -318
- package/dist/runtime.d.ts +1 -1
- package/dist/runtime.js +124 -6
- package/docs/authoring-model.md +4 -0
- package/docs/buildings-3d.md +177 -28
- package/docs/compiled-style-performance.md +14 -3
- package/docs/data-is-separate-from-design.md +137 -5
- package/docs/native-artifact-profile.md +5 -0
- package/docs/native-resource-urls.md +5 -3
- package/docs/theme-transitions-and-blends.md +50 -0
- package/docs/trees-3d.md +63 -4
- package/package.json +4 -3
package/dist/runtime.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { C as TileflowAnalytics, a2 as TileflowManifestFetchOptions, a3 as TileflowManifestLoadOptions, a4 as TileflowMapMode, a5 as TileflowMapModeOptions, ai as TileflowResolvedRuntimeTheme, aG as TileflowRuntimeCenterLike, a as TileflowRuntimeColorScheme, T as TileflowRuntimeManifest, aH as TileflowRuntimeManifestMap, b as TileflowRuntimeManifestMapEntry, c as TileflowRuntimeManifestTheme, aI as TileflowRuntimeSource, aJ as TileflowRuntimeSourceValidation, aK as TileflowRuntimeStyle, aL as TileflowRuntimeStyleOptions, d as TileflowRuntimeSystemThemes, aM as TileflowRuntimeViewOptions, aN as TileflowSessionController, aO as TileflowSessionGrantResponse, aR as TileflowStyleFontFace, ba as TileflowThemeBlendSelection, bh as TileflowThemeSelection, e as TileflowTimeOfDay, f as TileflowTimeOfDayVariants, bp as assertValidTileflowRuntimeSource, bq as clearTileflowManifestCache, bs as createTileflowSessionController, bt as createTileflowSessionId, bu as defaultTileflowManifestUrl, bv as defaultTileflowRuntimeView, bx as getTileflowAnalyticsApiUrl, by as getTileflowStyleFontFaces, bz as inferTileflowAnalyticsFromStyleUrl, bA as isTileflowTimeOfDay, bB as loadTileflowManifest, bC as mergeTileflowAnalytics, bD as nearestTileflowBlendTheme, bE as normalizeTileflowRuntimeCenter, bF as normalizeTileflowStaticImageSize, c2 as normalizeTileflowSurfaceId, bG as normalizeTileflowUrl, bH as resolveTileflowAnalyticsRequestUrl, bI as resolveTileflowManifestMap, bJ as resolveTileflowMapMode, bK as resolveTileflowRuntimeStyle, bL as resolveTileflowRuntimeTheme, bM as resolveTileflowRuntimeThemeBlend, bN as resolveTileflowRuntimeView, bO as resolveTileflowStaticImageUrl, bP as shouldLoadTileflowManifest, bQ as startTileflowSession, bS as tileflowStyleFontFaceLimits, bT as tileflowStyleFontFacesMetadataKey, bU as tileflowThemeBlendLimits, bW as tileflowTimesOfDay, bY as validateTileflowRuntimeSource, bZ as validateTileflowThemeBlendSelection, b_ as validateTileflowThemeSelection } from './manifest-types-Bzxon3dx.js';
|
package/dist/runtime.js
CHANGED
|
@@ -29,12 +29,70 @@ var init_portable_identity_rules = __esm({
|
|
|
29
29
|
}
|
|
30
30
|
});
|
|
31
31
|
|
|
32
|
+
// src/time-of-day.ts
|
|
33
|
+
function isTileflowTimeOfDay(value) {
|
|
34
|
+
return typeof value === "string" && tileflowTimesOfDay.includes(value);
|
|
35
|
+
}
|
|
36
|
+
function validateTileflowTimeOfDayVariants(variants, themes) {
|
|
37
|
+
const issues = [];
|
|
38
|
+
const owners = /* @__PURE__ */ new Map();
|
|
39
|
+
const families = Object.entries(variants);
|
|
40
|
+
for (const [family, moments] of families) {
|
|
41
|
+
if (!isTileflowThemeName(family) || !Object.hasOwn(themes, family)) {
|
|
42
|
+
issues.push({ path: [family], message: "Time-of-day family must name an existing theme" });
|
|
43
|
+
}
|
|
44
|
+
if (!Object.values(moments).includes(family)) {
|
|
45
|
+
issues.push({ path: [family], message: "A family must include its own theme in its presets" });
|
|
46
|
+
}
|
|
47
|
+
for (const [moment, name] of Object.entries(moments)) {
|
|
48
|
+
if (name === void 0) continue;
|
|
49
|
+
if (!isTileflowTimeOfDay(moment) || !isTileflowThemeName(name) || !Object.hasOwn(themes, name)) {
|
|
50
|
+
issues.push({
|
|
51
|
+
path: [family, moment],
|
|
52
|
+
message: "Expected a supported moment and an existing theme"
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
const owner = owners.get(name);
|
|
56
|
+
if (owner !== void 0 && owner !== family) {
|
|
57
|
+
issues.push({
|
|
58
|
+
path: [family, moment],
|
|
59
|
+
message: `Theme "${name}" already belongs to family "${owner}"`
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
owners.set(name, family);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return issues;
|
|
66
|
+
}
|
|
67
|
+
function resolveTileflowTimeOfDayTheme(variants, theme, timeOfDay) {
|
|
68
|
+
if (timeOfDay === void 0 || variants === void 0) return theme;
|
|
69
|
+
if (!isTileflowTimeOfDay(timeOfDay)) throw new TypeError("Expected day, dawn, dusk, or night");
|
|
70
|
+
const family = Object.hasOwn(variants, theme) ? variants[theme] : Object.values(variants).find((moments) => Object.values(moments).includes(theme));
|
|
71
|
+
if (!family) return theme;
|
|
72
|
+
const result = family[timeOfDay];
|
|
73
|
+
if (result === void 0) {
|
|
74
|
+
throw new Error(
|
|
75
|
+
`Theme "${theme}" does not offer timeOfDay="${timeOfDay}". Available: ${Object.keys(family).join(", ")}.`
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
return result;
|
|
79
|
+
}
|
|
80
|
+
var tileflowTimesOfDay;
|
|
81
|
+
var init_time_of_day = __esm({
|
|
82
|
+
"src/time-of-day.ts"() {
|
|
83
|
+
"use strict";
|
|
84
|
+
init_portable_identity_rules();
|
|
85
|
+
tileflowTimesOfDay = ["day", "dawn", "dusk", "night"];
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
|
|
32
89
|
// src/manifest-types.ts
|
|
33
|
-
var tileflowRuntimeManifestVersion, tileflowRuntimeManifestLimits;
|
|
90
|
+
var tileflowRuntimeManifestVersion, tileflowTimeOfDayManifestVersion, tileflowRuntimeManifestLimits;
|
|
34
91
|
var init_manifest_types = __esm({
|
|
35
92
|
"src/manifest-types.ts"() {
|
|
36
93
|
"use strict";
|
|
37
94
|
tileflowRuntimeManifestVersion = 1;
|
|
95
|
+
tileflowTimeOfDayManifestVersion = 2;
|
|
38
96
|
tileflowRuntimeManifestLimits = Object.freeze({ maximumBytes: 1024 * 1024 });
|
|
39
97
|
}
|
|
40
98
|
});
|
|
@@ -103,6 +161,24 @@ var init_model = __esm({
|
|
|
103
161
|
}
|
|
104
162
|
});
|
|
105
163
|
|
|
164
|
+
// src/time-of-day-schema.ts
|
|
165
|
+
function createTileflowTimeOfDaySchema(operations, themeName) {
|
|
166
|
+
return operations.record(
|
|
167
|
+
themeName,
|
|
168
|
+
operations.object({
|
|
169
|
+
day: operations.optional(themeName),
|
|
170
|
+
dawn: operations.optional(themeName),
|
|
171
|
+
dusk: operations.optional(themeName),
|
|
172
|
+
night: operations.optional(themeName)
|
|
173
|
+
})
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
var init_time_of_day_schema = __esm({
|
|
177
|
+
"src/time-of-day-schema.ts"() {
|
|
178
|
+
"use strict";
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
|
|
106
182
|
// src/manifest-schema.ts
|
|
107
183
|
function createTileflowRuntimeManifestSchema(primitives, operations, identities = createPortableIdentitySchemas(operations)) {
|
|
108
184
|
const { tileflowPortableIdSchema: tileflowPortableIdSchema2, tileflowThemeNameSchema: tileflowThemeNameSchema2 } = identities;
|
|
@@ -158,12 +234,24 @@ function createTileflowRuntimeManifestSchema(primitives, operations, identities
|
|
|
158
234
|
})
|
|
159
235
|
),
|
|
160
236
|
themes: operations.record(tileflowThemeNameSchema2, manifestThemeSchema),
|
|
237
|
+
timeOfDay: operations.optional(
|
|
238
|
+
createTileflowTimeOfDaySchema(operations, tileflowThemeNameSchema2)
|
|
239
|
+
),
|
|
161
240
|
usageMode: operations.optional(operations.literal("session")),
|
|
162
241
|
view: operations.optional(viewSchema),
|
|
163
242
|
worldGeneration: operations.optional(operations.literal("v1"))
|
|
164
243
|
}),
|
|
165
244
|
(entry, context) => {
|
|
166
245
|
const names = Object.keys(entry.themes);
|
|
246
|
+
if (entry.timeOfDay) {
|
|
247
|
+
for (const issue of validateTileflowTimeOfDayVariants(entry.timeOfDay, entry.themes)) {
|
|
248
|
+
context.addIssue({
|
|
249
|
+
code: "custom",
|
|
250
|
+
message: issue.message,
|
|
251
|
+
path: ["timeOfDay", ...issue.path]
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
}
|
|
167
255
|
if (names.length === 0) {
|
|
168
256
|
context.addIssue({
|
|
169
257
|
code: "custom",
|
|
@@ -217,10 +305,23 @@ function createTileflowRuntimeManifestSchema(primitives, operations, identities
|
|
|
217
305
|
operations.object({
|
|
218
306
|
apiUrl: operations.optional(apiOriginSchema),
|
|
219
307
|
maps: operations.record(tileflowPortableIdSchema2, manifestMapSchema),
|
|
220
|
-
version: operations.
|
|
308
|
+
version: operations.union(
|
|
309
|
+
operations.literal(tileflowRuntimeManifestVersion),
|
|
310
|
+
operations.literal(2)
|
|
311
|
+
)
|
|
221
312
|
}),
|
|
222
313
|
(manifest, context) => {
|
|
223
314
|
const mapCount = Object.keys(manifest.maps).length;
|
|
315
|
+
if (manifest.version === 1) {
|
|
316
|
+
for (const [name, map] of Object.entries(manifest.maps)) {
|
|
317
|
+
if (map.timeOfDay !== void 0)
|
|
318
|
+
context.addIssue({
|
|
319
|
+
code: "custom",
|
|
320
|
+
message: "timeOfDay requires manifest version 2",
|
|
321
|
+
path: ["maps", name, "timeOfDay"]
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
}
|
|
224
325
|
if (mapCount === 0) {
|
|
225
326
|
context.addIssue({ code: "custom", message: "Expected at least one map", path: ["maps"] });
|
|
226
327
|
}
|
|
@@ -333,6 +434,8 @@ var init_manifest_schema = __esm({
|
|
|
333
434
|
init_portable_identity_schema();
|
|
334
435
|
init_runtime_view_schema();
|
|
335
436
|
init_model();
|
|
437
|
+
init_time_of_day();
|
|
438
|
+
init_time_of_day_schema();
|
|
336
439
|
}
|
|
337
440
|
});
|
|
338
441
|
|
|
@@ -387,6 +490,7 @@ var init_manifest_schema_classic = __esm({
|
|
|
387
490
|
return z2.enum(values);
|
|
388
491
|
},
|
|
389
492
|
literal: (value) => z2.literal(value),
|
|
493
|
+
union: (left, right) => z2.union([classic(left), classic(right)]),
|
|
390
494
|
tuplePair(left, right) {
|
|
391
495
|
return z2.tuple([classic(left), classic(right)]);
|
|
392
496
|
},
|
|
@@ -422,7 +526,8 @@ __export(manifest_exports, {
|
|
|
422
526
|
safeParseTileflowRuntimeManifest: () => safeParseTileflowRuntimeManifest,
|
|
423
527
|
tileflowRuntimeManifestLimits: () => tileflowRuntimeManifestLimits,
|
|
424
528
|
tileflowRuntimeManifestSchema: () => tileflowRuntimeManifestSchema,
|
|
425
|
-
tileflowRuntimeManifestVersion: () => tileflowRuntimeManifestVersion
|
|
529
|
+
tileflowRuntimeManifestVersion: () => tileflowRuntimeManifestVersion,
|
|
530
|
+
tileflowTimeOfDayManifestVersion: () => tileflowTimeOfDayManifestVersion
|
|
426
531
|
});
|
|
427
532
|
function parseTileflowRuntimeManifest(input) {
|
|
428
533
|
return tileflowRuntimeManifestSchema.parse(input);
|
|
@@ -451,6 +556,8 @@ var init_manifest = __esm({
|
|
|
451
556
|
|
|
452
557
|
// src/runtime.ts
|
|
453
558
|
init_portable_identity_rules();
|
|
559
|
+
init_time_of_day();
|
|
560
|
+
init_time_of_day();
|
|
454
561
|
var defaultTileflowManifestUrl = "/tileflow/manifest.json";
|
|
455
562
|
var defaultTileflowRuntimeView = Object.freeze({
|
|
456
563
|
bearing: 0,
|
|
@@ -532,7 +639,8 @@ function resolveTileflowRuntimeStyle(options) {
|
|
|
532
639
|
const theme = resolveTileflowRuntimeTheme(
|
|
533
640
|
options.manifestMap,
|
|
534
641
|
options.theme,
|
|
535
|
-
options.colorScheme
|
|
642
|
+
options.colorScheme,
|
|
643
|
+
options.timeOfDay
|
|
536
644
|
);
|
|
537
645
|
return {
|
|
538
646
|
analytics: {
|
|
@@ -549,7 +657,7 @@ function resolveTileflowRuntimeStyle(options) {
|
|
|
549
657
|
}
|
|
550
658
|
return null;
|
|
551
659
|
}
|
|
552
|
-
function resolveTileflowRuntimeTheme(map, selection, colorScheme) {
|
|
660
|
+
function resolveTileflowRuntimeTheme(map, selection, colorScheme, timeOfDay) {
|
|
553
661
|
let name;
|
|
554
662
|
if (selection === void 0) {
|
|
555
663
|
name = map.defaultTheme;
|
|
@@ -571,6 +679,8 @@ function resolveTileflowRuntimeTheme(map, selection, colorScheme) {
|
|
|
571
679
|
`Tileflow resolved invalid theme ${JSON.stringify(name)} for map "${map.name}"; expected a concrete portable theme name.`
|
|
572
680
|
);
|
|
573
681
|
}
|
|
682
|
+
if (Object.hasOwn(map.themes, name))
|
|
683
|
+
name = resolveTileflowTimeOfDayTheme(map.timeOfDay, name, timeOfDay);
|
|
574
684
|
const theme = Object.hasOwn(map.themes, name) ? map.themes[name] : void 0;
|
|
575
685
|
if (!theme) {
|
|
576
686
|
const available = Object.keys(map.themes).sort().join(", ");
|
|
@@ -1106,6 +1216,7 @@ function resolveTileflowManifestMap(manifest, mapName) {
|
|
|
1106
1216
|
defaultTheme: entry.defaultTheme,
|
|
1107
1217
|
...entry.environment ? { environment: entry.environment } : {},
|
|
1108
1218
|
...entry.systemThemes ? { systemThemes: { ...entry.systemThemes } } : {},
|
|
1219
|
+
...entry.timeOfDay ? { timeOfDay: entry.timeOfDay } : {},
|
|
1109
1220
|
themes: entry.themes,
|
|
1110
1221
|
usageMode: entry.usageMode,
|
|
1111
1222
|
view: entry.view,
|
|
@@ -1123,7 +1234,12 @@ function resolveTileflowStaticImageUrl(input) {
|
|
|
1123
1234
|
const apiUrl = normalizeTileflowUrl(input.manifestMap.apiUrl ?? "https://api.tileflow.dev");
|
|
1124
1235
|
const mapId = encodeURIComponent(input.manifestMap.mapId);
|
|
1125
1236
|
const url = new URL(`/maps/${mapId}/static.png`, apiUrl);
|
|
1126
|
-
const theme = resolveTileflowRuntimeTheme(
|
|
1237
|
+
const theme = resolveTileflowRuntimeTheme(
|
|
1238
|
+
input.manifestMap,
|
|
1239
|
+
input.theme,
|
|
1240
|
+
input.colorScheme,
|
|
1241
|
+
input.timeOfDay
|
|
1242
|
+
);
|
|
1127
1243
|
url.searchParams.set("center", input.center.join(","));
|
|
1128
1244
|
url.searchParams.set("theme", theme.name);
|
|
1129
1245
|
url.searchParams.set("zoom", String(input.zoom));
|
|
@@ -1291,6 +1407,7 @@ export {
|
|
|
1291
1407
|
getTileflowAnalyticsApiUrl,
|
|
1292
1408
|
getTileflowStyleFontFaces,
|
|
1293
1409
|
inferTileflowAnalyticsFromStyleUrl,
|
|
1410
|
+
isTileflowTimeOfDay,
|
|
1294
1411
|
loadTileflowManifest,
|
|
1295
1412
|
mergeTileflowAnalytics,
|
|
1296
1413
|
nearestTileflowBlendTheme,
|
|
@@ -1311,6 +1428,7 @@ export {
|
|
|
1311
1428
|
tileflowStyleFontFaceLimits,
|
|
1312
1429
|
tileflowStyleFontFacesMetadataKey,
|
|
1313
1430
|
tileflowThemeBlendLimits,
|
|
1431
|
+
tileflowTimesOfDay,
|
|
1314
1432
|
validateTileflowRuntimeSource,
|
|
1315
1433
|
validateTileflowThemeBlendSelection,
|
|
1316
1434
|
validateTileflowThemeSelection
|
package/docs/authoring-model.md
CHANGED
|
@@ -215,6 +215,10 @@ surveyed isoline. `water({bathymetryLabels: {}})` separately opts into numeric m
|
|
|
215
215
|
from each polygon's absolute minimum band depth. Existing maps gain neither detail by default, and
|
|
216
216
|
sources without bathymetry bindings omit both requested layers. These labels describe coarse band
|
|
217
217
|
floors, not measured survey soundings, and must not be presented as hydrographic sounding data.
|
|
218
|
+
An explicitly selected Bathymetry source can instead declare `vectorLayers.contours: true` to use
|
|
219
|
+
real contour lines and their `depth` values. Its optional named seafloor and water features have
|
|
220
|
+
separate `water.bathymetryLandforms` and `water.bathymetryWaterNames` styles, with the language
|
|
221
|
+
selected by `labels(...)`. See [the marine source contract](data-is-separate-from-design.md#declare-contour-and-name-layers).
|
|
218
222
|
|
|
219
223
|
`addresses` renders the standard OpenMapTiles `housenumber` points at detailed zooms and exposes a
|
|
220
224
|
single semantic `labels` style. `landforms` renders the standard `mountain_peak` classes (`peak`,
|
package/docs/buildings-3d.md
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
# 3D buildings
|
|
2
2
|
|
|
3
|
-
`createTileflowBuildings3D` raises the buildings of a Tileflow map's vector tiles round the camera
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
3
|
+
`createTileflowBuildings3D` raises the buildings of a Tileflow map's vector tiles round the camera.
|
|
4
|
+
By default it preserves the map's building colours and native extrusion lighting (`style.light`
|
|
5
|
+
and its vertical wall gradient). Showing, hiding and changing height are immediate. Cast shadows,
|
|
6
|
+
contact shadow, illuminated outlines, random colour variation and night lights are off.
|
|
7
|
+
|
|
8
|
+
Scene sun lighting, shadows, decorative effects and animated height remain available through
|
|
9
|
+
explicit options. They are independent of the map's trees.
|
|
10
|
+
|
|
11
|
+
Tileflow landmarks and a map's own building models can stand in the same 3D scene, replacing
|
|
12
|
+
the buildings under their footprints.
|
|
9
13
|
|
|
10
14
|
It is a custom WebGL2 layer for MapLibre GL JS, drawn above the map's buildings and below its
|
|
11
15
|
labels. It needs WebGL2 and the Mercator projection, which MapLibre's globe turns into from about
|
|
@@ -27,23 +31,27 @@ import {
|
|
|
27
31
|
|
|
28
32
|
export function raiseTheCity(map: TileflowBuildings3DMap, controller: TileflowThemeController) {
|
|
29
33
|
const buildings = createTileflowBuildings3D({map, controller});
|
|
30
|
-
//
|
|
31
|
-
void buildings.show(
|
|
34
|
+
// No animation unless requested.
|
|
35
|
+
void buildings.show();
|
|
32
36
|
return buildings;
|
|
33
37
|
}
|
|
34
38
|
```
|
|
35
39
|
|
|
36
|
-
`show()
|
|
37
|
-
|
|
38
|
-
|
|
40
|
+
`show()`, `hide()` and `grow(scale)` change the height immediately by default (duration 0).
|
|
41
|
+
`show({transition: {duration: 800, mode: 'fade'}})` reveals full-height buildings with a
|
|
42
|
+
dithered fade. The default animation mode is `grow`; both modes respect reduced-motion settings.
|
|
43
|
+
To animate, pass `{transition: {duration: 800}}` explicitly; `grow(scale)` accepts a height share
|
|
44
|
+
from 0 to 1. Each returns a promise that resolves once the change ends; a newer call takes over from where the buildings stand. A browser preference for reduced
|
|
39
45
|
motion changes at once. `dispose()` takes the layer off the map.
|
|
40
46
|
|
|
41
47
|
The renderer reads the source's z15 tiles round the camera (z15 is where World's buildings carry
|
|
42
48
|
their heights; a source that stops earlier is read at its own maximum zoom), up to `maxTiles`
|
|
43
49
|
tiles (25 by default), and meshes them a few milliseconds per frame. It reads World's
|
|
44
|
-
`render_height`, `render_min_height`, `hide_3d`, `building_tone
|
|
45
|
-
|
|
46
|
-
|
|
50
|
+
`render_height`, `render_min_height`, `hide_3d`, `height_source`, `building_tone`, `class`,
|
|
51
|
+
`night_use` and the roof fields (see [roofs](#roofs)), and the places in World's `poi` layer (see
|
|
52
|
+
[night light](#night-light)). `stats.declared` tells whether the map's
|
|
53
|
+
style describes its 3D buildings. Recompile existing maps to pick up the new defaults: previously
|
|
54
|
+
compiled metadata with explicit effects enabled continues to honour those values.
|
|
47
55
|
|
|
48
56
|
## Design the buildings in the map
|
|
49
57
|
|
|
@@ -66,7 +74,16 @@ export default defineMap({
|
|
|
66
74
|
extends: streets,
|
|
67
75
|
modules: {
|
|
68
76
|
buildings: refine({
|
|
69
|
-
threeDimensional: {
|
|
77
|
+
threeDimensional: {
|
|
78
|
+
lighting: 'sun',
|
|
79
|
+
shadows: true,
|
|
80
|
+
occlusion: true,
|
|
81
|
+
edges: 1,
|
|
82
|
+
colorVariation: 0.06,
|
|
83
|
+
windows: true,
|
|
84
|
+
nightLight: 0.6,
|
|
85
|
+
edgeColor: '#2b2a33',
|
|
86
|
+
},
|
|
70
87
|
renderStack: {
|
|
71
88
|
// Core's 3D buildings take the colour of the map's 3D building pass.
|
|
72
89
|
tints: refineRenderTarget({
|
|
@@ -91,14 +108,140 @@ export default defineMap({
|
|
|
91
108
|
set of themes changes, and the themes either side of a blend's position mix on the GPU, so a
|
|
92
109
|
moving blend costs nothing per frame.
|
|
93
110
|
- `threeDimensional.shadows` and `occlusion` (the band at the foot of the walls and the darker tight
|
|
94
|
-
corners) default to
|
|
95
|
-
decoration, not data, and are off by default.
|
|
111
|
+
corners) default to false. `windows: true` adds invented night windows, lit by rooms; they are
|
|
112
|
+
decoration, not data, and are off by default. Where World says what a building is used for
|
|
113
|
+
(`class`), sheds, garages, barns, factories, churches and service buildings get no rows of
|
|
114
|
+
windows, homes light more of theirs at night, in a warm light, and offices, schools and hospitals
|
|
115
|
+
fewer, in a cooler one.
|
|
96
116
|
- `threeDimensional.edges` and `edgeColor` set the [edges](#edges), and `nightLight` the
|
|
97
117
|
[night light](#night-light). Numbers and colours may be theme tokens.
|
|
98
118
|
|
|
119
|
+
## Landmarks
|
|
120
|
+
|
|
121
|
+
Maps on Tileflow World show Tileflow's landmarks, such as the Eiffel Tower, as 3D models in place of
|
|
122
|
+
the buildings under them. They come from a landmarks tileset kept apart from the map's data: each of
|
|
123
|
+
its tiles carries the models standing in it, with their levels of detail and footprints, and each
|
|
124
|
+
region's index names its tiles by their hash. The renderer reads the tileset's TileJSON once the
|
|
125
|
+
buildings show in 3D, then only the indexes of the regions round the camera and the landmark tiles
|
|
126
|
+
near it, so a landmark in Paris costs nothing in Madrid. New and improved landmarks reach every map
|
|
127
|
+
without a new build of the map. A tileset that cannot be read is tried again after five minutes,
|
|
128
|
+
and the buildings stay drawn meanwhile.
|
|
129
|
+
|
|
130
|
+
A map switches them off, or names the TileJSON of another landmarks tileset:
|
|
131
|
+
|
|
132
|
+
<!-- docs:check -->
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
import {defineMap, refine} from '@tileflow/core';
|
|
136
|
+
import {streets} from '@tileflow/maps';
|
|
137
|
+
|
|
138
|
+
export default defineMap({
|
|
139
|
+
id: 'no-landmarks',
|
|
140
|
+
name: 'Streets without landmarks',
|
|
141
|
+
version: 1,
|
|
142
|
+
extends: streets,
|
|
143
|
+
modules: {
|
|
144
|
+
buildings: refine({threeDimensional: {landmarks: false}}),
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
The map's own building models come first: a landmark with the same id as one of them, or whose
|
|
150
|
+
footprint meets one's, is not drawn.
|
|
151
|
+
|
|
152
|
+
## Building models
|
|
153
|
+
|
|
154
|
+
A map can place its own 3D models of buildings in the city: a landmark the extruded footprints draw
|
|
155
|
+
badly, or the buildings an application presents. Each model stands where the map says, in the same
|
|
156
|
+
light, shadows and theme as the buildings round it, and the buildings under its footprint are no
|
|
157
|
+
longer raised while it stands. Put glTF binaries (`.glb`) in a directory, declare it in the map's
|
|
158
|
+
`models`, and say where each model stands:
|
|
159
|
+
|
|
160
|
+
<!-- docs:check -->
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
import {defineMap, refine} from '@tileflow/core';
|
|
164
|
+
import {streets, streetsModels} from '@tileflow/maps';
|
|
165
|
+
|
|
166
|
+
export default defineMap({
|
|
167
|
+
id: 'paris-landmarks',
|
|
168
|
+
name: 'Paris landmarks',
|
|
169
|
+
version: 1,
|
|
170
|
+
extends: streets,
|
|
171
|
+
// ./models holds eiffel-tower.glb, and eiffel-tower.mid.glb and eiffel-tower.far.glb for
|
|
172
|
+
// farther views.
|
|
173
|
+
// Keep Streets' tree models alongside the building models.
|
|
174
|
+
models: [streetsModels, './models'],
|
|
175
|
+
modules: {
|
|
176
|
+
buildings: refine({
|
|
177
|
+
threeDimensional: {
|
|
178
|
+
models: [{model: 'eiffel-tower', at: [2.294481, 48.858258], heading: 0}],
|
|
179
|
+
modelColors: {surface: '#7a5c45', accent: '#5b4332'},
|
|
180
|
+
},
|
|
181
|
+
}),
|
|
182
|
+
},
|
|
183
|
+
});
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
`at` is the longitude and latitude of the model's origin and `heading` the degrees clockwise from
|
|
187
|
+
north its own north is turned to. A map places up to 256 models; one model may stand in several
|
|
188
|
+
places. A placement may instead name a prepared model file served elsewhere with `url`, its HTTPS
|
|
189
|
+
address; the map then needs no `.glb` of its own for it.
|
|
190
|
+
|
|
191
|
+
How to prepare a model:
|
|
192
|
+
|
|
193
|
+
- **Materials name the parts.** Name each material `surface`, `secondary`, `glass`, `accent` or
|
|
194
|
+
`roof` (an exporter's `.001` suffix is ignored); a model whose materials name a tree's parts is a
|
|
195
|
+
[tree model](https://github.com/tileflow/tileflow-sdk/blob/main/packages/core/docs/trees-3d.md)
|
|
196
|
+
instead. Each part takes its colour from `modelColors`, or from the buildings' colour when the
|
|
197
|
+
design gives it none, so the model follows the theme from day to night; vertex colours lighter or
|
|
198
|
+
darker than the rest of their part shade it within that colour.
|
|
199
|
+
- **Metres round an origin on the ground.** The origin is where the model stands, with the model
|
|
200
|
+
above it and its own north along +y once read Z up; nothing may lie more than half a metre below
|
|
201
|
+
it.
|
|
202
|
+
- **What it stands in for.** A mesh whose material is named `footprint` is never drawn: it lays
|
|
203
|
+
out on the ground what the model stands in for, such as a landmark's whole base. Without one, the
|
|
204
|
+
model's own outline on the ground stands in, courtyards included.
|
|
205
|
+
- **Levels of detail.** `<id>.mid.glb` and `<id>.far.glb`, when present, are drawn farther from the
|
|
206
|
+
camera's centre, from about 900 m and 2.6 km.
|
|
207
|
+
- **Budgets.** A level has at most 200,000 triangles and 200,000 vertices, and a map at most 64
|
|
208
|
+
building models. Triangles of the default scene are read with their node transforms; textures,
|
|
209
|
+
animations and compressed geometry (Draco or meshopt) are not.
|
|
210
|
+
|
|
211
|
+
The build reads each model, reports a problem with the file and what to change, works out its
|
|
212
|
+
footprint and writes it beside the style as a prepared model file: a glTF binary whose meshes are
|
|
213
|
+
quantised (`KHR_mesh_quantization`) and compressed by meshoptimizer (`EXT_meshopt_compression`),
|
|
214
|
+
which any glTF viewer opens. The renderer downloads a model only when it stands near the tiles round
|
|
215
|
+
the camera, keeps the nearest dozen, and builds the tiles under it again without the buildings that
|
|
216
|
+
reach more than half a metre into its footprint; a neighbour that only touches it stays. A model
|
|
217
|
+
that cannot be downloaded leaves those buildings standing. Reading a prepared model file loads
|
|
218
|
+
meshoptimizer's WebAssembly decoder, so a page with a Content Security Policy allows
|
|
219
|
+
`'wasm-unsafe-eval'` in its `script-src`.
|
|
220
|
+
|
|
221
|
+
## Roofs
|
|
222
|
+
|
|
223
|
+
A building whose tiles carry a roof shape (World's `roof_shape`) gets that roof: `gabled`,
|
|
224
|
+
`hipped`, `half_hipped`, `pyramidal`, `skillion`, `gambrel`, `mansard` and `saltbox` roofs, and
|
|
225
|
+
faceted `dome`, `onion` and `round` ones. The ridge follows the building's long side, or
|
|
226
|
+
`roof_orientation` and `roof_direction` when the tiles state them, and the walls rise to the roof,
|
|
227
|
+
so gable ends are walls. Each slope takes the sun, shadows and edges like any face: the ridge and
|
|
228
|
+
the hips are drawn as edges too.
|
|
229
|
+
|
|
230
|
+
The roof's height is `roof_height` where the tiles state it, taken from the building's height.
|
|
231
|
+
Otherwise the roof keeps the pitch of its shape (30° for a gabled or hipped roof, a half sphere for a
|
|
232
|
+
dome); it stands on the walls when the height came from levels or a fallback, and reaches the
|
|
233
|
+
building's height when that was measured (`height_source` `height`). A building without a roof
|
|
234
|
+
shape, a flat roof, a building with courtyards or one far from a rectangle (an L, a U) keeps a flat
|
|
235
|
+
roof, and so does a building a tile's buffer cuts short.
|
|
236
|
+
|
|
99
237
|
## Light and shadows
|
|
100
238
|
|
|
101
|
-
|
|
239
|
+
`threeDimensional.lighting` defaults to `'style'`: it uses MapLibre's `light` position,
|
|
240
|
+
anchor, colour and intensity, together with the authored extrusion's `verticalGradient`.
|
|
241
|
+
Changing the scene sun does not change this base lighting. The style's native lighting defaults
|
|
242
|
+
apply when it declares no light.
|
|
243
|
+
|
|
244
|
+
With `lighting: 'sun'`, the light comes from `controller.getSun()`: the theme's sun, mixed during a blend, or the sun an
|
|
102
245
|
application places by direction or by date and place (see
|
|
103
246
|
[sun and light](https://github.com/tileflow/tileflow-sdk/blob/main/packages/core/docs/sun-and-light.md)).
|
|
104
247
|
A sunlit roof keeps the building's colour, a wall turned to a low sun glows, and shade takes the
|
|
@@ -113,15 +256,16 @@ buildings, it multiplies the ground by the shadows and by a band at the foot of
|
|
|
113
256
|
and icons drawn above it keep their colours. Layers kept under 3D content with
|
|
114
257
|
`createTileflowGroundLayers` are darkened with the ground. Other Core 3D layers on the map, such as
|
|
115
258
|
[3D trees](https://github.com/tileflow/tileflow-sdk/blob/main/packages/core/docs/trees-3d.md), share
|
|
116
|
-
the buildings' shadows
|
|
259
|
+
the buildings' shadows when `shadows: true`. With it off, buildings neither cast nor receive
|
|
260
|
+
scene shadows, even when another 3D layer uses them.
|
|
117
261
|
|
|
118
262
|
## Edges
|
|
119
263
|
|
|
120
264
|
Where one building ends and the next starts stays legible at any distance: each roof's outline and
|
|
121
|
-
each building's outer corners catch the light in a line one CSS pixel wide, whatever the zoom,
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
a tile's edge draw none. `edges` sets their strength from 0 (none) to 1
|
|
265
|
+
each building's outer corners catch the light in a line one CSS pixel wide, whatever the zoom, when enabled. `colorVariation` independently varies building brightness by up to the specified
|
|
266
|
+
fraction (0–1); it defaults to 0, so authored colours remain unchanged. Use 0.06 for the earlier
|
|
267
|
+
6% variation. The lines are strongest at night; diagonals inside a roof, inner corners and the cuts at
|
|
268
|
+
a tile's edge draw none. `edges` sets their strength from 0 (the default, none) to 1.
|
|
125
269
|
|
|
126
270
|
`edgeColor` gives the lines a colour of the map's own, per theme through a token: a dark outline as
|
|
127
271
|
on a printed map, or a neon one at night. It is drawn as it is, unlit, at the full `edges` strength
|
|
@@ -129,8 +273,8 @@ by day and night, mixed between the themes either side of a blend's position.
|
|
|
129
273
|
|
|
130
274
|
## Night light
|
|
131
275
|
|
|
132
|
-
|
|
133
|
-
|
|
276
|
+
With `nightLight` above 0, the buildings light the street from data at night (as the sun's
|
|
277
|
+
`brightness` falls):
|
|
134
278
|
|
|
135
279
|
- Landmarks (World's `building_tone` `destination`) and towers of 40 m or more, one in two, are
|
|
136
280
|
flood-lit at their foot, on the wall and the ground around it.
|
|
@@ -144,7 +288,7 @@ The night use comes from World's `night_use` on each building. In tiles whose bu
|
|
|
144
288
|
carry it, the renderer finds it in the tile's places: a place inside a building, or within a few
|
|
145
289
|
metres of its facade, gives the building its use.
|
|
146
290
|
|
|
147
|
-
`nightLight` sets how bright all of this shines, from 0 (none) to 2; it defaults to
|
|
291
|
+
`nightLight` sets how bright all of this shines, from 0 (none) to 2; it defaults to 0.
|
|
148
292
|
|
|
149
293
|
For the streets themselves, see
|
|
150
294
|
[street lights](https://github.com/tileflow/tileflow-sdk/blob/main/packages/core/docs/street-lights.md),
|
|
@@ -167,6 +311,11 @@ way kept the frame pacing of the same city drawn without shadows.
|
|
|
167
311
|
|
|
168
312
|
- Mercator only; the layer draws nothing below its `minzoom`.
|
|
169
313
|
- No terrain: buildings stand on flat ground.
|
|
170
|
-
- One building colour per feature and theme
|
|
171
|
-
|
|
314
|
+
- One building colour per feature and theme, on its roof and walls alike; facades are not drawn.
|
|
315
|
+
- Roofs follow the building's smallest enclosing rectangle: a building with courtyards or far from a
|
|
316
|
+
rectangle keeps a flat roof, and `sawtooth` roofs are drawn flat.
|
|
172
317
|
- A colour expression is evaluated at zoom 16.
|
|
318
|
+
- Building models and landmarks take no edges or night light of their own yet, and cannot be
|
|
319
|
+
picked or styled by state.
|
|
320
|
+
- Hosted deploys do not take a map's own model files yet: `tileflow validate --target hosted` reports
|
|
321
|
+
them. Models named by `url` deploy as they are.
|
|
@@ -249,8 +249,19 @@ The closed category vocabulary is `arts-entertainment`, `education`, `food-drink
|
|
|
249
249
|
`transport`, and `visitor-amenity`. Producer `type` and `icon` values use stable snake_case names.
|
|
250
250
|
When an `icon` sprite exists it is used directly; an unknown or absent icon safely falls back to the
|
|
251
251
|
category's themed image role. `labels` and `icons` are booleans. Category styles control only
|
|
252
|
-
presentation and
|
|
253
|
-
`placement.coupleIconAndLabel` keeps a POI's icon and label atomic during collision placement
|
|
252
|
+
presentation and never promote candidates above the producer's selection.
|
|
253
|
+
`placement.coupleIconAndLabel` keeps a POI's icon and label atomic during collision placement:
|
|
254
|
+
both are required.
|
|
255
|
+
|
|
256
|
+
MapLibre orders symbol placement only within a layer, so the module compiles every category into
|
|
257
|
+
one layer per representation and orders it by `filter_rank` and `size_rank`: a station is placed
|
|
258
|
+
before a cash machine whatever their categories. Per-category appearance becomes a `match` on
|
|
259
|
+
`category`; zoom curves are hoisted above it so each category keeps its exact curve. Categories that
|
|
260
|
+
need different layer-wide properties use `placement.order: 'categories'`, one layer per category in
|
|
261
|
+
listed order. `placement.prominentRank` places the most important ranks in a second layer above road
|
|
262
|
+
and place labels, which keeps landmarks visible on maps whose labels give street names priority.
|
|
263
|
+
When icons and labels are both drawn, unnamed POIs are omitted except the self-explanatory types in
|
|
264
|
+
`placement.iconOnlyTypes` (parking and public toilets by default), which draw only their pictogram.
|
|
254
265
|
|
|
255
266
|
```ts
|
|
256
267
|
poi({
|
|
@@ -263,4 +274,4 @@ poi({
|
|
|
263
274
|
|
|
264
275
|
The same contract drives regular POI layers, custom HUD treatments, semantic interactions, and
|
|
265
276
|
feature inspection. There is intentionally no compatibility path for the former class mapping,
|
|
266
|
-
rank ceilings, or named density/detail presets.
|
|
277
|
+
rank ceilings, per-category densities, or named density/detail presets.
|