@tileflow/core 0.1.0-alpha.20 → 0.1.0-alpha.22
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/GENERATED_OUTPUT_LICENSE.md +21 -0
- package/LICENSE +201 -0
- package/NOTICE +9 -0
- package/README.md +1097 -145
- package/THIRD_PARTY_NOTICES.md +243 -0
- package/TRADEMARKS.md +10 -0
- package/dist/browser.d.ts +182 -13
- package/dist/browser.js +4207 -116
- package/dist/build.d.ts +158 -0
- package/dist/build.js +14836 -0
- package/dist/capture-scene-BK_0J2oR.d.ts +161 -0
- package/dist/capture.d.ts +9 -0
- package/dist/capture.js +206 -0
- package/dist/config-reference.json +31882 -0
- package/dist/index.d.ts +643 -182
- package/dist/index.js +15943 -4352
- package/dist/manifest-B5GKVMFE.d.ts +757 -0
- package/dist/manifest.d.ts +2 -0
- package/dist/manifest.js +245 -0
- package/dist/map-BHL_0Mkx.d.ts +1145 -0
- package/dist/runtime.d.ts +2 -0
- package/dist/runtime.js +1172 -0
- package/package.json +37 -5
- package/dist/runtime-DroKDIuB.d.ts +0 -1069
package/README.md
CHANGED
|
@@ -1,93 +1,346 @@
|
|
|
1
1
|
# @tileflow/core
|
|
2
2
|
|
|
3
|
-
Typed Tileflow
|
|
3
|
+
Typed Tileflow map contracts, semantic modules, and deterministic MapLibre style compilation.
|
|
4
|
+
|
|
5
|
+
Every public `tileflow.config.ts` exports one map. Most maps import an existing map and override only
|
|
6
|
+
the design fields they own:
|
|
4
7
|
|
|
5
8
|
```ts
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
9
|
+
import {
|
|
10
|
+
defineMap,
|
|
11
|
+
defineTheme,
|
|
12
|
+
fixed,
|
|
13
|
+
labels,
|
|
14
|
+
poi,
|
|
15
|
+
roads,
|
|
16
|
+
token,
|
|
17
|
+
water,
|
|
18
|
+
zoom,
|
|
19
|
+
} from '@tileflow/core';
|
|
20
|
+
import {streets, streetsThemes} from '@tileflow/maps';
|
|
21
|
+
|
|
22
|
+
const madridDark = defineTheme(streetsThemes.dark, {
|
|
23
|
+
id: 'madrid-dark',
|
|
24
|
+
version: 1,
|
|
25
|
+
colorScheme: 'dark',
|
|
26
|
+
tokens: {color: {'surface.land': '#0d1320', 'surface.water': '#081e2e'}},
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
export default defineMap({
|
|
30
|
+
id: 'madrid',
|
|
31
|
+
name: 'Madrid',
|
|
32
|
+
version: 1,
|
|
33
|
+
extends: streets,
|
|
34
|
+
themes: {light: streetsThemes.light, dark: madridDark},
|
|
35
|
+
defaultTheme: 'light',
|
|
36
|
+
systemThemes: {light: 'light', dark: 'dark'},
|
|
37
|
+
projection: 'globe',
|
|
38
|
+
modules: {
|
|
39
|
+
water: water({
|
|
40
|
+
bodies: {
|
|
41
|
+
fill: {
|
|
42
|
+
opacity: fixed(0.95, {reason: 'Madrid keeps this water density in every theme'}),
|
|
43
|
+
},
|
|
25
44
|
},
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
primary: {
|
|
40
|
-
surface: {
|
|
41
|
-
fill: {
|
|
42
|
-
color: '#E4A85B',
|
|
43
|
-
width: zoom.linear([
|
|
44
|
-
[7, 0.6],
|
|
45
|
-
[16, 8],
|
|
46
|
-
]),
|
|
47
|
-
},
|
|
48
|
-
},
|
|
45
|
+
}),
|
|
46
|
+
roads: roads({
|
|
47
|
+
detail: 'streets',
|
|
48
|
+
hierarchy: 'strong',
|
|
49
|
+
classes: {
|
|
50
|
+
primary: {
|
|
51
|
+
surface: {
|
|
52
|
+
fill: {
|
|
53
|
+
color: fixed('#E4A85B', {reason: 'Madrid brand roads stay warm in every theme'}),
|
|
54
|
+
width: zoom.linear([
|
|
55
|
+
[7, fixed(0.6, {reason: 'Madrid keeps its primary-road hierarchy across themes'})],
|
|
56
|
+
[16, fixed(8, {reason: 'Madrid keeps its primary-road hierarchy across themes'})],
|
|
57
|
+
]),
|
|
49
58
|
},
|
|
50
59
|
},
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
60
|
+
},
|
|
61
|
+
secondary: {surface: {fill: {color: token.color('roads.secondary')}}},
|
|
62
|
+
},
|
|
63
|
+
}),
|
|
64
|
+
labels: labels({
|
|
65
|
+
language: 'local',
|
|
66
|
+
places: 'all',
|
|
67
|
+
roads: 'streets',
|
|
68
|
+
styles: {
|
|
69
|
+
places: {
|
|
70
|
+
city: {
|
|
71
|
+
text: {
|
|
72
|
+
size: fixed(18, {reason: 'Madrid keeps its city-label scale across themes'}),
|
|
73
|
+
haloWidth: fixed(1.5, {reason: 'Madrid keeps its city-label halo across themes'}),
|
|
74
|
+
},
|
|
58
75
|
},
|
|
59
|
-
}
|
|
60
|
-
poi: poi({categories: ['food', 'culture', 'transit'], color: 'category'}),
|
|
76
|
+
},
|
|
61
77
|
},
|
|
62
|
-
|
|
63
|
-
},
|
|
78
|
+
}),
|
|
79
|
+
poi: poi({categories: ['food-drink', 'arts-entertainment', 'transport'], color: 'category'}),
|
|
64
80
|
},
|
|
81
|
+
view: {center: [-3.7038, 40.4168], pitch: 35, zoom: 12},
|
|
65
82
|
});
|
|
66
83
|
```
|
|
67
84
|
|
|
85
|
+
Map identity and scenes belong to the leaf and do not inherit. Hosted browser policy is configured
|
|
86
|
+
in Tileflow and does not enter cartographic authoring or compilation.
|
|
87
|
+
|
|
88
|
+
Set `projection: 'globe'` for MapLibre's adaptive globe preset. Global zooms render as a sphere,
|
|
89
|
+
then transition to Mercator between zoom 10 and 12 so detailed streets remain planar. Omit the
|
|
90
|
+
property, or set it to `'mercator'`, for a consistently flat map.
|
|
91
|
+
|
|
92
|
+
Terrain keeps the original `'none' | 'hillshade' | '3d'` shorthand. The object form can style every
|
|
93
|
+
MapLibre hillshade paint control, or generate vector contours in the browser from an explicit DEM
|
|
94
|
+
tile template. Contours require `demUrl`, `demMaxZoom`, and zoom-indexed `[minor, index]`
|
|
95
|
+
`thresholds`; the raster `url` remains the separate TileJSON endpoint. Set `mode: 'none'` to compile
|
|
96
|
+
contours without adding a raster source, hillshade, or 3D terrain:
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
terrain: {
|
|
100
|
+
mode: 'none',
|
|
101
|
+
encoding: 'terrarium',
|
|
102
|
+
contours: {
|
|
103
|
+
demUrl: 'https://terrain.example.test/{z}/{x}/{y}.webp',
|
|
104
|
+
demMaxZoom: 13,
|
|
105
|
+
maxZoom: 15,
|
|
106
|
+
overzoom: 2,
|
|
107
|
+
thresholds: {
|
|
108
|
+
9: [100, 500],
|
|
109
|
+
11: [50, 250],
|
|
110
|
+
13: [20, 100],
|
|
111
|
+
15: [10, 50],
|
|
112
|
+
},
|
|
113
|
+
minor: {
|
|
114
|
+
color: token.color('terrain.contour.minor'),
|
|
115
|
+
width: token.number('terrain.contour.minor-width'),
|
|
116
|
+
},
|
|
117
|
+
index: {
|
|
118
|
+
color: token.color('terrain.contour.index'),
|
|
119
|
+
width: token.number('terrain.contour.index-width'),
|
|
120
|
+
},
|
|
121
|
+
labels: {
|
|
122
|
+
color: token.color('terrain.contour.label'),
|
|
123
|
+
haloColor: token.color('terrain.contour.halo'),
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Declare those `terrain.*` color and number roles with the same keys in every map theme. The
|
|
130
|
+
compiled source is ordinary MapLibre vector data with source layer `contours`; its tile URL
|
|
131
|
+
contains the safely encoded DEM template and complete generation parameters. Register the generic
|
|
132
|
+
protocol before MapLibre reads the style. It lazily initializes the pinned, locally bundled
|
|
133
|
+
`maplibre-contour@0.1.0` browser module on the first contour tile; no public CDN runtime is needed.
|
|
134
|
+
`overzoom` may not exceed the lowest threshold zoom, so generated DEM requests never use a negative
|
|
135
|
+
zoom. Every index interval must be a whole multiple of its minor interval. Contour labels emit the
|
|
136
|
+
scaled numeric elevation without assuming a display unit. Source and layer visibility cannot begin
|
|
137
|
+
before the first configured threshold zoom. To bound main-thread work, effective minor intervals
|
|
138
|
+
must be at least 250 units at z0–4, 100 at z5–7, 50 at z8–10, 20 at z11–12, and 10 from z13 onward.
|
|
139
|
+
Use a trusted, size-bounded DEM tile service; the protocol intentionally accepts author-supplied
|
|
140
|
+
HTTP(S) templates rather than proxying or republishing terrain data.
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
import {registerTileflowContourProtocol} from '@tileflow/core/browser';
|
|
144
|
+
|
|
145
|
+
registerTileflowContourProtocol({addProtocol: maplibregl.addProtocol});
|
|
146
|
+
```
|
|
147
|
+
|
|
68
148
|
## Authoring model
|
|
69
149
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
150
|
+
Tileflow exposes one authoring concept and one constructor: `defineMap()`. A complete map omits
|
|
151
|
+
`extends`; an inherited map sets `extends` to another imported map object. All ten first-party
|
|
152
|
+
maps are independent standalone maps. They use the sole semantic compiler while defining
|
|
153
|
+
their complete designs and asset providers directly; no official map imports or extends another
|
|
154
|
+
official map. Applications can extend any of those maps through the same public API. Streets
|
|
155
|
+
itself owns coordinated light and dark themes.
|
|
156
|
+
There is no public compiler selector or alternate compiler. The authoring contract has no recipe or
|
|
157
|
+
cartographic compatibility alias.
|
|
158
|
+
|
|
159
|
+
### Machine-readable V1 surface
|
|
160
|
+
|
|
161
|
+
`tileflowAuthoringManifest` is the deterministic, deeply frozen description of the public semantic
|
|
162
|
+
language. Its schema version is `tileflowAuthoringManifestSchemaVersion`. The manifest's `domains`
|
|
163
|
+
array is generated from the compiler's closed registry and records exact compile order, module and
|
|
164
|
+
service dependencies, and provided services. `operations` lists only integrated public operations:
|
|
165
|
+
defining/extending a map, directly replacing, refining, disabling, or resetting a keyed domain,
|
|
166
|
+
and adding owner-local render passes or target refinements. Direct declaration is the sole
|
|
167
|
+
replacement syntax; there is no redundant `set()` helper.
|
|
168
|
+
|
|
169
|
+
Agents and other tools can discover this contract without importing executable configuration:
|
|
170
|
+
`tileflow language manifest --json` emits the manifest and `tileflow language schema --json` emits
|
|
171
|
+
the packaged generated authoring/resolved JSON Schema. Both commands are deterministic, read-only,
|
|
172
|
+
and network-free. The manifest links every domain to its authoring, options, patch, and resolved
|
|
173
|
+
schema definitions, and enumerates the closed expression and render-selector vocabulary with its
|
|
174
|
+
finite limits.
|
|
175
|
+
|
|
176
|
+
The manifest also describes the exact structured compiler contract. A successful
|
|
177
|
+
`createStyleResult()` has `{ok: true, diagnostics, report, style}`; a failure has
|
|
178
|
+
`{ok: false, diagnostics, report}` and forbids `style`. Report schema version 1 requires
|
|
179
|
+
`domains`, `map`, `planner`, `schemaVersion`, and sorted semantic `targets`; `theme`, source
|
|
180
|
+
`requirements`, and physical `provenance` are optional. Pass `{inspection: true}` only when the
|
|
181
|
+
read-only physical-output provenance sidecar is needed. Its IDs and indexes are diagnostic
|
|
182
|
+
observations, never stable or addressable authoring targets.
|
|
183
|
+
|
|
184
|
+
`diffTileflowMaps(before, after)` resolves both inputs before comparing them and returns semantic
|
|
185
|
+
diff schema version 1:
|
|
186
|
+
|
|
187
|
+
```ts
|
|
188
|
+
{
|
|
189
|
+
schemaVersion: 1,
|
|
190
|
+
from: {id, version},
|
|
191
|
+
to: {id, version},
|
|
192
|
+
summary: {add, remove, change, total},
|
|
193
|
+
changes: [
|
|
194
|
+
{kind: 'add', path: '/projection', after: 'globe'},
|
|
195
|
+
{kind: 'remove', path: '/modules/water', before: {/* ... */}},
|
|
196
|
+
{kind: 'change', path: '/view/zoom', before: 12, after: 13},
|
|
197
|
+
],
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
Paths are RFC 6901 JSON Pointers. Object keys compare recursively in code-unit order; arrays compare
|
|
202
|
+
atomically because map inheritance replaces arrays. Leaf identity (`id`, `name`, `version`) and
|
|
203
|
+
delivery metadata identify the endpoints but are not cartographic changes.
|
|
204
|
+
|
|
205
|
+
Custom build tooling can opt into a separate, read-only diagnostic sidecar from the explicit build
|
|
206
|
+
entrypoint:
|
|
207
|
+
|
|
208
|
+
```ts
|
|
209
|
+
import {createStyleWithInspection} from '@tileflow/core/build';
|
|
210
|
+
|
|
211
|
+
const {style, inspection} = createStyleWithInspection(map);
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
`inspection.layers` stays aligned with the final Style layer order and records the semantic
|
|
215
|
+
`owner`, `slot`, `target`, and ordered render passes/refinements represented by each layer, including
|
|
216
|
+
layers merged by the physical planner. `createStyleFromCatalogWithInspection` and
|
|
217
|
+
`createStylesFromCatalogWithInspection` provide the corresponding catalog-oriented forms. The
|
|
218
|
+
ordinary Style bytes are identical to compilation without inspection; private compiler metadata is
|
|
219
|
+
stripped before finalization, and the sidecar never enters a Style, runtime manifest, or production
|
|
220
|
+
artifact unless a caller deliberately stores it.
|
|
221
|
+
|
|
222
|
+
Resolution happens before validation, asset preparation, compilation, capture, build, or deploy.
|
|
223
|
+
Only `view` deep-merges (its nested arrays and expressions still replace). The `themes` collection
|
|
224
|
+
replaces atomically when declared and clears an inherited `systemThemes` mapping; omission inherits
|
|
225
|
+
the collection. `defaultTheme` may independently choose one inherited concrete theme, while an
|
|
226
|
+
explicit `systemThemes` replaces the complete light/dark mapping. Every resolved selector must name
|
|
227
|
+
a theme in the final collection.
|
|
228
|
+
`modules` merges by domain name: an omitted domain remains inherited, while declaring `roads(...)`
|
|
229
|
+
or another domain replaces that inherited module request and every compiler-owned contribution
|
|
230
|
+
attached to it as a unit. `data`, `projection`, `terrain`, `marine`, `icons`, and the text provider
|
|
231
|
+
are atomic; identity and tooling metadata are leaf-owned. `disable()` removes the domain and
|
|
232
|
+
its complete inherited render stack. The resolved result is a standalone map with no runtime dependency on
|
|
233
|
+
TypeScript imports.
|
|
234
|
+
|
|
235
|
+
`icons` is an intentional exception to implicit array composition: omission inherits the exact
|
|
236
|
+
parent array, any declaration replaces it atomically, and `[]` disables map icons. Compose with a
|
|
237
|
+
spread when the child should keep a parent's directories. `fonts` and `glyphs` are mutually
|
|
238
|
+
exclusive text providers; declaring either atomically replaces an inherited provider of either
|
|
239
|
+
kind. Unknown keys and former compatibility shapes are rejected.
|
|
240
|
+
|
|
241
|
+
Most authors should extend an existing official or application map. A complete standalone map uses
|
|
242
|
+
the same `defineMap()` call without `extends`:
|
|
243
|
+
|
|
244
|
+
```ts
|
|
245
|
+
import {defineMap} from '@tileflow/core';
|
|
246
|
+
import {streetsIcons, streetsThemes} from '@tileflow/maps';
|
|
247
|
+
|
|
248
|
+
const companyBase = defineMap({
|
|
249
|
+
id: 'company-base',
|
|
250
|
+
name: 'Company base',
|
|
251
|
+
version: 1,
|
|
252
|
+
glyphs: {
|
|
253
|
+
kind: 'url',
|
|
254
|
+
url: 'https://api.tileflow.dev/fonts/{fontstack}/{range}.pbf',
|
|
255
|
+
fontStacks: ['Noto Sans Regular', 'Noto Sans Bold'],
|
|
256
|
+
},
|
|
257
|
+
icons: [streetsIcons],
|
|
258
|
+
themes: {light: streetsThemes.light, dark: streetsThemes.dark},
|
|
259
|
+
defaultTheme: 'light',
|
|
260
|
+
systemThemes: {light: 'light', dark: 'dark'},
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
export default defineMap({
|
|
264
|
+
id: 'company-navigation',
|
|
265
|
+
version: 1,
|
|
266
|
+
extends: companyBase,
|
|
267
|
+
projection: 'globe',
|
|
268
|
+
});
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
The standalone map above is complete as written. A map always owns its complete text provider; Core does not
|
|
272
|
+
obtain fonts or sprites from World and never invents a fallback URL. The URL-backed first-party
|
|
273
|
+
`streets`, `baedeker`, `ferraris`, `harad`, `soundings`, `verdant`, and `sanFrancisto` maps declare their glyph
|
|
274
|
+
providers directly, while `siegfried` declares packaged fonts, so ordinary imports and derived
|
|
275
|
+
maps compile without out-of-band release metadata.
|
|
73
276
|
|
|
74
277
|
```mermaid
|
|
75
278
|
flowchart LR
|
|
76
|
-
A["
|
|
77
|
-
B --> C["
|
|
78
|
-
C --> D["
|
|
79
|
-
D --> E["
|
|
279
|
+
A["tileflow.config.ts exports one map"] --> B["Resolve imported map lineage"]
|
|
280
|
+
B --> C["Complete semantic design + closed domain registry"]
|
|
281
|
+
C --> D["Domain IR + assembly + render stacks"]
|
|
282
|
+
D --> E["Physical planner"]
|
|
283
|
+
E --> F["Single MapLibre lowering"]
|
|
284
|
+
F --> G["Validated Style JSON + compilation report"]
|
|
80
285
|
```
|
|
81
286
|
|
|
82
287
|
Modules are keyed by domain, so object order never controls rendering and a domain can appear only
|
|
83
|
-
once.
|
|
84
|
-
deliberately. The public domains are `land`, `water`, `roads`, `buildings`, `boundaries`,
|
|
85
|
-
`poi`, `aeroways`, and `
|
|
288
|
+
once. A map extending Streets inherits its complete module set. Use `disable()` to remove a domain
|
|
289
|
+
deliberately. The public domains are `land`, `water`, `nautical`, `roads`, `buildings`, `boundaries`,
|
|
290
|
+
`labels`, `poi`, `aeroways`, `transit`, `vegetation`, `addresses`, and `landforms`.
|
|
291
|
+
The closed domain registry is exhaustive over that domain set and owns defaults, dependency order,
|
|
292
|
+
services, and compiler orchestration. Contract tests keep its keys and type tags in lockstep with
|
|
293
|
+
the strict resolved schema, generated authoring/options/patch/resolved schema aliases, and public
|
|
294
|
+
exports. Disabling `roads` also removes dependent road names, shields, and junction references;
|
|
295
|
+
independent place, water, aerodrome, and POI labels remain under the labels module.
|
|
296
|
+
|
|
297
|
+
Every styling module supports semantic shortcuts and exact semantic targets. Visual style values
|
|
298
|
+
accept typed token refs, documented `fixed(value, {reason})` values, closed data expressions through
|
|
299
|
+
`expr.*` plus `field(...)`, and zoom functions through `zoom.step(...)`, `zoom.linear(...)`, or
|
|
300
|
+
`zoom.exponential(...)`. Refs and fixed leaves also work inside typed expressions and zoom stops.
|
|
301
|
+
Structural controls such as presets, visibility, and zoom gates remain ordinary literals. Exact
|
|
302
|
+
controls address stable concepts such as `roads.classes.primary.surface.fill`, not renderer layer
|
|
303
|
+
IDs.
|
|
304
|
+
|
|
305
|
+
The `land` module exposes stable land-use targets for `cemetery`, `civic`, `commercial`,
|
|
306
|
+
`education`, `government`, `industrial`, `medical`, `military`, `parking`, `railway`,
|
|
307
|
+
`recreation`, and `residential`. Its land-cover taxonomy distinguishes physical cover from authored
|
|
308
|
+
urban green: `farmland`, `flowerbed`, `grass`, `ice`, `meadow`, `protected`,
|
|
309
|
+
`recreationGround`, `rock`, `sand`, `scrub`, `urbanPark`, `villageGreen`, `wetland`, and
|
|
310
|
+
`wood`. The plain `grass` branch excludes every typed grass subclass, so one source feature cannot
|
|
311
|
+
receive two opaque green fills. Parking areas are polygons from the configured land-use source;
|
|
312
|
+
parking access aisles remain road features under `roads.serviceTypes.parkingAisle`. The
|
|
313
|
+
`commercial` target recognizes ordinary `commercial` and `retail` values plus Tileflow's derived
|
|
314
|
+
`business_area` ground class. Its `globalLandcover` fill styles the optional low-zoom global
|
|
315
|
+
land-cover extension without a raw layer patch. The `water.bathymetry` fill does the same for the
|
|
316
|
+
typed Tileflow World V1 depth bands. An explicit `water({bathymetryContours: {}})` traces the
|
|
317
|
+
submerged edges between those discrete polygon bands; it is an approximate visual contour, not a
|
|
318
|
+
surveyed isoline. `water({bathymetryLabels: {}})` separately opts into numeric metre values derived
|
|
319
|
+
from each polygon's absolute minimum band depth. Existing maps gain neither detail by default, and
|
|
320
|
+
sources without bathymetry bindings omit both requested layers. These labels describe coarse band
|
|
321
|
+
floors, not measured survey soundings, and must not be presented as hydrographic sounding data.
|
|
322
|
+
|
|
323
|
+
`addresses` renders the standard OpenMapTiles `housenumber` points at detailed zooms and exposes a
|
|
324
|
+
single semantic `labels` style. `landforms` renders the standard `mountain_peak` classes (`peak`,
|
|
325
|
+
`volcano`, `saddle`, `ridge`, `cliff`, and `arete`) with rank-aware collision priority and optional
|
|
326
|
+
metric elevation. Both source-layer and field names remain remappable through `openMapTiles(...)`;
|
|
327
|
+
landform names share the language selected by `labels({language: ...})`.
|
|
328
|
+
|
|
329
|
+
`aeroways.runwayRef` owns high-zoom runway designators from the remappable OpenMapTiles `ref`
|
|
330
|
+
field. Aerodrome names stay under `labels.styles.aerodrome`; `labels.aerodromeCodes` controls whether
|
|
331
|
+
they append no code, IATA only, or IATA with an ICAO fallback (`'none' | 'iata' | 'all'`).
|
|
86
332
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
333
|
+
The `vegetation` module binds individual-tree points from the optional OpenMapTiles `tree`
|
|
334
|
+
extension. Its default `mode: '3d'` emits a portable circle fallback plus metadata for runtimes
|
|
335
|
+
that can upgrade the points to instanced 3D trees. `mode: 'flat'` keeps the MapLibre circles, and
|
|
336
|
+
`flat` exposes the complete `CircleStyle` fallback. `threeDimensional` controls bark color,
|
|
337
|
+
broadleaf and conifer palettes, and independent height and crown scales. The legacy `minZoom`
|
|
338
|
+
shortcut remains available; `flat.minZoom` takes precedence when both are present. The binding
|
|
339
|
+
includes height, crown diameter, genus, leaf type, and species fields so compatible runtimes can
|
|
340
|
+
preserve source measurements and botanical form without hard-coding raw property names. The
|
|
341
|
+
pitched-scene stack follows physical height: pedestrian and transport surfaces, transport markings
|
|
342
|
+
and road names, buildings, then vegetation. Place, water, aerodrome, and POI annotations remain
|
|
343
|
+
last so geographic names stay readable without making street paint or text float over 3D geometry.
|
|
91
344
|
|
|
92
345
|
## Shared visual primitives
|
|
93
346
|
|
|
@@ -99,7 +352,7 @@ having to learn different spellings for the same MapLibre behavior:
|
|
|
99
352
|
- `LineStyle`: color, width, opacity, dash, blur, gap, offset, pattern, caps, joins, and zoom range.
|
|
100
353
|
- `LineHatchStyle`: repeated diagonal detail with color, opacity, spacing, size, angle, and zoom
|
|
101
354
|
range for a road structure.
|
|
102
|
-
- `TextStyle`: field, font
|
|
355
|
+
- `TextStyle`: field, exact font face, exact fallback faces, size, color, halo, spacing metrics, transform,
|
|
103
356
|
collision policy, rotation, fixed/variable anchoring, line constraints, and zoom range.
|
|
104
357
|
- `IconStyle`: image, size, color, halo, rotation/alignment, collision policy, and zoom range.
|
|
105
358
|
- `CircleStyle`: radius, fill/stroke appearance, blur, opacity, pitch behavior, and zoom range.
|
|
@@ -110,6 +363,34 @@ Compounds express common cartographic structures: `AreaStyle` has `fill` and `ou
|
|
|
110
363
|
optional `text`, `icon`, and `marker`. Geographic selection remains owned by semantic modules;
|
|
111
364
|
ordinary visual styles intentionally do not accept raw source filters.
|
|
112
365
|
|
|
366
|
+
## Compiled-style performance
|
|
367
|
+
|
|
368
|
+
The semantic compiler resolves semantic modules and their owned contributions first, then compacts equivalent
|
|
369
|
+
physical MapLibre layers. At high-detail zooms, road classes become a small set of data-driven
|
|
370
|
+
cohorts; equivalent road labels and tunnel hatches share compatible buckets; and land-cover,
|
|
371
|
+
land-use, and waterway classes share compatible buckets. Compaction selects
|
|
372
|
+
cohorts from compiler-owned semantic targets, not by parsing physical IDs; its temporary
|
|
373
|
+
owner/slot/target provenance is stripped from the public Style JSON.
|
|
374
|
+
|
|
375
|
+
After physical planning, Core embeds a bounded private `tileflow:interaction-manifest` lookup for the
|
|
376
|
+
final POI representations. `@tileflow/interactions/maplibre` validates and consumes that metadata
|
|
377
|
+
so applications can bind to `domain: 'poi'` without depending on physical layer IDs. The lookup is
|
|
378
|
+
paired atomically with the exact style it describes; it is not a public style-authoring API.
|
|
379
|
+
|
|
380
|
+
Use the exported structural sweep to enforce budgets without loading tiles or a browser:
|
|
381
|
+
|
|
382
|
+
```ts
|
|
383
|
+
import {analyzeTileflowStylePerformance, createStyle} from '@tileflow/core';
|
|
384
|
+
import map from './tileflow.config';
|
|
385
|
+
|
|
386
|
+
const report = analyzeTileflowStylePerformance(createStyle(map));
|
|
387
|
+
console.log(report.zooms[16]);
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
The report covers z0–z22 by default and includes active layers, conservative bucket estimates,
|
|
391
|
+
symbols, and active layers per source-layer. It is intended for regression gates; browser traces
|
|
392
|
+
remain the source of truth for decode, upload, placement, and frame time.
|
|
393
|
+
|
|
113
394
|
A `SymbolStyle` root zoom range governs its text/icon layer and is inherited by its marker layer.
|
|
114
395
|
Because a marker is materialized as a separate circle layer, an explicit marker range may refine
|
|
115
396
|
that inherited default. Text and icon ranges must agree because MapLibre renders them together.
|
|
@@ -119,36 +400,109 @@ The road module distinguishes the path family semantically:
|
|
|
119
400
|
```ts
|
|
120
401
|
roads({
|
|
121
402
|
extras: {paths: true},
|
|
403
|
+
crossings: {image: token.image('roads.crosswalk')},
|
|
404
|
+
sidewalks: {
|
|
405
|
+
surface: {color: token.color('roads.city.casing'), minZoom: 17},
|
|
406
|
+
pattern: {
|
|
407
|
+
pattern: token.image('roads.sidewalkPattern'),
|
|
408
|
+
minZoom: 17,
|
|
409
|
+
opacity: fixed(0.6, {reason: 'Sidewalk texture strength is invariant'}),
|
|
410
|
+
},
|
|
411
|
+
},
|
|
412
|
+
roundabouts: {
|
|
413
|
+
casing: {strokeColor: token.color('roads.default.casing')},
|
|
414
|
+
fill: {strokeColor: token.color('roads.default')},
|
|
415
|
+
},
|
|
122
416
|
areas: {
|
|
123
417
|
pedestrian: {
|
|
124
|
-
fill: {color: '
|
|
125
|
-
outline: {
|
|
418
|
+
fill: {color: token.color('surface.land')},
|
|
419
|
+
outline: {
|
|
420
|
+
color: token.color('roads.default.casing'),
|
|
421
|
+
width: fixed(1, {reason: 'Pedestrian-area outline weight is invariant'}),
|
|
422
|
+
},
|
|
126
423
|
},
|
|
127
424
|
},
|
|
128
425
|
classes: {
|
|
129
426
|
primary: {
|
|
130
427
|
tunnel: {
|
|
131
|
-
casing: {
|
|
132
|
-
|
|
133
|
-
|
|
428
|
+
casing: {
|
|
429
|
+
color: token.color('roads.city.tunnelCasing'),
|
|
430
|
+
width: fixed(10, {reason: 'Primary-tunnel hierarchy is invariant'}),
|
|
431
|
+
},
|
|
432
|
+
fill: {
|
|
433
|
+
color: token.color('roads.city.tunnel'),
|
|
434
|
+
width: fixed(8, {reason: 'Primary-tunnel hierarchy is invariant'}),
|
|
435
|
+
},
|
|
436
|
+
hatch: {
|
|
437
|
+
color: token.color('roads.city.tunnelCasing'),
|
|
438
|
+
opacity: fixed(0.25, {reason: 'Tunnel hatch strength is invariant'}),
|
|
439
|
+
spacing: fixed(10, {reason: 'Tunnel hatch rhythm is invariant'}),
|
|
440
|
+
},
|
|
441
|
+
},
|
|
442
|
+
},
|
|
443
|
+
pedestrian: {
|
|
444
|
+
surface: {
|
|
445
|
+
fill: {
|
|
446
|
+
color: token.color('roads.path.transition'),
|
|
447
|
+
width: fixed(6, {reason: 'Pedestrian-road hierarchy is invariant'}),
|
|
448
|
+
},
|
|
449
|
+
},
|
|
450
|
+
},
|
|
451
|
+
footway: {
|
|
452
|
+
surface: {
|
|
453
|
+
fill: {
|
|
454
|
+
color: token.color('roads.path'),
|
|
455
|
+
width: fixed(1.2, {reason: 'Path hierarchy is invariant'}),
|
|
456
|
+
},
|
|
457
|
+
},
|
|
458
|
+
},
|
|
459
|
+
cycleway: {
|
|
460
|
+
surface: {
|
|
461
|
+
fill: {
|
|
462
|
+
color: token.color('roads.cycleway'),
|
|
463
|
+
width: fixed(1.5, {reason: 'Cycleway hierarchy is invariant'}),
|
|
464
|
+
},
|
|
465
|
+
},
|
|
466
|
+
},
|
|
467
|
+
steps: {
|
|
468
|
+
surface: {
|
|
469
|
+
fill: {
|
|
470
|
+
color: token.color('roads.path.casing'),
|
|
471
|
+
width: fixed(1.4, {reason: 'Step hierarchy is invariant'}),
|
|
472
|
+
},
|
|
473
|
+
},
|
|
474
|
+
},
|
|
475
|
+
pathway: {
|
|
476
|
+
surface: {
|
|
477
|
+
fill: {
|
|
478
|
+
color: token.color('roads.path'),
|
|
479
|
+
width: fixed(1.2, {reason: 'Path hierarchy is invariant'}),
|
|
480
|
+
},
|
|
134
481
|
},
|
|
135
482
|
},
|
|
136
|
-
pedestrian: {surface: {fill: {color: '#F5F6F7', width: 6}}},
|
|
137
|
-
footway: {surface: {fill: {color: '#D9DEE3', width: 1.2}}},
|
|
138
|
-
cycleway: {surface: {fill: {color: '#9ED8C5', width: 1.5}}},
|
|
139
|
-
steps: {surface: {fill: {color: '#C7CED6', dash: [1, 1], width: 1.4}}},
|
|
140
|
-
pathway: {surface: {fill: {color: '#E8ECEF', width: 1.2}}},
|
|
141
483
|
},
|
|
142
484
|
modifiers: {
|
|
143
485
|
expressway: {widthScale: 1.06},
|
|
144
486
|
ramp: {widthScale: 0.7},
|
|
145
|
-
unpaved: {surface: {fill: {color: '
|
|
146
|
-
construction: {
|
|
147
|
-
|
|
487
|
+
unpaved: {surface: {fill: {color: token.color('roads.path')}}},
|
|
488
|
+
construction: {
|
|
489
|
+
surface: {
|
|
490
|
+
fill: {opacity: fixed(0.7, {reason: 'Construction emphasis is invariant'})},
|
|
491
|
+
},
|
|
492
|
+
},
|
|
493
|
+
indoor: {
|
|
494
|
+
surface: {fill: {opacity: fixed(0.4, {reason: 'Indoor-road emphasis is invariant'})}},
|
|
495
|
+
},
|
|
148
496
|
},
|
|
149
497
|
restrictions: {
|
|
150
|
-
access: {
|
|
151
|
-
|
|
498
|
+
access: {
|
|
499
|
+
surface: {fill: {opacity: fixed(0.55, {reason: 'Restricted-road emphasis is invariant'})}},
|
|
500
|
+
},
|
|
501
|
+
toll: {
|
|
502
|
+
surface: {
|
|
503
|
+
casing: {color: fixed('#C5B7D8', {reason: 'Toll ink is regulatory and invariant'})},
|
|
504
|
+
},
|
|
505
|
+
},
|
|
152
506
|
},
|
|
153
507
|
serviceTypes: {
|
|
154
508
|
driveway: {widthScale: 0.75},
|
|
@@ -160,9 +514,12 @@ roads({
|
|
|
160
514
|
These targets are non-overlapping translations of the OpenMapTiles road class and subclass. The
|
|
161
515
|
same names are available under `labels().roadClasses` and `labels().styles.roads`. Surface, tunnel,
|
|
162
516
|
and bridge phases can be controlled independently for each target; each structure has
|
|
163
|
-
`shadow`/`casing`/`fill` and an optional `hatch`.
|
|
164
|
-
`size` is omitted and never participate in label collision.
|
|
165
|
-
|
|
517
|
+
`shadow`/`casing`/`fill` and an optional `hatch`. Glyph hatch marks inherit the resolved fill width
|
|
518
|
+
when `size` is omitted and never participate in label collision. Setting `hatch.pattern` instead
|
|
519
|
+
emits a repeated sprite texture clipped to the resolved fill width. No raw source filter or generated
|
|
520
|
+
layer ID is needed. `hatch.patternWidths` can list intrinsic sprite heights named
|
|
521
|
+
`${pattern}-${width}`; the compiler selects the nearest height from the resolved road width so the
|
|
522
|
+
texture's marks remain approximately constant in screen pixels across classes, ramps, and zooms.
|
|
166
523
|
|
|
167
524
|
`classes.pedestrian` styles line-like pedestrian ways. Polygon pedestrian plazas are a distinct
|
|
168
525
|
geometry and use `areas.pedestrian`, including optional fill opacity, outline color, or sprite
|
|
@@ -182,93 +539,532 @@ Road names, shields, and motorway junctions remain label concerns:
|
|
|
182
539
|
```ts
|
|
183
540
|
labels({
|
|
184
541
|
roads: 'all',
|
|
185
|
-
shields: '
|
|
542
|
+
shields: 'major',
|
|
186
543
|
junctions: true,
|
|
187
544
|
styles: {
|
|
188
545
|
shields: {
|
|
189
|
-
default: {
|
|
190
|
-
|
|
191
|
-
|
|
546
|
+
default: {
|
|
547
|
+
icon: {
|
|
548
|
+
image: token.image('roads.shield.rectangleNeutral'),
|
|
549
|
+
optional: false,
|
|
550
|
+
pitchAlignment: 'viewport',
|
|
551
|
+
rotationAlignment: 'viewport',
|
|
552
|
+
textFit: 'width',
|
|
553
|
+
textFitPadding: [0, 4, 0, 4],
|
|
554
|
+
},
|
|
555
|
+
text: {
|
|
556
|
+
color: token.color('labels.shieldDark'),
|
|
557
|
+
font: token.font('places'),
|
|
558
|
+
optional: false,
|
|
559
|
+
pitchAlignment: 'viewport',
|
|
560
|
+
rotationAlignment: 'viewport',
|
|
561
|
+
},
|
|
562
|
+
},
|
|
563
|
+
overview: {minZoom: 6, maxZoom: 11, placement: 'point'},
|
|
564
|
+
detail: {minZoom: 11, placement: 'line', spacing: 400},
|
|
565
|
+
kinds: {
|
|
566
|
+
'rectangle-neutral': {image: token.image('roads.shield.rectangleNeutral')},
|
|
567
|
+
'rectangle-blue': {image: token.image('roads.shield.rectangleBlue')},
|
|
568
|
+
},
|
|
569
|
+
textColors: {
|
|
570
|
+
dark: {color: token.color('labels.shieldDark')},
|
|
571
|
+
light: {color: token.color('labels.shieldLight')},
|
|
572
|
+
},
|
|
573
|
+
},
|
|
574
|
+
junctions: {
|
|
575
|
+
text: {
|
|
576
|
+
color: token.color('labels.strong'),
|
|
577
|
+
haloColor: token.color('labels.halo'),
|
|
578
|
+
haloWidth: fixed(2, {reason: 'Junction halo weight is invariant'}),
|
|
192
579
|
},
|
|
193
580
|
},
|
|
194
|
-
junctions: {text: {color: '#405264', haloColor: '#fff', haloWidth: 2}},
|
|
195
581
|
},
|
|
196
582
|
});
|
|
197
583
|
```
|
|
198
584
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
585
|
+
`icon.textFit` and `icon.textFitPadding` let a symbol background grow with its text. Setting both
|
|
586
|
+
icon and text `optional: false` keeps a route shield atomic during collision placement: neither an
|
|
587
|
+
empty badge nor a detached reference can render alone. `overview` renders producer-selected points
|
|
588
|
+
at low zoom and `detail` switches to line placement at street zooms; exclusive `maxZoom: 11` and
|
|
589
|
+
inclusive `minZoom: 11` make that handoff atomic. Keeping both icon and text aligned to `viewport`
|
|
590
|
+
makes the badge horizontal regardless of road bearing.
|
|
591
|
+
|
|
592
|
+
`kinds` and `textColors` are closed presentation tables. The vector producer supplies semantic
|
|
593
|
+
`shield_kind`, `shield_text_color`, `shield_text`, and `shield_rank` values; Core only matches them
|
|
594
|
+
against the authored tables and falls back to `default`. Country, route-prefix, and network-shape
|
|
595
|
+
rules therefore stay in the data authority instead of leaking into a map theme or multiplying
|
|
596
|
+
runtime layers.
|
|
597
|
+
|
|
598
|
+
Tileflow World owns POI classification, editorial zoom eligibility, and cross-source ranking. Its
|
|
599
|
+
canonical `poi` layer exposes `category`, `type`, `icon`, `min_zoom`, `filter_rank`, and `size_rank`;
|
|
600
|
+
Core validates that complete contract and never reconstructs those decisions from OpenMapTiles
|
|
601
|
+
`class`, `subclass`, or `rank`. `filter_rank` is an
|
|
602
|
+
integer from 0 through 5, with lower values reserved for the strongest candidates. The numeric
|
|
603
|
+
`density` option is an inclusive threshold from 1 through 5 and defaults to 3. `size_rank` is an
|
|
604
|
+
integer from 0 through 16 used to order eligible candidates before MapLibre performs final
|
|
605
|
+
collision placement.
|
|
208
606
|
|
|
209
|
-
|
|
210
|
-
|
|
607
|
+
The closed category vocabulary is `arts-entertainment`, `education`, `food-drink`, `landmark`,
|
|
608
|
+
`lodging`, `medical`, `park-nature`, `public-services`, `religion`, `retail`, `sport-leisure`,
|
|
609
|
+
`transport`, and `visitor-amenity`. Producer `type` and `icon` values use stable snake_case names.
|
|
610
|
+
When an `icon` sprite exists it is used directly; an unknown or absent icon safely falls back to the
|
|
611
|
+
category's themed image role. `labels` and `icons` are booleans. Category styles control only
|
|
612
|
+
presentation and may hide or delay candidates, never promote them above the producer's selection.
|
|
613
|
+
`placement.coupleIconAndLabel` keeps a POI's icon and label atomic during collision placement.
|
|
211
614
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
615
|
+
```ts
|
|
616
|
+
poi({
|
|
617
|
+
categories: ['food-drink', 'landmark', 'transport'],
|
|
618
|
+
density: 3,
|
|
619
|
+
icons: true,
|
|
620
|
+
labels: true,
|
|
621
|
+
});
|
|
622
|
+
```
|
|
216
623
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
624
|
+
The same contract drives regular POI layers, custom HUD treatments, semantic interactions, and
|
|
625
|
+
feature inspection. There is intentionally no compatibility path for the former class mapping,
|
|
626
|
+
rank ceilings, or named density/detail presets.
|
|
627
|
+
|
|
628
|
+
## Themes and module styles
|
|
629
|
+
|
|
630
|
+
A map declares complete named appearances in `themes`, chooses one deterministic
|
|
631
|
+
`defaultTheme`, and may map the browser's light/dark preference through `systemThemes`. `system` is
|
|
632
|
+
never a compiled theme name: it is a runtime request that resolves immediately to one concrete
|
|
633
|
+
name. Disk builds write `styles/<map>/<theme>.json` beneath their output root. Public and
|
|
634
|
+
content-addressed Style URLs belong to the runtime manifest and may include a deployment prefix;
|
|
635
|
+
capture scenes and receipts store the concrete name rather than `system`.
|
|
636
|
+
|
|
637
|
+
Themes are flat, JSON-safe documents with `colorScheme`, identity, typography, lighting, and four
|
|
638
|
+
typed token catalogs: `color`, `font`, `image`, and `number`. Every theme on one map must expose the
|
|
639
|
+
same token keys, so changing a theme can never change map structure or silently lose a semantic
|
|
640
|
+
role. `defineTheme(base, definition)` is an authoring convenience that returns a fully materialized
|
|
641
|
+
document; it does not leave an inheritance edge in the resolved config.
|
|
642
|
+
|
|
643
|
+
The key in the `themes` record is its concrete runtime selector and output-path segment. The
|
|
644
|
+
theme's own `id` and `version` are its editorial identity and build provenance, so they need not
|
|
645
|
+
equal that key. Concrete keys may be names such as `day` and `night`; every `systemThemes.light` or
|
|
646
|
+
`systemThemes.dark` value must name an existing theme whose `colorScheme` matches that branch.
|
|
647
|
+
|
|
648
|
+
```ts
|
|
649
|
+
import {color, defineMap, defineTheme, fixed, roads, token} from '@tileflow/core';
|
|
650
|
+
import {streets, streetsThemes} from '@tileflow/maps';
|
|
651
|
+
|
|
652
|
+
const dark = defineTheme(streetsThemes.dark, {
|
|
653
|
+
id: 'company-dark',
|
|
654
|
+
version: 1,
|
|
655
|
+
colorScheme: 'dark',
|
|
656
|
+
tokens: {
|
|
657
|
+
color: {
|
|
658
|
+
'surface.land': '#101722',
|
|
659
|
+
'labels.primary': color.mix('#eef3fb', token.color('surface.background'), {amount: 0.08}),
|
|
660
|
+
},
|
|
661
|
+
},
|
|
662
|
+
});
|
|
663
|
+
|
|
664
|
+
export default defineMap({
|
|
665
|
+
id: 'company',
|
|
666
|
+
version: 1,
|
|
667
|
+
extends: streets,
|
|
668
|
+
themes: {light: streetsThemes.light, dark},
|
|
669
|
+
defaultTheme: 'light',
|
|
670
|
+
systemThemes: {light: 'light', dark: 'dark'},
|
|
671
|
+
modules: {
|
|
672
|
+
roads: roads({
|
|
673
|
+
classes: {
|
|
674
|
+
primary: {
|
|
675
|
+
surface: {
|
|
676
|
+
fill: {
|
|
677
|
+
color: fixed('#ff2d78', {reason: 'Regulatory primary-road ink is invariant'}),
|
|
678
|
+
},
|
|
679
|
+
},
|
|
680
|
+
},
|
|
681
|
+
secondary: {
|
|
682
|
+
surface: {
|
|
683
|
+
fill: {
|
|
684
|
+
color: token.color('roads.secondary'),
|
|
685
|
+
},
|
|
686
|
+
},
|
|
687
|
+
},
|
|
688
|
+
},
|
|
689
|
+
}),
|
|
690
|
+
},
|
|
691
|
+
});
|
|
692
|
+
```
|
|
693
|
+
|
|
694
|
+
Semantic modules own structure and behavior; visual fields accept typed token references.
|
|
695
|
+
Use a visual literal only inside a theme. If a module value intentionally must not vary, wrap it in
|
|
696
|
+
`fixed(value, {reason})`. `tileflow inspect --json` reports stable `THEME_IMPLICIT_FIXED`
|
|
697
|
+
diagnostics: implicit color, font, and image literals block Style compilation, while direct visual
|
|
698
|
+
number literals are warnings so an agent can document their invariance deliberately. Structural
|
|
699
|
+
numbers such as zoom bounds and ranks are not visual-theme diagnostics. `color.alpha()` and
|
|
700
|
+
deterministic OKLCH `color.mix()` keep derived palette logic inspectable. Unknown refs, cycles,
|
|
701
|
+
cross-category refs, token-schema drift, and unresolved visual nodes fail before Style JSON is
|
|
702
|
+
emitted.
|
|
703
|
+
|
|
704
|
+
There is no physical-layer override for the Tileflow basemap or separate official-map authoring
|
|
705
|
+
surface. Basemap behavior belongs to a typed control or owner-local render stack. Every
|
|
706
|
+
official and application map therefore follows the same schema, inheritance rules, diagnostics,
|
|
707
|
+
and lowering path. `createStyle()` validates the final planned Style JSON with the MapLibre style
|
|
708
|
+
spec and never returns an invalid style.
|
|
220
709
|
|
|
221
710
|
## Data is separate from design
|
|
222
711
|
|
|
223
|
-
|
|
712
|
+
Team data uses named logical sources and validated MapLibre overlays. The hosted identity never
|
|
713
|
+
contains a CDN URL; `local` is the explicit account-free PMTiles resolution for Dev and Capture:
|
|
224
714
|
|
|
225
715
|
```ts
|
|
226
|
-
{
|
|
227
|
-
|
|
228
|
-
|
|
716
|
+
import {defineMap, hostedTileset, maplibreOverlay} from '@tileflow/core';
|
|
717
|
+
import {streets} from '@tileflow/maps';
|
|
718
|
+
|
|
719
|
+
const stores = hostedTileset({
|
|
720
|
+
tileset: 'stores',
|
|
721
|
+
local: './data/stores.pmtiles',
|
|
722
|
+
attribution: 'Store data © Example',
|
|
723
|
+
});
|
|
724
|
+
|
|
725
|
+
export default defineMap({
|
|
726
|
+
id: 'store-locator',
|
|
727
|
+
version: 1,
|
|
728
|
+
extends: streets,
|
|
729
|
+
sources: {stores},
|
|
730
|
+
overlays: {
|
|
731
|
+
stores: maplibreOverlay({
|
|
732
|
+
source: 'stores',
|
|
733
|
+
placement: 'above-roads',
|
|
734
|
+
layers: [
|
|
735
|
+
{
|
|
736
|
+
id: 'stores-points',
|
|
737
|
+
type: 'circle',
|
|
738
|
+
'source-layer': 'store_locations',
|
|
739
|
+
paint: {
|
|
740
|
+
'circle-color': ['match', ['get', 'category'], 'restaurant', '#ef4444', '#64748b'],
|
|
741
|
+
},
|
|
742
|
+
},
|
|
743
|
+
],
|
|
744
|
+
}),
|
|
745
|
+
},
|
|
746
|
+
});
|
|
747
|
+
```
|
|
748
|
+
|
|
749
|
+
Tileflow automatically registers only protocols in namespaces that Tileflow owns.
|
|
750
|
+
`tileflow-pmtiles://` is reserved for PMTiles resources managed by Tileflow. Tileflow never
|
|
751
|
+
automatically registers, removes, or replaces the global `pmtiles://` handler. Explicit PMTiles
|
|
752
|
+
sources supplied by an application remain the application's responsibility.
|
|
753
|
+
|
|
754
|
+
For local resolution, the Style URL is based on the logical tileset ID and remains stable when Dev
|
|
755
|
+
creates a new immutable snapshot. Physical generation IDs and local content hashes never enter the
|
|
756
|
+
Style representation. A new Dev request or Capture operation resolves current and retains that
|
|
757
|
+
snapshot for its duration; an operation already in progress keeps its acquired generation.
|
|
758
|
+
|
|
759
|
+
`local` does not make build output own or publish the archive. Production builds reject unresolved
|
|
760
|
+
local PMTiles. Use explicit managed publication or an application-owned production source.
|
|
761
|
+
|
|
762
|
+
One overlay owns one ordered group of ordinary MapLibre Style Layers. Public layer IDs are retained
|
|
763
|
+
for `map.on(...)`, feature queries, and application state. Placement is one of `above-water`,
|
|
764
|
+
`below-roads`, `above-roads`, `above-buildings`, `below-labels`, or `above-labels`; overlays sharing
|
|
765
|
+
a placement sort by overlay ID. A map accepts up to 16 Team sources in addition to Tileflow World
|
|
766
|
+
and terrain. These semantic boundaries do not expose generated basemap layer IDs.
|
|
767
|
+
`sources` and `overlays` merge by key through `extends`, declarations replace atomically, and
|
|
768
|
+
`remove()` deletes one inherited key. Removing a still-used source fails validation. There is no
|
|
769
|
+
interaction DSL or parallel expression language.
|
|
770
|
+
|
|
771
|
+
Omitting `data` selects the compiler-owned Tileflow World `v1` compatibility generation:
|
|
772
|
+
|
|
773
|
+
```ts
|
|
774
|
+
import {streets} from '@tileflow/maps';
|
|
775
|
+
|
|
776
|
+
export default streets;
|
|
777
|
+
```
|
|
778
|
+
|
|
779
|
+
Theme typography can set `fallbacks`, `letterSpacing`, and `transform` globally or per label
|
|
780
|
+
domain, while the font token catalog gives module styles a stable semantic target. Text delivery is
|
|
781
|
+
explicit and atomic: a map may declare either ordered `fonts` directories
|
|
782
|
+
for browser font files or a `glyphs` provider for PBF glyphs, never both. Omitting both inherits the
|
|
783
|
+
parent's provider; declaring either replaces an inherited provider of either kind. After resolution,
|
|
784
|
+
a map that emits text must have exactly one provider. A text-free standalone map may omit both. On a derived
|
|
785
|
+
map, omission inherits the parent provider while `fonts: []` explicitly removes it; that empty array
|
|
786
|
+
is valid only when the resolved map emits no text.
|
|
787
|
+
|
|
788
|
+
```ts
|
|
789
|
+
import {defineMap, defineTheme} from '@tileflow/core';
|
|
790
|
+
import {streets, streetsThemes} from '@tileflow/maps';
|
|
791
|
+
|
|
792
|
+
const brandLight = defineTheme(streetsThemes.light, {
|
|
793
|
+
id: 'brand-light',
|
|
794
|
+
version: 1,
|
|
795
|
+
colorScheme: 'light',
|
|
796
|
+
tokens: {font: {default: 'Brand Sans Regular', places: 'Brand Sans Regular'}},
|
|
797
|
+
});
|
|
798
|
+
const brandDark = defineTheme(streetsThemes.dark, {
|
|
799
|
+
id: 'brand-dark',
|
|
800
|
+
version: 1,
|
|
801
|
+
colorScheme: 'dark',
|
|
802
|
+
tokens: {font: {default: 'Brand Sans Regular', places: 'Brand Sans Regular'}},
|
|
803
|
+
});
|
|
804
|
+
|
|
805
|
+
export default defineMap({
|
|
806
|
+
id: 'brand-map',
|
|
807
|
+
version: 1,
|
|
808
|
+
extends: streets,
|
|
809
|
+
fonts: ['./fonts'],
|
|
810
|
+
themes: {light: brandLight, dark: brandDark},
|
|
811
|
+
defaultTheme: 'light',
|
|
812
|
+
systemThemes: {light: 'light', dark: 'dark'},
|
|
813
|
+
});
|
|
229
814
|
```
|
|
230
815
|
|
|
231
|
-
|
|
816
|
+
Node preparation reads TTF, OTF, and WOFF2 files, uses each OpenType full name as its exact
|
|
817
|
+
`text-font` ID, and requires `LICENSE.txt` in every contributing directory. Directories apply left
|
|
818
|
+
to right; a later exact name replaces an earlier face and case-only collisions fail. Only primary
|
|
819
|
+
faces used by the final style become content-addressed assets and strict `tileflow:fontFaces`
|
|
820
|
+
metadata. `font` names an exact OpenType full name or glyph face; local `fallbacks` name exact faces
|
|
821
|
+
or explicit CSS generic families. Tileflow never appends a weight suffix or derives one face from
|
|
822
|
+
another. Browser adapters load local faces before MapLibre. Native releases use a locked PBF
|
|
823
|
+
`glyphs` URL provider instead, which enumerates the exact comma-joined MapLibre request keys in
|
|
824
|
+
`fontStacks`.
|
|
825
|
+
|
|
826
|
+
Streets, Ferraris, Härad, Soundings, Verdant, and San Francisto each declare
|
|
827
|
+
`https://api.tileflow.dev/fonts/{fontstack}/{range}.pbf` with the exact `Noto Sans Regular` and
|
|
828
|
+
`Noto Sans Bold` stacks. That compatibility URL is canonical but not content-addressed; responses revalidate and
|
|
829
|
+
do not make a resolved map byte-reproducible. Exact official glyph identity belongs to the
|
|
830
|
+
separately published `/base/<assetSetSha256>/glyphs/...` global base-asset contract. In that URL,
|
|
831
|
+
`assetSetSha256` identifies the standalone glyph collection; it is not the same-domain value as the
|
|
832
|
+
per-map `assetSetSha256` in `build-manifest.json`.
|
|
833
|
+
Cyberpunk and Matrix each replace the URL provider with their own packaged Oxanium directory and
|
|
834
|
+
reference the exact local faces `Oxanium Medium` and `Oxanium SemiBold`. Baedeker and Siegfried
|
|
835
|
+
each own a packaged directory containing Cormorant Garamond Regular, SemiBold, and Italic.
|
|
836
|
+
|
|
837
|
+
Name the official generation deliberately, or use another OpenMapTiles-compatible vector source:
|
|
232
838
|
|
|
233
839
|
```ts
|
|
234
840
|
import {openMapTiles, tileflowWorld, vectorTiles} from '@tileflow/core';
|
|
235
841
|
|
|
236
|
-
const official = tileflowWorld(
|
|
842
|
+
const official = tileflowWorld();
|
|
237
843
|
const external = vectorTiles({
|
|
238
|
-
|
|
844
|
+
tiles: ['pmtiles://./test/fixtures/world.pmtiles'],
|
|
845
|
+
revision: 'fixture-1',
|
|
239
846
|
attribution: '© Example © OpenStreetMap contributors',
|
|
240
847
|
schema: openMapTiles(),
|
|
241
848
|
});
|
|
242
849
|
```
|
|
243
850
|
|
|
244
|
-
The
|
|
245
|
-
|
|
851
|
+
The resolved map controls how data is drawn; `data` controls where compatible features come from.
|
|
852
|
+
For Tileflow World, the compiler emits the selector TileJSON URL
|
|
853
|
+
`https://api.tileflow.dev/tiles/world/tiles.json`. Omitted data or `tileflowWorld()` selects
|
|
854
|
+
`world-v1/current`; `tileflowWorld({release: {releaseId, descriptorSha256}})` binds an exact release
|
|
855
|
+
through query parameters. Runtime and capture resolve `current` once per session or job and then
|
|
856
|
+
use the immutable release described by that response. `openMapTiles({layers, fields})` can bind renamed
|
|
246
857
|
source-layers or properties while preserving the versioned semantic contract. External browser
|
|
247
858
|
credentials do not belong in public Style JSON; supply them through the framework's
|
|
248
859
|
`transformRequest` integration.
|
|
249
860
|
|
|
861
|
+
External exact-test sources may use one TileJSON `url` or a bounded direct `tiles` list with optional
|
|
862
|
+
`bounds`, `minzoom`, and `maxzoom`. `pmtiles://` is supported for a checked-in or bring-your-own
|
|
863
|
+
archive. Public vector URLs are HTTPS, root-relative, or HTTP only on loopback development hosts.
|
|
864
|
+
A PMTiles URL names one `.pmtiles` archive through an HTTPS, loopback HTTP, root-relative, or safe
|
|
865
|
+
repository-relative target; credentials, fragments, traversal, and other protocols are rejected at
|
|
866
|
+
both authoring validation and compilation. An external `revision` participates in capture identity,
|
|
867
|
+
so changing fixture bytes cannot silently reuse a baseline.
|
|
868
|
+
|
|
250
869
|
Tileflow's OpenMapTiles contract also recognizes the optional `globallandcover` extension. The
|
|
251
|
-
|
|
252
|
-
classes beneath the OSM land layers
|
|
870
|
+
official Streets map maps its `barren`, `crop`, `grass`, `shrub`, `snow`, `trees`, and
|
|
871
|
+
`urban` classes beneath the OSM land layers. Tileflow World V1 supplies native generalized
|
|
872
|
+
geometry through z10; Streets fades it progressively while detailed `landcover`/`landuse` gains
|
|
873
|
+
opacity and removes it at z11. This is a macro bridge, not local semantic detail. Sources with a
|
|
253
874
|
different layer name can use `openMapTiles({layers: {globalLandcover: 'my_landcover'}})`; archives
|
|
254
|
-
without the extension remain compatible and simply render no features for that style layer.
|
|
875
|
+
without the extension remain compatible and simply render no features for that style layer. Use
|
|
876
|
+
`land({globalLandcover: {...}})` to customize its fill, opacity, visibility, and zoom range. `crop`
|
|
877
|
+
shares the theme's semantic `landcover.farmland` color with detailed farmland, while `barren` uses
|
|
878
|
+
`landcover.rock`; neither class borrows a road color.
|
|
879
|
+
|
|
880
|
+
The normalized schema also records the meaning of the OpenMapTiles `park` layer. Generic
|
|
881
|
+
`openMapTiles()` defaults to `semantics.parkLayer: 'mixed'` and compiles a private compatibility
|
|
882
|
+
branch for ordinary legacy parks. `tileflowWorldV1Schema()` fixes it to `'protected-only'`: that
|
|
883
|
+
layer is a protection tint, while urban parks and gardens come from
|
|
884
|
+
`landcover.class=grass/subclass=park|garden`. The ambiguous legacy `park` class is therefore not
|
|
885
|
+
a public land-cover target.
|
|
886
|
+
|
|
887
|
+
Tileflow World V1 makes `bathymetry`, `globallandcover`, `circular_feature`, `sidewalk`, and
|
|
888
|
+
`street_furniture` mandatory. Use `tileflowWorldV1Schema()` for that product instead of making raw
|
|
889
|
+
layer names part of a style. Publication tooling calls `validateTileflowWorldV1Tilejson(...)` and
|
|
890
|
+
fails closed unless each layer occurs exactly once, declares its contracted native zooms, and
|
|
891
|
+
advertises the typed fields consumed by the maps. This includes bathymetry z0–z9, global land cover
|
|
892
|
+
z0–z10, the three detailed-city layers at native z15, and numeric
|
|
893
|
+
`transportation.clearance_extra_px_z15` for butt-capping procedural-roundabout approaches. Generic
|
|
894
|
+
OpenMapTiles sources keep the detailed extensions optional and the compiler omits unsupported
|
|
895
|
+
detail. Resolving omitted data or
|
|
896
|
+
`tileflowWorld(...)` uses the strict V1 schema directly, and `water({bathymetry: {...}})` styles the
|
|
897
|
+
emitted depth bands without raw layer IDs. Use `water({bathymetryContours: {...}})` to customize the
|
|
898
|
+
opt-in companion line layer; an empty style selects subtle defaults, and the zero-depth band is
|
|
899
|
+
excluded so the ordinary coastline remains authoritative. The lines follow discrete band polygon
|
|
900
|
+
edges and are therefore approximate rather than surveyed depth contours. Use
|
|
901
|
+
`water({bathymetryLabels: {...}})` to customize the separate opt-in symbol layer; an empty style
|
|
902
|
+
selects the defaults, whose text is the absolute band-minimum number in metres with no unit suffix.
|
|
903
|
+
It remains a band-floor annotation, not a survey sounding.
|
|
904
|
+
|
|
905
|
+
For independent ocean products, `marine` composes stable auxiliary vector sources without changing
|
|
906
|
+
the primary World source:
|
|
907
|
+
|
|
908
|
+
```ts
|
|
909
|
+
import {bathymetry, defineMap, fixed, nautical} from '@tileflow/core';
|
|
910
|
+
import {streets} from '@tileflow/maps';
|
|
911
|
+
|
|
912
|
+
export default defineMap({
|
|
913
|
+
id: 'chart',
|
|
914
|
+
version: 1,
|
|
915
|
+
extends: streets,
|
|
916
|
+
marine: 'chart', // 'none' | 'bathymetry' | 'nautical' | 'chart'
|
|
917
|
+
modules: {
|
|
918
|
+
nautical: nautical({
|
|
919
|
+
soundings: {minZoom: 13},
|
|
920
|
+
aids: {minZoom: 11},
|
|
921
|
+
lights: {minZoom: 12},
|
|
922
|
+
hazards: {minZoom: 12},
|
|
923
|
+
hazardAreas: {
|
|
924
|
+
fill: {opacity: fixed(0.1, {reason: 'Chart hazard wash is invariant'})},
|
|
925
|
+
},
|
|
926
|
+
wrecks: {minZoom: 12},
|
|
927
|
+
wreckAreas: {
|
|
928
|
+
outline: {opacity: fixed(0.6, {reason: 'Chart wreck outline is invariant'})},
|
|
929
|
+
},
|
|
930
|
+
reefs: {fill: {opacity: fixed(0.3, {reason: 'Chart reef wash is invariant'})}},
|
|
931
|
+
navigationAreas: {
|
|
932
|
+
fill: {opacity: fixed(0.05, {reason: 'Chart navigation-area wash is invariant'})},
|
|
933
|
+
},
|
|
934
|
+
coverage: {outline: {visible: false}},
|
|
935
|
+
labels: {
|
|
936
|
+
navigationAreas: {minZoom: 11},
|
|
937
|
+
reefs: {minZoom: 13},
|
|
938
|
+
},
|
|
939
|
+
}),
|
|
940
|
+
},
|
|
941
|
+
});
|
|
942
|
+
```
|
|
943
|
+
|
|
944
|
+
`bathymetry` resolves to vector source `tileflow-bathymetry` and supplies the 13-stop
|
|
945
|
+
`bathymetry-v1` band contract to `water(...)`. `nautical` resolves to `tileflow-nautical`; the
|
|
946
|
+
`nautical(...)` module
|
|
947
|
+
styles its `sounding`, `aid`, `light`, `hazard`, `wreck`, `reef`, `navigation_area`, and `coverage`
|
|
948
|
+
layers. The schema also names the product's provider-neutral provenance fields (`provenance`,
|
|
949
|
+
`provider`, `cell`, `edition`, `update`, `scale`, `coverage`, and `licence`). `chart` enables both.
|
|
950
|
+
Point features label depth, name, or light character as applicable. The `lighthouses` symbol style
|
|
951
|
+
targets `aid` points whose canonical `class` is `lighthouse`, leaving other aids under `aids`.
|
|
952
|
+
Polygon reefs, navigation
|
|
953
|
+
areas, hazards, wrecks, and named/provider coverage have separate collision-aware labels under
|
|
954
|
+
`nautical({labels: {...}})`; unnamed coverage does not emit empty text.
|
|
955
|
+
Core does not hard-code a nautical provider or attribution: the selected TileJSON owns its actual
|
|
956
|
+
coverage and attribution, and derived vector tiles are not represented as a certified ENC for
|
|
957
|
+
navigation. The V1 geometry contract preserves `sounding`, `aid`, and `light` as points;
|
|
958
|
+
`reef`, `navigation_area`, and `coverage` as polygons; and both point and polygon geometries for
|
|
959
|
+
`hazard` and `wreck`. Point symbols and polygon areas compile separately, without fabricated
|
|
960
|
+
centroids.
|
|
961
|
+
Advanced callers can override either TileJSON URL, attribution, or source ID and can set the other
|
|
962
|
+
member to `false`:
|
|
963
|
+
|
|
964
|
+
```ts
|
|
965
|
+
marine: {
|
|
966
|
+
bathymetry: {url: 'https://tiles.example.test/depth/tiles.json'},
|
|
967
|
+
nautical: false,
|
|
968
|
+
}
|
|
969
|
+
```
|
|
970
|
+
|
|
971
|
+
Use the `bathymetry(...)` helper when the map needs relief or product-level styling. Bathymetry is
|
|
972
|
+
one logical product with two independently loaded physical sources:
|
|
973
|
+
|
|
974
|
+
```ts
|
|
975
|
+
marine: {
|
|
976
|
+
bathymetry: bathymetry({
|
|
977
|
+
display: 'hybrid',
|
|
978
|
+
bands: {
|
|
979
|
+
opacity: fixed(0.72, {reason: 'Keep chart symbols above the depth wash'}),
|
|
980
|
+
},
|
|
981
|
+
contours: {visible: true},
|
|
982
|
+
relief: {
|
|
983
|
+
multidirectional: true,
|
|
984
|
+
opacity: fixed(0.18, {reason: 'Relief stays legible beneath bands and labels'}),
|
|
985
|
+
},
|
|
986
|
+
}),
|
|
987
|
+
nautical: false,
|
|
988
|
+
}
|
|
989
|
+
```
|
|
990
|
+
|
|
991
|
+
`display: 'bands'` loads only vector TileJSON `/tiles/bathymetry/tiles.json` as
|
|
992
|
+
`tileflow-bathymetry`. `display: 'relief'` loads only `/tiles/bathymetry/dem/tiles.json` as
|
|
993
|
+
`tileflow-bathymetry-dem` and emits continuous `color-relief` plus hillshade. `display: 'hybrid'`
|
|
994
|
+
loads both. The hosted DEM contract is 512 px, Terrarium-encoded, lossless WebP; advanced compatible
|
|
995
|
+
sources may override `url`, `sourceId`, `encoding`, or `tileSize` inside `relief`.
|
|
996
|
+
Multidirectional illumination uses directions 270°, 315°, 0°, and 45°. The simple
|
|
997
|
+
`marine: 'chart'` shorthand deliberately stays vector-only; DEM relief is an explicit advanced
|
|
998
|
+
opt-in.
|
|
999
|
+
|
|
1000
|
+
The vector palette uses thresholds −11 000, −8 000, −6 000, −4 000, −2 000, −1 000, −500, −200,
|
|
1001
|
+
−100, −50, −20, −10, and 0 metres. `bands`, `contours`, and `labels` remain separate controls.
|
|
1002
|
+
Until native contour fields are contracted, contours are a visual fallback drawn from band polygon
|
|
1003
|
+
edges, not surveyed isolines. The V1 schema exposes optional source-layer names
|
|
1004
|
+
`bathymetry_contour`, `seafloor_landform`, `water_name`, and `bathymetry_coverage`, but promises
|
|
1005
|
+
fields only for required `bathymetry.min_depth` and `bathymetry.sort_key`.
|
|
1006
|
+
|
|
1007
|
+
The DEM enables future client-side sampling and depth profiles, but Core intentionally exposes no
|
|
1008
|
+
profile or remote sampling API yet. Rendered relief must not be interpreted as navigation-safe
|
|
1009
|
+
depth data.
|
|
255
1010
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
1011
|
+
Omitting `marine` preserves the transitional Tileflow World V1 bathymetry fallback. Declaring
|
|
1012
|
+
`marine: 'none'` suppresses that fallback, while a selected bathymetry sidecar always takes
|
|
1013
|
+
precedence. Compiled styles retain the singular `tileflow:data` metadata for existing consumers and
|
|
1014
|
+
add `tileflow:sources` plus `tileflow:sourceRequirements` for independent product validation.
|
|
1015
|
+
|
|
1016
|
+
Detailed city datasets can bind `sidewalk`, `streetFurniture`, and `circularFeature` source layers
|
|
1017
|
+
through `openMapTiles({layers, fields})`. When present, `roads.sidewalks` owns source-backed
|
|
1018
|
+
pedestrian polygons, `roads.crossings` owns oriented crossing icons, and `roads.roundabouts` owns
|
|
1019
|
+
metric circular road rings. These bindings are optional: a generic OpenMapTiles source remains
|
|
1020
|
+
valid and the compiler omits unsupported detail instead of inventing geometry. Crossing icons are
|
|
1021
|
+
explicit because the data contract cannot assume a sprite name. They require positive physical
|
|
1022
|
+
`markings` evidence and reject `crossing=no`; both fields are semantic bindings, so custom schemas
|
|
1023
|
+
can remap them without weakening that rule.
|
|
1024
|
+
|
|
1025
|
+
The optional `business_corridor` extension contains activity-selected source footprints below
|
|
1026
|
+
buildings. Its `activity_score`, `rank`, `min_zoom`, and `confidence` fields control a quiet local
|
|
1027
|
+
warm tint without drawing POI-radius circles. Current building footprints may carry sparse
|
|
1028
|
+
`building_tone=commercial|destination|active`; absence means the neutral base building. The three
|
|
1029
|
+
values preserve why the warm tone was selected even when a theme maps them to the same color.
|
|
1030
|
+
Building visibility is fixed by the layer zoom contract and never by optional semantics. Immutable
|
|
1031
|
+
V8.9 archives carrying `building_kind` or `has_business` remain readable as a defensive
|
|
1032
|
+
compatibility path, but new candidates do not emit those fields. The land module also exposes
|
|
1033
|
+
`medical`, `education`, and `government` independently instead of collapsing their already distinct
|
|
1034
|
+
source classes into one civic color.
|
|
1035
|
+
|
|
1036
|
+
World and text assets are independent contracts. `tileflowWorld()` selects `world-v1/current` or an
|
|
1037
|
+
exact `releaseId + descriptorSha256`; a `glyphs` declaration contains its own complete URL. Ordinary
|
|
1038
|
+
imports of Streets, Baedeker, Ferraris, Härad, Siegfried, Soundings, Cyberpunk, Matrix, Verdant,
|
|
1039
|
+
and San Francisto
|
|
1040
|
+
remain usable because each official map owns or inherits a URL or packaged-font provider.
|
|
1041
|
+
URL-backed maps become exact-byte reproducible
|
|
1042
|
+
when the immutable global base-asset set is published and their explicit URL is updated to its
|
|
1043
|
+
`assetSetSha256` path; no World or compiler fallback participates in that rollout. The global
|
|
1044
|
+
base-asset manifest's hash and a map build manifest's identically named `assetSetSha256` use
|
|
1045
|
+
different contracts and must never be substituted for one another.
|
|
1046
|
+
|
|
1047
|
+
Upstream data attribution remains in the MapLibre source. `Map by Tileflow` is a separate product
|
|
1048
|
+
credit/trademark surface and must not replace, obscure, or be presented as upstream attribution.
|
|
261
1049
|
|
|
262
1050
|
## Capture scenes
|
|
263
1051
|
|
|
264
|
-
Commit named scenes
|
|
1052
|
+
Commit named `scenes` on the singular exported map. A scene implicitly targets that map, so it does
|
|
1053
|
+
not repeat a `map` selector. Scene metadata does not inherit and is removed before cartographic
|
|
1054
|
+
compilation; it affects visual evidence, not style or manifest identity.
|
|
265
1055
|
|
|
266
1056
|
```ts
|
|
267
|
-
|
|
268
|
-
|
|
1057
|
+
import {defineMap} from '@tileflow/core';
|
|
1058
|
+
import {streets} from '@tileflow/maps';
|
|
1059
|
+
|
|
1060
|
+
export default defineMap({
|
|
1061
|
+
id: 'madrid',
|
|
1062
|
+
name: 'Madrid',
|
|
1063
|
+
version: 1,
|
|
1064
|
+
extends: streets,
|
|
269
1065
|
scenes: {
|
|
270
1066
|
'madrid-desktop': {
|
|
271
|
-
|
|
1067
|
+
theme: 'dark',
|
|
272
1068
|
camera: {type: 'center', center: [-3.7038, 40.4168], zoom: 12},
|
|
273
1069
|
viewport: {width: 1280, height: 800, dpr: 1},
|
|
274
1070
|
},
|
|
@@ -276,31 +1072,100 @@ export default defineTileflow({
|
|
|
276
1072
|
});
|
|
277
1073
|
```
|
|
278
1074
|
|
|
279
|
-
Use `tileflow
|
|
280
|
-
for exact pixels and a schema-version-
|
|
281
|
-
image identities.
|
|
1075
|
+
Use `tileflow preview --scene madrid-desktop` (`tileflow dev` remains an alias) for live review and
|
|
1076
|
+
`tileflow capture madrid-desktop` for exact pixels and a schema-version-4 receipt containing the
|
|
1077
|
+
concrete theme plus Streets, data, style, renderer, and image identities. World capture resolves the selected TileJSON
|
|
1078
|
+
once and records the exact `world-v1` release plus descriptor/archive/data-contract hashes;
|
|
1079
|
+
external fixtures may identify their explicit revision.
|
|
1080
|
+
|
|
1081
|
+
Every exact World V1 release ID is already canonical at the producer boundary: 12–128 characters
|
|
1082
|
+
matching `^world-v1-[a-z0-9][a-z0-9._-]*[a-z0-9]$`. Core validates the literal value and never trims,
|
|
1083
|
+
lowercases, or upgrades it to another World generation.
|
|
282
1084
|
|
|
283
1085
|
## Public API and browser subpath
|
|
284
1086
|
|
|
285
|
-
Config, data, modules, compilation, validation, runtime resolution, and capture-scene APIs
|
|
286
|
-
from the package root.
|
|
287
|
-
|
|
1087
|
+
Config, data, modules, compilation, validation, runtime resolution, and capture-scene APIs remain
|
|
1088
|
+
available from the package root. Capture scenes, manifests, and browser-runtime surfaces also have
|
|
1089
|
+
explicit `@tileflow/core/capture`, `@tileflow/core/manifest`, and `@tileflow/core/runtime`
|
|
1090
|
+
subpaths. Node build integrations import the small deterministic map-identity contract from
|
|
1091
|
+
`@tileflow/core/build`: `collectTileflowMapBuildLineage`, `createTileflowMapBuildManifest`,
|
|
1092
|
+
`hashTileflowMapRevision`, `hashTileflowAssetSet`, and `hashTileflowAssetSetIdentities`. The latter
|
|
1093
|
+
reproduces the same asset-set v1 hash from exact per-file identities after another system has
|
|
1094
|
+
independently confirmed the immutable bytes; package or bundle hashes are not substitutes. The
|
|
1095
|
+
previous aggregate-wrapper and recipe-selector APIs, `renderer`, top-level `tiles`/`tileset`, module
|
|
1096
|
+
arrays, and top-level raw `layers` are not part of this API. Raw MapLibre layers are accepted only
|
|
1097
|
+
inside a named `maplibreOverlay()` bound to a named Team source.
|
|
1098
|
+
|
|
1099
|
+
Advanced cartography remains in the same public language: `withRenderStack()`, `renderPass()`, and
|
|
1100
|
+
`refineRenderTarget()` address owner-local semantic targets without exposing physical layer IDs,
|
|
1101
|
+
sources, source layers, raw filters, or before/after anchors. Typed `field()` references and
|
|
1102
|
+
`expr.*` builders stay semantic until the compiler's single lowering boundary. There is no recipe
|
|
1103
|
+
subpath or alternate compiler integration API.
|
|
1104
|
+
|
|
1105
|
+
`mapRevisionSha256` hashes only resolved cartography: the effective design after `extends`, the
|
|
1106
|
+
semantic language, compiler-owned effective contributions, and exact source icon/font identities.
|
|
1107
|
+
Leaf `id`, `name`, editorial `mapVersion`, default `view`, capture `scenes`, and `delivery` policy do
|
|
1108
|
+
not change that content identity. They remain explicit on the map, manifest, Style, capture receipt,
|
|
1109
|
+
or Hosted deployment fingerprint that owns them. Compiler ABI, package versions, generated assets,
|
|
1110
|
+
and the concrete release selected by World `current` likewise have separate identities.
|
|
1111
|
+
|
|
1112
|
+
Runtime manifest version 1 has one shape for local and Hosted delivery. Every map entry owns
|
|
1113
|
+
`defaultTheme`, optional `systemThemes`, and a `themes` record whose leaves contain
|
|
1114
|
+
`colorScheme`, `styleUrl`, and optional font/deployment identity. Hosted-only identity is additive
|
|
1115
|
+
metadata on the same map entry; there is no delivery discriminator or parallel `styles` alias.
|
|
1116
|
+
`parseTileflowRuntimeManifest()` accepts only this canonical shape. The strict bounded schema uses
|
|
1117
|
+
the same portable map and theme IDs as authoring, rejects `system` as a published name, duplicate
|
|
1118
|
+
font identities, unsafe owner-relative URLs, prototype-bearing input, unknown fields, and JSON
|
|
1119
|
+
larger than 1 MiB.
|
|
1120
|
+
|
|
1121
|
+
```json
|
|
1122
|
+
{
|
|
1123
|
+
"version": 1,
|
|
1124
|
+
"maps": {
|
|
1125
|
+
"madrid": {
|
|
1126
|
+
"defaultTheme": "light",
|
|
1127
|
+
"systemThemes": {"light": "light", "dark": "dark"},
|
|
1128
|
+
"themes": {
|
|
1129
|
+
"light": {"colorScheme": "light", "styleUrl": "/styles/madrid/light.json"},
|
|
1130
|
+
"dark": {"colorScheme": "dark", "styleUrl": "/styles/madrid/dark.json"}
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
```
|
|
1136
|
+
|
|
1137
|
+
Runtime fetches share a successful result for 30 seconds, never cache failures, use
|
|
1138
|
+
`cache: 'no-store'`, enforce a 10-second timeout (configurable up to 60 seconds), compose an
|
|
1139
|
+
external abort signal, and can be invalidated with `clearTileflowManifestCache()`.
|
|
1140
|
+
|
|
1141
|
+
Named map `view` values travel in the manifest. `resolveTileflowRuntimeTheme()` turns an omitted,
|
|
1142
|
+
explicit, or browser `system` request into one concrete published entry; unknown names fail closed.
|
|
1143
|
+
`resolveTileflowRuntimeView()` defines the shared
|
|
1144
|
+
precedence as explicit runtime values, then the manifest view, then the single exported
|
|
1145
|
+
`defaultTileflowRuntimeView` (`[0, 20]`, zoom 2, bearing/pitch 0). Browser delivery is one
|
|
1146
|
+
discriminated `TileflowRuntimeSource`: `kind: 'tileflow'` resolves a named map only through its
|
|
1147
|
+
published manifest, while `kind: 'maplibre'` accepts a direct style object or URL. The runtime
|
|
1148
|
+
subpath does not import the config compiler, invent a localhost style URL, or change image mode by
|
|
1149
|
+
environment.
|
|
288
1150
|
|
|
289
1151
|
Framework adapters import the browser-only lifecycle kernel explicitly:
|
|
290
1152
|
|
|
291
1153
|
```ts
|
|
292
1154
|
import {
|
|
1155
|
+
attachTileflowFairUseNotice,
|
|
293
1156
|
attachTileflowMapLifecycle,
|
|
294
|
-
createTileflowMarkerController,
|
|
295
1157
|
createTileflowSessionStarter,
|
|
296
1158
|
createTileflowTransformRequest,
|
|
1159
|
+
registerTileflowWorldRequestBridge,
|
|
297
1160
|
} from '@tileflow/core/browser';
|
|
298
1161
|
```
|
|
299
1162
|
|
|
300
1163
|
The browser entry is not re-exported from the root. It is SSR-safe to import, has no MapLibre
|
|
301
1164
|
runtime dependency, and reads no browser global during module evaluation. See the
|
|
302
1165
|
[framework browser runtime contract](../../docs/contracts/framework-browser-runtime.md) and the
|
|
303
|
-
[cartographic authoring contract](../../docs/contracts/cartographic-authoring.md).
|
|
1166
|
+
[cartographic authoring contract](../../docs/contracts/cartographic-authoring.md). Exact field
|
|
1167
|
+
resolution and asset rules live in the
|
|
1168
|
+
[map inheritance contract](../../docs/contracts/map-inheritance.md).
|
|
304
1169
|
|
|
305
1170
|
## Hosted session authorization
|
|
306
1171
|
|
|
@@ -315,12 +1180,99 @@ can set `grantTimeoutMs` from 1 to 120,000 milliseconds.
|
|
|
315
1180
|
server-owned commercial authorization. User `transformRequest` callbacks still run first; Tileflow
|
|
316
1181
|
then decorates only the resulting eligible URL and preserves the other request options.
|
|
317
1182
|
|
|
1183
|
+
Set `analytics.surfaceId` to a stable product location when the same Map appears in several places:
|
|
1184
|
+
|
|
1185
|
+
```ts
|
|
1186
|
+
const analytics = {surfaceId: 'store-locator'};
|
|
1187
|
+
```
|
|
1188
|
+
|
|
1189
|
+
A Surface ID is 1–64 lowercase ASCII letters, digits, `.`, `_`, or `-`; it starts and ends with a
|
|
1190
|
+
letter or digit. Missing or invalid values become `default`. Use a durable integration name such as
|
|
1191
|
+
`checkout` or `dealer-search`, not a URL, branch, preview, random component instance, or user ID.
|
|
1192
|
+
Surface labels do not change authorization, entitlement, or Session identity.
|
|
1193
|
+
|
|
1194
|
+
For exact Tileflow World release tiles, framework adapters also use the browser request bridge to
|
|
1195
|
+
observe the response's safe fair-use state without adding identity, query parameters, credentials,
|
|
1196
|
+
or user headers to the public World URL. The bridge requires the immutable release path and exactly
|
|
1197
|
+
one lowercase descriptor digest; the retired mutable World template is never intercepted. Early
|
|
1198
|
+
`GRACE` stays silent; signed late `GRACE` creates a compact
|
|
1199
|
+
accessible owner-action pill, and `MANAGED_REQUIRED` creates a stronger in-map banner. A missing
|
|
1200
|
+
header, absent tile, MapLibre error, or failed response cannot erase an existing manage action; a
|
|
1201
|
+
later successful `OPEN` response can clear it. Shaped empty tiles remain render-safe while the owner
|
|
1202
|
+
action stays available. Successful World tile bodies are streamed with a 16 MiB maximum: an
|
|
1203
|
+
oversized `Content-Length` is rejected before reading, and chunked responses are cancelled as soon
|
|
1204
|
+
as their accumulated bytes cross the same bound. Abort signals cancel an active reader; errors do
|
|
1205
|
+
not include the remote response body.
|
|
1206
|
+
|
|
318
1207
|
Other subpaths under `src/`, `themes/`, `templates/`, and `modules/` are internal implementation
|
|
319
1208
|
details and can change during alpha releases.
|
|
320
1209
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
1210
|
+
Official map definitions and the source assets they own ship together in `@tileflow/maps`, not in
|
|
1211
|
+
Core. That package exports the maps and their reusable directory descriptors:
|
|
1212
|
+
|
|
1213
|
+
```ts
|
|
1214
|
+
import {
|
|
1215
|
+
baedeker,
|
|
1216
|
+
baedekerFonts,
|
|
1217
|
+
baedekerIcons,
|
|
1218
|
+
cyberpunk,
|
|
1219
|
+
cyberpunkFonts,
|
|
1220
|
+
cyberpunkIcons,
|
|
1221
|
+
ferraris,
|
|
1222
|
+
ferrarisIcons,
|
|
1223
|
+
harad,
|
|
1224
|
+
haradIcons,
|
|
1225
|
+
matrix,
|
|
1226
|
+
matrixFonts,
|
|
1227
|
+
matrixIcons,
|
|
1228
|
+
sanFrancisto,
|
|
1229
|
+
sanFrancistoIcons,
|
|
1230
|
+
siegfried,
|
|
1231
|
+
siegfriedFonts,
|
|
1232
|
+
siegfriedIcons,
|
|
1233
|
+
soundings,
|
|
1234
|
+
soundingsIcons,
|
|
1235
|
+
streets,
|
|
1236
|
+
streetsIcons,
|
|
1237
|
+
streetsThemes,
|
|
1238
|
+
verdant,
|
|
1239
|
+
verdantIcons,
|
|
1240
|
+
} from '@tileflow/maps';
|
|
1241
|
+
```
|
|
1242
|
+
|
|
1243
|
+
All ten official maps are complete standalone maps. The sole semantic compiler is implicit; none
|
|
1244
|
+
imports or extends another official map, and each declares its own icon directory.
|
|
1245
|
+
Streets declares `[streetsIcons]` and exposes its complete coordinated appearances as
|
|
1246
|
+
`streetsThemes.light` and `streetsThemes.dark`; image tokens select the matching sidewalk pattern
|
|
1247
|
+
without changing the asset collection. Baedeker declares `[baedekerIcons]`, whose eight original
|
|
1248
|
+
patterns support its travel-atlas design, and `[baedekerFonts]`, its own Cormorant Garamond
|
|
1249
|
+
directory; it derives contours in the browser from unpackaged Mapterhorn terrain tiles. Cyberpunk
|
|
1250
|
+
declares `[cyberpunkIcons]` and `[cyberpunkFonts]`; Matrix
|
|
1251
|
+
independently declares `[matrixIcons]` and `[matrixFonts]`. San Francisto declares
|
|
1252
|
+
`[sanFrancistoIcons]` for its four technical hatches and schematic POI node, derives contours from
|
|
1253
|
+
unpackaged Mapterhorn tiles, and uses the canonical Noto Sans glyph provider. The same asset
|
|
1254
|
+
operation is available to applications:
|
|
1255
|
+
|
|
1256
|
+
```ts
|
|
1257
|
+
export default defineMap({
|
|
1258
|
+
id: 'brand-map',
|
|
1259
|
+
version: 1,
|
|
1260
|
+
extends: streets,
|
|
1261
|
+
icons: [...streets.icons, './icons'],
|
|
1262
|
+
});
|
|
1263
|
+
```
|
|
1264
|
+
|
|
1265
|
+
`icons` is a `readonly TileflowIconDirectory[]`. Omission inherits the parent's exact array,
|
|
1266
|
+
declaration replaces it atomically, and `[]` means no icons. Directories apply left to right.
|
|
1267
|
+
`<id>.<ext>` publishes an icon as `<id>`; `<id>.pattern.<ext>` publishes an intrinsic-size pattern
|
|
1268
|
+
as `<id>`. The published ID must already be canonical lower-kebab; a later exact ID wins and a
|
|
1269
|
+
case-only collision fails. There is no built-in selector, source object, external
|
|
1270
|
+
sprite selector, icon mapping, icon-specific inheritance, additive command, or compatibility alias.
|
|
1271
|
+
|
|
1272
|
+
`@tileflow/dev` resolves local and package directory descriptors, verifies real-path containment,
|
|
1273
|
+
and prepares ordinary public artifacts without serializing installation paths. It compiles one
|
|
1274
|
+
deterministic sprite from the final icon composition and validates every literal `icon-image`,
|
|
1275
|
+
`fill-pattern`, and `line-pattern` in the final style against it. It also prepares any declared font
|
|
1276
|
+
directories generically; `baedekerFonts`, `cyberpunkFonts`, `matrixFonts`, and `siegfriedFonts` are
|
|
1277
|
+
ordinary package descriptors rather than pipeline special cases. Calling the pure compiler for a map whose style needs unprepared
|
|
1278
|
+
assets fails instead of emitting broken runtime references.
|