@tileflow/core 0.1.0-alpha.41 → 0.1.0-alpha.43

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/README.md +51 -69
  2. package/THIRD_PARTY_NOTICES.md +1 -1
  3. package/dist/{3d-CzLTD0RK.d.ts → 3d-CW5Ls0An.d.ts} +32 -2
  4. package/dist/3d.d.ts +1 -1
  5. package/dist/3d.js +337 -34
  6. package/dist/browser.d.ts +119 -7
  7. package/dist/browser.js +754 -61
  8. package/dist/build.d.ts +8 -4
  9. package/dist/build.js +284 -4
  10. package/dist/config-reference.json +4005 -2284
  11. package/dist/{icon-composition-HUh_Lj8Z.d.ts → icon-composition-Bbw9umUA.d.ts} +46 -3
  12. package/dist/index.d.ts +22 -5
  13. package/dist/index.js +254 -6
  14. package/dist/{manifest-types-DnhSJjCM.d.ts → manifest-types-CwjHd4qc.d.ts} +10 -0
  15. package/dist/manifest.d.ts +2 -2
  16. package/dist/native-profile.d.ts +4 -0
  17. package/dist/native-profile.js +2 -0
  18. package/dist/native.d.ts +87 -1
  19. package/dist/native.js +980 -460
  20. package/dist/runtime.d.ts +1 -1
  21. package/dist/worker/camera-tile-worker.global.js +2 -2
  22. package/package.json +4 -5
  23. package/docs/application-tile-reading.md +0 -86
  24. package/docs/authoring-model.md +0 -269
  25. package/docs/boundary-areas-and-choropleths.md +0 -217
  26. package/docs/buildings-3d.md +0 -608
  27. package/docs/camera-moves-and-tile-cache.md +0 -112
  28. package/docs/capture-scenes.md +0 -40
  29. package/docs/capture-shots.md +0 -173
  30. package/docs/cartographic-defaults.md +0 -210
  31. package/docs/compiled-style-performance.md +0 -286
  32. package/docs/data-is-separate-from-design.md +0 -610
  33. package/docs/depth-markers.md +0 -65
  34. package/docs/dynamic-style-images.md +0 -100
  35. package/docs/globe-atmosphere.md +0 -113
  36. package/docs/hosted-session-authorization.md +0 -106
  37. package/docs/map-and-terrain-examples.md +0 -156
  38. package/docs/map-emphasis.md +0 -89
  39. package/docs/native-artifact-profile.md +0 -152
  40. package/docs/native-resource-urls.md +0 -369
  41. package/docs/nautical-marks.md +0 -308
  42. package/docs/public-api-and-browser-subpath.md +0 -101
  43. package/docs/route-drawing.md +0 -191
  44. package/docs/shared-visual-primitives.md +0 -101
  45. package/docs/street-lights.md +0 -127
  46. package/docs/street-sky.md +0 -156
  47. package/docs/style-states.md +0 -73
  48. package/docs/sun-and-light.md +0 -167
  49. package/docs/theme-transitions-and-blends.md +0 -317
  50. package/docs/themes-and-module-styles.md +0 -156
  51. package/docs/trees-3d.md +0 -295
@@ -1,610 +0,0 @@
1
- # Data is separate from design
2
-
3
- Start with the [@tileflow/core guide](https://github.com/tileflow/tileflow-sdk/blob/main/packages/core/README.md) for installation and a complete first example.
4
-
5
- Team data uses named logical sources and validated MapLibre overlays. The hosted identity never
6
- contains a CDN URL; `local` is the explicit account-free PMTiles resolution for Dev and Capture:
7
-
8
- ```ts
9
- import {defineMap, hostedTileset, maplibreOverlay} from '@tileflow/core';
10
- import {streets} from '@tileflow/maps';
11
-
12
- const stores = hostedTileset({
13
- tileset: 'stores',
14
- local: './data/stores.pmtiles',
15
- attribution: 'Store data © Example',
16
- });
17
-
18
- export default defineMap({
19
- id: 'store-locator',
20
- version: 1,
21
- extends: streets,
22
- sources: {stores},
23
- overlays: {
24
- stores: maplibreOverlay({
25
- source: 'stores',
26
- placement: 'above-roads',
27
- layers: [
28
- {
29
- id: 'stores-points',
30
- type: 'circle',
31
- 'source-layer': 'store_locations',
32
- paint: {
33
- 'circle-color': ['match', ['get', 'category'], 'restaurant', '#ef4444', '#64748b'],
34
- },
35
- },
36
- ],
37
- }),
38
- },
39
- });
40
- ```
41
-
42
- Tileflow automatically registers only protocols in namespaces that Tileflow owns.
43
- `tileflow-pmtiles://` is reserved for PMTiles resources managed by Tileflow. Tileflow never
44
- automatically registers, removes, or replaces the global `pmtiles://` handler. Explicit PMTiles
45
- sources supplied by an application remain the application's responsibility.
46
-
47
- For local resolution, the Style URL is based on the logical tileset ID and remains stable when Dev
48
- creates a new immutable snapshot. Physical generation IDs and local content hashes never enter the
49
- Style representation. Each Dev PMTiles request retains the snapshot it resolved until its response
50
- is prepared, and strong generation ETags prevent one cached PMTiles read from silently combining
51
- ranges from different generations. Standalone Capture retains one snapshot for its complete render.
52
- Application Capture does not freeze every local dataset across the complete screenshot window; the
53
- application's development server remains authoritative for its per-read generation.
54
-
55
- `local` does not make build output own or publish the archive. Production builds reject unresolved
56
- local PMTiles. Explicitly publish the tileset through Tileflow's managed upload, validation,
57
- versioning, and hosting flow, or serve it from infrastructure owned by the application.
58
-
59
- One overlay owns one ordered group of ordinary MapLibre Style Layers. Public layer IDs are retained
60
- for `map.on(...)`, feature queries, and application state. Placement is one of `above-water`,
61
- `below-roads`, `above-roads`, `above-buildings`, `below-labels`, or `above-labels`; overlays sharing
62
- a placement sort by overlay ID. A map accepts up to 16 Team sources in addition to Tileflow World
63
- and terrain. These semantic boundaries do not expose generated basemap layer IDs.
64
- `sources` and `overlays` merge by key through `extends`, declarations replace atomically, and
65
- `remove()` deletes one inherited key. Removing a still-used source fails validation. There is no
66
- interaction DSL or parallel expression language.
67
-
68
- Omitting `data` selects the compiler-owned Tileflow World `v1` compatibility generation:
69
-
70
- ```ts
71
- import {streets} from '@tileflow/maps';
72
-
73
- export default streets;
74
- ```
75
-
76
- Theme typography can set `fallbacks`, `letterSpacing`, and `transform` globally or per label
77
- domain, while the font token catalog gives module styles a stable semantic target. Text delivery is
78
- explicit and atomic: a map may declare either ordered `fonts` directories
79
- for browser font files or a `glyphs` provider for PBF glyphs, never both. Omitting both inherits the
80
- parent's provider; declaring either replaces an inherited provider of either kind. After resolution,
81
- a map that emits text must have exactly one provider. A text-free standalone map may omit both. On a derived
82
- map, omission inherits the parent provider while `fonts: []` explicitly removes it; that empty array
83
- is valid only when the resolved map emits no text.
84
-
85
- ```ts
86
- import {defineMap, defineTheme} from '@tileflow/core';
87
- import {streets, streetsThemes} from '@tileflow/maps';
88
-
89
- const brandLight = defineTheme(streetsThemes.light, {
90
- id: 'brand-light',
91
- version: 1,
92
- colorScheme: 'light',
93
- tokens: {font: {default: 'Brand Sans Regular', places: 'Brand Sans Regular'}},
94
- });
95
- const brandDark = defineTheme(streetsThemes.dark, {
96
- id: 'brand-dark',
97
- version: 1,
98
- colorScheme: 'dark',
99
- tokens: {font: {default: 'Brand Sans Regular', places: 'Brand Sans Regular'}},
100
- });
101
-
102
- export default defineMap({
103
- id: 'brand-map',
104
- version: 1,
105
- extends: streets,
106
- fonts: ['./fonts'],
107
- themes: {light: brandLight, dark: brandDark},
108
- defaultTheme: 'light',
109
- systemThemes: {light: 'light', dark: 'dark'},
110
- });
111
- ```
112
-
113
- Node preparation reads TTF, OTF, and WOFF2 files, uses each OpenType full name as its exact
114
- `text-font` ID, and requires `LICENSE.txt` in every contributing directory. Directories apply left
115
- to right; a later exact name replaces an earlier face and case-only collisions fail. Only primary
116
- faces used by the final style become content-addressed assets and strict `tileflow:fontFaces`
117
- metadata. `font` names an exact OpenType full name or glyph face; local `fallbacks` name exact faces
118
- or explicit CSS generic families. Tileflow never appends a weight suffix or derives one face from
119
- another; the only derived stacks are line-fitted text backgrounds on a `glyphs` provider, which keep
120
- their source faces' shapes. Browser adapters load local faces before MapLibre. Native releases use a locked PBF
121
- `glyphs` URL provider instead, which enumerates the exact comma-joined MapLibre request keys in
122
- `fontStacks`.
123
-
124
- Streets, Soundings, and Blueprint each declare
125
- `https://api.tileflow.dev/base/33d4de5e8086d9d629d67d3f39fedb87e23686c4c1ac653c27e2a52aee9d00b3/glyphs/{fontstack}/{range}.pbf`
126
- with the exact `Noto Sans Regular` and `Noto Sans Bold` stacks. The full hash identifies one
127
- published, immutable glyph collection independent of World data. The older `/fonts/...` endpoint
128
- remains a mutable compatibility URL and does not make a resolved map byte-reproducible. The global
129
- glyph `assetSetSha256` is not the same-domain value as the per-map `assetSetSha256` in
130
- `build-manifest.json`.
131
- Baedeker, 1777, and Siegfried each own a packaged directory containing Cormorant Garamond Regular, SemiBold, and Italic.
132
-
133
- Name the official generation deliberately, or use another OpenMapTiles-compatible vector source:
134
-
135
- ```ts
136
- import {openMapTiles, tileflowWorld, vectorTiles} from '@tileflow/core';
137
-
138
- const official = tileflowWorld();
139
- const external = vectorTiles({
140
- tiles: ['pmtiles://./test/fixtures/world.pmtiles'],
141
- revision: 'fixture-1',
142
- attribution: '© Example © OpenStreetMap contributors',
143
- schema: openMapTiles(),
144
- });
145
- ```
146
-
147
- The resolved map controls how data is drawn; `data` controls where compatible features come from.
148
- For Tileflow World, the compiler emits the selector TileJSON URL
149
- `https://api.tileflow.dev/tiles/world/tiles.json`. Omitted data or `tileflowWorld()` selects
150
- `world-v1/current`; `tileflowWorld({release: {releaseId, descriptorSha256}})` binds an exact release
151
- through query parameters. Runtime and capture resolve `current` once per session or job and then
152
- use the immutable release described by that response. `openMapTiles({layers, fields})` can bind renamed
153
- source-layers or properties while preserving the versioned semantic contract. External browser
154
- credentials do not belong in public Style JSON; supply them through the framework's
155
- `transformRequest` integration.
156
-
157
- External exact-test sources may use one TileJSON `url` or a bounded direct `tiles` list with optional
158
- `bounds`, `minzoom`, and `maxzoom`. `pmtiles://` is supported for a checked-in or bring-your-own
159
- archive. Public vector URLs are HTTPS, root-relative, or HTTP only on loopback development hosts.
160
- A PMTiles URL names one `.pmtiles` archive through an HTTPS, loopback HTTP, root-relative, or safe
161
- repository-relative target; credentials, fragments, traversal, and other protocols are rejected at
162
- both authoring validation and compilation. An external `revision` participates in capture identity,
163
- so changing fixture bytes cannot silently reuse a baseline.
164
-
165
- Tileflow's OpenMapTiles contract also recognizes the optional `globallandcover` extension. The
166
- official Streets map maps its `barren`, `crop`, `grass`, `shrub`, `snow`, `trees`, and
167
- `urban` classes beneath the OSM land layers. Tileflow World V1 supplies native generalized
168
- geometry through z10; Streets fades it progressively while detailed `landcover`/`landuse` gains
169
- opacity and removes it at z11. This is a macro bridge, not local semantic detail. Sources with a
170
- different layer name can use `openMapTiles({layers: {globalLandcover: 'my_landcover'}})`; archives
171
- without the extension remain compatible and simply render no features for that style layer. Use
172
- `land({globalLandcover: {...}})` to customize its fill, opacity, visibility, and zoom range. `crop`
173
- shares the theme's semantic `landcover.farmland` color with detailed farmland, while `barren` uses
174
- `landcover.rock`; neither class borrows a road color.
175
-
176
- The normalized schema also records the meaning of the OpenMapTiles `park` layer. Generic
177
- `openMapTiles()` defaults to `semantics.parkLayer: 'mixed'` and compiles a private compatibility
178
- branch for ordinary legacy parks. `tileflowWorldV1Schema()` fixes it to `'protected-only'`: that
179
- layer is a protection tint, while urban parks and gardens come from
180
- `landcover.class=grass/subclass=park|garden`. The ambiguous legacy `park` class is therefore not
181
- a public land-cover target.
182
-
183
- Tileflow World V1 makes `bathymetry`, `globallandcover`, `circular_feature`, `sidewalk`,
184
- `street_furniture`, and `transportation_outline` mandatory. Use `tileflowWorldV1Schema()` for that product instead of making raw
185
- layer names part of a style. Publication tooling calls `validateTileflowWorldV1Tilejson(...)` and
186
- fails closed unless each layer occurs exactly once, declares its contracted native zooms, and
187
- advertises the typed fields consumed by the maps. This includes bathymetry z0–z9, global land cover
188
- z0–z10, the three detailed-city layers at native z15, and numeric
189
- `transportation.clearance_extra_px_z15` for butt-capping procedural-roundabout approaches. Generic
190
- OpenMapTiles sources keep the detailed extensions optional and the compiler omits unsupported
191
- detail. Resolving omitted data or
192
- `tileflowWorld(...)` uses the strict V1 schema directly, and `water({bathymetry: {...}})` styles the
193
- emitted depth bands without raw layer IDs. Use `water({bathymetryContours: {...}})` to customize the
194
- opt-in companion line layer; an empty style selects subtle defaults, and the zero-depth band is
195
- excluded so the ordinary coastline remains authoritative. The lines follow discrete band polygon
196
- edges and are therefore approximate rather than surveyed depth contours. Use
197
- `water({bathymetryLabels: {...}})` to customize the separate opt-in symbol layer; an empty style
198
- selects the defaults, whose text is the absolute band-minimum number in metres with no unit suffix.
199
- It remains a band-floor annotation, not a survey sounding.
200
-
201
- For independent ocean products, `marine` composes stable auxiliary vector sources without changing
202
- the primary World source:
203
-
204
- ```ts
205
- import {bathymetry, defineMap, fixed, nautical} from '@tileflow/core';
206
- import {streets} from '@tileflow/maps';
207
-
208
- export default defineMap({
209
- id: 'chart',
210
- version: 1,
211
- extends: streets,
212
- marine: 'chart', // 'none' | 'bathymetry' | 'nautical' | 'chart'
213
- modules: {
214
- nautical: nautical({
215
- buoys: {minZoom: 12},
216
- lights: {minZoom: 9},
217
- navigationAreas: {
218
- fill: {opacity: fixed(0.05, {reason: 'Chart navigation-area wash is invariant'})},
219
- },
220
- reefs: {fill: {opacity: fixed(0.3, {reason: 'Chart reef wash is invariant'})}},
221
- labels: {navigationAreas: {minZoom: 12}},
222
- }),
223
- },
224
- });
225
- ```
226
-
227
- `bathymetry` resolves to vector source `tileflow-bathymetry` and supplies the 13-stop
228
- `bathymetry-v1` band contract to `water(...)`. `nautical` resolves to `tileflow-nautical` at
229
- `/tiles/nautical/tiles.json`; the `nautical(...)` module draws its `nautical-v1` buoys, beacons,
230
- lighthouses, lights, hazards, wrecks, reefs, navigation areas, routes, cables, harbours, ports, and
231
- a restrained coverage indication. `chart` enables both. Every map that draws Nautical declares a
232
- "Not for navigation" notice, which the browser runtimes render on the map. The
233
- [Nautical marks guide](https://github.com/tileflow/tileflow-sdk/blob/main/packages/core/docs/nautical-marks.md)
234
- describes the schema, the semantic groups, the theme image tokens, and the notice.
235
- Core does not hard-code a nautical provider or attribution: the selected TileJSON owns its actual
236
- coverage and attribution, and derived vector tiles are not represented as a certified ENC for
237
- navigation. Point symbols and polygon areas compile separately, without fabricated centroids.
238
- Advanced callers can override either TileJSON URL, attribution, or source ID and can set the other
239
- member to `false`:
240
-
241
- ```ts
242
- marine: {
243
- bathymetry: {url: 'https://tiles.example.test/depth/tiles.json'},
244
- nautical: false,
245
- }
246
- ```
247
-
248
- Use the `bathymetry(...)` helper when the map needs relief or product-level styling. Bathymetry is
249
- one logical product with two independently loaded physical sources:
250
-
251
- ```ts
252
- marine: {
253
- bathymetry: bathymetry({
254
- display: 'hybrid',
255
- bands: {
256
- opacity: fixed(0.72, {reason: 'Keep chart symbols above the depth wash'}),
257
- },
258
- contours: {visible: true},
259
- relief: {
260
- multidirectional: true,
261
- opacity: fixed(0.18, {reason: 'Relief stays legible beneath bands and labels'}),
262
- },
263
- }),
264
- nautical: false,
265
- }
266
- ```
267
-
268
- `display: 'bands'` loads only vector TileJSON `/tiles/bathymetry/tiles.json` as
269
- `tileflow-bathymetry`. `display: 'relief'` loads only `/tiles/bathymetry/dem/tiles.json` as
270
- `tileflow-bathymetry-dem` and emits continuous `color-relief` plus hillshade. `display: 'hybrid'`
271
- loads both. The hosted DEM contract is 512 px, Terrarium-encoded, lossless WebP; advanced compatible
272
- sources may override `url`, `sourceId`, `encoding`, or `tileSize` inside `relief`.
273
- Multidirectional illumination uses directions 270°, 315°, 0°, and 45°. The simple
274
- `marine: 'chart'` shorthand deliberately stays vector-only; DEM relief is an explicit advanced
275
- opt-in.
276
-
277
- The vector palette uses thresholds −11 000, −8 000, −6 000, −4 000, −2 000, −1 000, −500, −200,
278
- −100, −50, −20, −10, and 0 metres. `bands`, `contours`, and `labels` remain separate controls.
279
- Without an optional-layer declaration, contours and depth labels retain the legacy polygon-band
280
- behavior. `marine` shorthands and existing maps keep that behavior.
281
-
282
- ### Declare contour and name layers
283
-
284
- For a compatible source, `vectorLayers` explicitly declares optional layers. Core never fetches
285
- TileJSON to detect them and never substitutes polygon edges for declared contour lines. A missing
286
- declared layer therefore draws no features; validate the selected dataset before rendering.
287
- `display: 'bands'` or `'hybrid'` loads the vector source.
288
-
289
- | Declaration | Source layer | Geometry and fields |
290
- | --------------------------------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------- |
291
- | `contours: true` | `bathymetry_contour` | Lines with numeric `depth`, negative metres below the source vertical datum. Zero and positive depths are excluded. |
292
- | `landforms: true` or a range inventory | `seafloor_landform` | Named points, lines, or polygons with stable source `id`, provider `class`, and `name`. |
293
- | `waterNames: true` or a range inventory | `water_name` | Named points, lines, or polygons with the same name contract. |
294
-
295
- Name geometry stays in its source representation. `placement: 'point'` (the default) lets the
296
- renderer place names inside polygons or on points. Use `placement: 'line'` to label source lines.
297
- Core creates no substitute point, centroid, or line feature.
298
-
299
- Name layers preserve source properties; `id` is a source identifier, with no guarantee of a numeric
300
- renderer feature ID. Optional `name:<language>` fields retain the source's language assignments.
301
- `labels({language: 'es'})` selects a nonempty `name:es`, falling back to `name`; `auto` and `local`
302
- use `name` directly. Core does not infer the language of an unqualified name or invent translations.
303
- Optional `min_zoom` is inclusive and `max_zoom` exclusive, defaulting to 0 and 24. These nominal
304
- ranges intersect the symbol style's range. With `true`, MapLibre evaluates feature zoom filters at
305
- integer zooms: `min_zoom: 7.6` first becomes eligible at zoom 8. For exact fractional boundaries,
306
- declare an exhaustive range inventory instead:
307
-
308
- ```ts
309
- vectorLayers: {
310
- contours: true,
311
- landforms: {zoomRanges: [{minZoom: 0, maxZoom: 24}]},
312
- waterNames: {
313
- zoomRanges: [{minZoom: 1, maxZoom: 6.5}, {minZoom: 7.6, maxZoom: 10}],
314
- },
315
- }
316
- ```
317
-
318
- Each declared pair becomes a separate symbol layer with fractional renderer bounds and equality
319
- filters on the source's original range. A `7.6–10` feature appears at 7.6 and disappears at 10.
320
- Unlisted pairs are omitted. Core never discovers ranges from tiles: validate the source inventory
321
- against this exhaustive declaration before rendering, and retain that inventory with reproducible
322
- captures. Numeric pairs must match the source's encoded values exactly.
323
-
324
- Each name layer accepts 1–128 unique pairs with `0 <= minZoom < maxZoom <= 24`;
325
- `tileflowBathymetryNameLayerLimits.maxZoomRanges` exposes the limit. Input order does not affect
326
- output. Different pairs can overlap in zoom because equality filters keep their feature sets
327
- disjoint. A style may narrow a pair's visibility; an empty intersection emits no layer.
328
- Missing or null source bounds default to 0/24. Nonnumeric values use the same numeric-conversion
329
- fallbacks; numeric strings are converted. Such features draw only if their normalized pair is
330
- declared. Core preserves the original source properties.
331
-
332
- Optional numeric `sort_key` defaults to 0; smaller values enter collision placement first within
333
- each cohort. Cohorts follow ascending minimum then maximum zoom and sit below ordinary map labels.
334
- Names use normal collision placement without overlap by default.
335
-
336
- This example declares a source whose regional detail supports zoom 14. Its extended ranges are
337
- explicit design choices, not a promise of worldwide detail:
338
-
339
- <!-- docs:check -->
340
-
341
- ```ts
342
- import {bathymetry, defineMap, expr, field, fixed, labels, water} from '@tileflow/core';
343
- import {soundings} from '@tileflow/maps';
344
-
345
- export default defineMap({
346
- id: 'regional-depth',
347
- version: 1,
348
- extends: soundings,
349
- marine: {
350
- bathymetry: bathymetry({
351
- url: 'https://tiles.example.test/regional-depth/tiles.json',
352
- vectorLayers: {
353
- contours: true,
354
- landforms: {zoomRanges: [{minZoom: 0, maxZoom: 24}]},
355
- waterNames: {zoomRanges: [{minZoom: 7.6, maxZoom: 10}]},
356
- },
357
- contours: {},
358
- labels: {},
359
- }),
360
- nautical: false,
361
- },
362
- modules: {
363
- labels: labels({language: 'es'}),
364
- water: water({
365
- bathymetry: {
366
- maxZoom: 15,
367
- opacity: fixed(0.8, {reason: 'The selected regional source supplies detailed bands'}),
368
- },
369
- bathymetryContours: {maxZoom: 15},
370
- bathymetryLabels: {
371
- maxZoom: 15,
372
- text: {
373
- field: expr.concat(
374
- expr.toString(expr.abs(expr.get(field('bathymetryContourDepth')))),
375
- ' m',
376
- ),
377
- },
378
- },
379
- bathymetryLandforms: {maxZoom: 15},
380
- bathymetryWaterNames: {maxZoom: 15},
381
- }),
382
- },
383
- });
384
- ```
385
-
386
- `water.bathymetryContours` and `water.bathymetryLabels` now consume the declared lines and `depth`;
387
- labels default to line placement. Band fills still consume `min_depth` and `sort_key`.
388
- `water.bathymetryLandforms` and `water.bathymetryWaterNames` style the declared names; set
389
- `visible: false` to hide either. Without their declaration, those styles emit no name layers.
390
- Name styles may use `class` in expressions without changing the source classification.
391
-
392
- Mixed geometry can opt into `placementByGeometry` on either name style. Its resolved policy must
393
- include `point`, `line`, and `polygon`; `point` must be `'point'`, while the other two accept
394
- `'point'`, `'line'`, or `'line-center'`. The policy overrides scalar `placement`, including an
395
- inherited value. Without it, scalar placement and existing generated layers remain unchanged.
396
-
397
- For example, this map labels point features at their coordinates and retains line placement for
398
- lines and polygon boundaries:
399
-
400
- <!-- docs:check -->
401
-
402
- ```ts
403
- import {bathymetry, defineMap, labels, water} from '@tileflow/core';
404
- import {streets} from '@tileflow/maps';
405
-
406
- export default defineMap({
407
- id: 'marine-names',
408
- version: 1,
409
- extends: streets,
410
- marine: {
411
- bathymetry: bathymetry({
412
- vectorLayers: {landforms: {zoomRanges: [{minZoom: 0, maxZoom: 24}]}},
413
- }),
414
- nautical: false,
415
- },
416
- modules: {
417
- labels: labels({language: 'es'}),
418
- water: water({
419
- bathymetryLandforms: {
420
- placementByGeometry: {point: 'point', line: 'line', polygon: 'line'},
421
- },
422
- }),
423
- },
424
- });
425
- ```
426
-
427
- This example declares source availability; the selected source must actually contain those
428
- features. Equal placements share one geometry-filtered group per source range. A policy emits
429
- at most three groups per range (384 layers for one name family at the 128-range limit). Groups
430
- compile in `point`, `line`, `line-center` order below ordinary map labels; source priority orders
431
- features within each group. Module refinements inherit the policy and style every group.
432
- Render-stack refinements still address exact semantic targets; inspect each placement/range
433
- target when applying a narrower refinement.
434
- Normal collision still applies and can hide a name.
435
-
436
- Placement uses the received geometry: `point` places a line label at its first rendered vertex;
437
- it does not compute a stable line midpoint. Polygon `point` placement uses an interior position
438
- of the rendered polygon, while `line` follows its boundary. Core does not generate source anchors,
439
- merge identities or deduplicate names by text. A source can supply multiple stable point anchors
440
- with shared identity properties and distinct anchor properties; their extent, visibility and
441
- editorial policy belong to that source. Keep original source identities as properties rather than
442
- assuming a universal numeric `feature.id`.
443
-
444
- Water-module styles override product-level `bands`, `contours`, and `labels`. Existing custom
445
- `text.field` expressions remain authoritative: when refining a map from legacy polygons to native
446
- contours, use `expr.get(field('bathymetryContourDepth'))` for its depth. That auxiliary binding
447
- resolves to `depth` independently of the primary World schema; `bathymetryMinDepth` keeps its
448
- legacy band meaning. Raw `expr.get('depth')` is rejected by authoring validation.
449
- Increasing only `maxZoom` does not remove an opacity curve that fades out earlier. Extend ranges
450
- and opacity only where the selected source supports the intended detail.
451
-
452
- The V1 schema also reserves `bathymetry_coverage`; Core adds no coverage rendering by default.
453
- Derived contours and names provide geographic context, not certified navigation data.
454
-
455
- The DEM enables future client-side sampling and depth profiles, but Core intentionally exposes no
456
- profile or remote sampling API yet. Rendered relief must not be interpreted as navigation-safe
457
- depth data.
458
-
459
- Omitting `marine` preserves the transitional Tileflow World V1 bathymetry fallback. Declaring
460
- `marine: 'none'` suppresses that fallback, while a selected bathymetry sidecar always takes
461
- precedence. Compiled styles retain the singular `tileflow:data` metadata for existing consumers and
462
- add `tileflow:sources` plus `tileflow:sourceRequirements` for independent product validation.
463
-
464
- Detailed city datasets can bind `sidewalk`, `streetFurniture`, and `circularFeature` source layers
465
- through `openMapTiles({layers, fields})`. When present, `roads.sidewalks` owns source-backed
466
- pedestrian polygons, `roads.crossings` owns oriented crossing icons, and `roads.roundabouts` owns
467
- metric circular road rings. A `transportationOutline` binding names lines that trace the outer
468
- boundary of each road area; when bound, `roads.areas` outlines stroke those lines instead of the
469
- area polygons, so the holes of a plaza (tree pits, planting beds) are not stroked. These bindings
470
- are optional: a generic OpenMapTiles source remains
471
- valid and the compiler omits unsupported detail instead of inventing geometry. Crossing icons are
472
- explicit because the data contract cannot assume a sprite name. They require positive physical
473
- `markings` evidence, reject `crossing=no`, and require a numeric `direction` from 0 to 360 degrees
474
- (numbers and numeric strings are accepted). World derives missing directions only when the
475
- crossing has an unambiguous road axis; unresolved points on adjoining footways therefore remain
476
- hidden instead of receiving a default rotation. All three fields are semantic bindings, so custom
477
- schemas can remap them without weakening that rule.
478
-
479
- Metric road rings scale continuously with the map from zoom 15 through 24, including fractional
480
- zooms. Their casing is below surface road fills so approaching streets cover the outline at each
481
- junction. The ring fill stays below bridges and road markings.
482
-
483
- Road treatment `widthScale` scales both the stroke and an authored `gapWidth`, keeping hollow
484
- casings aligned with the carriageway at fractional zoom levels.
485
- When every dash treatment is continuous (`[1, 0]`), road layers omit the dash pattern so thin
486
- casings render as solid strokes. Actual construction and tunnel dashes remain patterned.
487
-
488
- Datasets may also publish the optional, decorative `canopy` layer: points inside urban parks and
489
- gardens, at most one per cell of a grid of about 20 m that measured tree canopy covers. Each point
490
- tells how much room a drawn grove of tree crowns has and how dense the canopy around it is. The
491
- points are not surveyed trees, are unrelated to the optional `tree` layer, and are not search or
492
- interaction targets. `openMapTiles()` and `tileflowWorldV1Schema()` bind the layer as `canopy` and
493
- its numeric fields as follows:
494
-
495
- - `canopyRank` (`rank`): a stable integer from 0 through 15. Lower ranks appear at lower zooms, and
496
- the rank can vary artwork.
497
- - `canopyClearanceAtZoom15` (`clearance_px_z15`): 4, 6, 8, 12, 16, or 24. This is the lower bound
498
- of the distance from the point to the nearest edge a crown must not cover, such as a park edge,
499
- water, a building, a plaza, a pitch, or a roadway, in pixels of a 512-pixel tile at zoom 15. A
500
- grove fits at zoom `z` when its drawn reach in screen pixels is at most
501
- `clearance_px_z15 × 2^(z − 15)`.
502
- - `canopyCoverPercent` (`cover_pct`): 3, 30, or 60, the lower bound of the share, in percent, of
503
- canopy taller than 3 m within 10 m of the point. The field is absent where no measurement exists;
504
- treat absence as a middle density.
505
-
506
- Features are MultiPoints, one per identical set of attributes. Native zoom 15 holds every point,
507
- zoom 14 holds the points whose rank is below 8 at the same positions, and clients overzoom zoom 15.
508
- `validateTileflowWorldV1Tilejson(...)` accepts a TileJSON without `canopy`; once declared, the layer
509
- must occur exactly once, declare native z14–z15, and advertise Number `rank`, `clearance_px_z15`, and
510
- `cover_pct`. No module draws the layer. A source without it remains valid, and render passes bound
511
- to it draw nothing. `capabilities: {canopy: false}` removes the binding, after which a pass with
512
- `feature: 'canopy'` fails with a missing-binding error.
513
-
514
- Draw groves with symbol render passes, one grove of whole crowns per point, sized to its room:
515
-
516
- <!-- docs:check -->
517
-
518
- ```ts
519
- import {expr, field, fixed, renderPass, token, vegetation, withRenderStack} from '@tileflow/core';
520
-
521
- // The grove artwork reaches 32 pixels from its center at icon size 1.
522
- const clearance = expr.get(field('canopyClearanceAtZoom15'));
523
- // The size that fills the room at one zoom, never larger than the artwork itself.
524
- const fit = (divisor: number) => expr.min(expr.divide(clearance, divisor), 1);
525
-
526
- const groves = (minZoom: number, fromRank: number, toRank: number) =>
527
- renderPass({
528
- placement: 'above-vegetation',
529
- feature: 'canopy',
530
- renderer: 'symbol',
531
- selector: {
532
- kind: 'all',
533
- selectors: [
534
- {field: 'canopyRank', kind: 'compare', operator: 'gte', value: fromRank},
535
- {field: 'canopyRank', kind: 'compare', operator: 'lt', value: toRank},
536
- ],
537
- },
538
- style: {
539
- minZoom,
540
- icon: {
541
- ignorePlacement: true,
542
- image: token.image('vegetation.canopy.grove'),
543
- overlap: 'always',
544
- size: expr.interpolate({base: 2, kind: 'exponential'}, expr.zoom(), [
545
- [14, fit(64)],
546
- [15, fit(32)],
547
- [16, fit(16)],
548
- [17, fit(8)],
549
- [18, fit(4)],
550
- ]),
551
- },
552
- text: {
553
- field: 'o',
554
- font: token.font('default'),
555
- opacity: fixed(0, {reason: 'The core text only decides placement'}),
556
- overlap: 'cooperative',
557
- size: fixed(8, {reason: 'Matches the solid core of the grove artwork'}),
558
- },
559
- },
560
- });
561
-
562
- export const canopyGroves = withRenderStack(vegetation(), {
563
- groves14: groves(14, 0, 4),
564
- groves15: groves(15, 4, 8),
565
- groves16: groves(16, 8, 16),
566
- });
567
- ```
568
-
569
- - Give each rank range its own pass with `minZoom`, so its points appear whole at their entry zoom.
570
- MapLibre evaluates expressions that combine zoom and feature data at whole zoom levels and
571
- interpolates between them, so a fade per feature would leave symbols half transparent. Zoom 14
572
- tiles carry the ranks below 8, so the first pass may cover ranks 0–7.
573
- - Size each grove from `canopyClearanceAtZoom15`. Here the size is `clearance / 32` at zoom 15 and
574
- doubles with each zoom until the grove reaches the artwork's size, which keeps the reach within
575
- the room.
576
- - Collision tests an icon's whole image, including the transparent margin around the crowns. A small
577
- invisible text the size of the grove's core therefore decides placement with
578
- `overlap: 'cooperative'`, while the icon draws with `overlap: 'always'` and `ignorePlacement: true`.
579
- Groves may overlap each other but give way to labels, which draw above the vegetation band and are
580
- therefore placed first, with the default `never`.
581
-
582
- Every theme of the map must define the image token, and the map's icon directories must provide the
583
- artwork. Native artifacts replace `cooperative` with `allowOverlap: true`, so there groves can cover
584
- labels; see [symbol overlap modes](shared-visual-primitives.md#symbol-overlap-modes).
585
-
586
- The optional `business_corridor` extension contains activity-selected source footprints below
587
- buildings. Its `activity_score`, `rank`, `min_zoom`, and `confidence` fields control a quiet local
588
- warm tint without drawing POI-radius circles. Current building footprints may carry sparse
589
- `building_tone=commercial|destination|active`; absence means the neutral base building. The three
590
- values preserve why the warm tone was selected even when a theme maps them to the same color.
591
- Buildings may also carry World's `class`, their use in Overture's building subtype values; without
592
- a tone, the buildings module colours `residential`, `commercial`, public buildings (`civic`,
593
- `education`, `medical`, `religious` and the like) and works (`industrial`, `agricultural`,
594
- `service`) with the theme's `residential`, `commercial`, `civic` and `industrial` building colours,
595
- and every other building with `generic`. Building visibility is fixed by the layer zoom contract and never by optional semantics. The land
596
- module also exposes
597
- `medical`, `education`, and `government` independently instead of collapsing their already distinct
598
- source classes into one civic color.
599
-
600
- World and text assets are independent contracts. `tileflowWorld()` selects `world-v1/current` or an
601
- exact `releaseId + descriptorSha256`; a `glyphs` declaration contains its own complete URL. Every
602
- official map compiles as imported because it owns or inherits a URL or packaged-font provider.
603
- URL-backed maps become exact-byte reproducible
604
- when the immutable global base-asset set is published and their explicit URL is updated to its
605
- `assetSetSha256` path; no World or compiler fallback participates in that rollout. The global
606
- base-asset manifest's hash and a map build manifest's identically named `assetSetSha256` use
607
- different contracts and must never be substituted for one another.
608
-
609
- Upstream data attribution remains in the MapLibre source. `Map by Tileflow` is a separate product
610
- credit/trademark surface and must not replace, obscure, or be presented as upstream attribution.
@@ -1,65 +0,0 @@
1
- # Depth markers
2
-
3
- DOM markers float above the map: in a 3D scene a pin behind a tower still shows in front of it.
4
- `createTileflowDepthMarkers` draws markers in the map's 3D scene instead. A MapLibre custom layer
5
- draws them against the depth the map's extrusions write, so nearer buildings hide them as the camera
6
- moves.
7
-
8
- ## Add markers
9
-
10
- This example assumes an existing MapLibre map named `map` with 3D buildings, and two pictures drawn
11
- by the application:
12
-
13
- <!-- docs:check -->
14
-
15
- ```ts
16
- import {createTileflowDepthMarkers, type TileflowDepthMarkerMap} from '@tileflow/core/browser';
17
-
18
- export function pinDestination(
19
- map: TileflowDepthMarkerMap,
20
- at: [number, number],
21
- pin: HTMLCanvasElement,
22
- ring: HTMLCanvasElement,
23
- ) {
24
- const markers = createTileflowDepthMarkers(map);
25
- // A pin facing the viewer, 28 × 40 CSS pixels, standing on the position.
26
- markers.add({lngLat: at, image: pin, size: [28, 40], anchor: 'bottom'});
27
- // A ring lying on the ground, 60 metres across.
28
- const halo = markers.add({lngLat: at, image: ring, size: [60, 60], alignment: 'ground'});
29
- return {
30
- pulse: (opacity: number) => halo.update({opacity}),
31
- dispose: () => markers.dispose(),
32
- };
33
- }
34
- ```
35
-
36
- A marker has:
37
-
38
- - **Position.** `lngLat`, and `altitude` in metres above the ground.
39
- - **Picture.** `image`: an image, an image bitmap, a canvas or image data. After drawing into a
40
- canvas again, pass it to `update({image})` so the marker uploads it once more. Markers that share
41
- a picture share its upload.
42
- - **Alignment.** `screen` (the default) faces the viewer at `size` in CSS pixels, like a pin.
43
- `ground` lies flat on the ground at `size` in metres, in the camera's perspective, like a ring or
44
- an arrow painted on the street.
45
- - **Anchor and placement.** `anchor` (`center` or `bottom`), `offset` in CSS pixels for screen
46
- markers, and `rotation` in degrees clockwise: from the screen's up for screen markers, from north
47
- for ground markers.
48
- - **Opacity.** From 0 to 1.
49
- - **`occlude`.** Defaults to true: nearer 3D content hides the marker. Set it to false for a marker
50
- that must always show.
51
-
52
- Markers test against the scene's depth without writing to it, so they never hide one another. A
53
- screen marker takes the depth of its position: a tower in front of the position hides the whole
54
- pin, a tower in front of part of it hides that part.
55
-
56
- ## Layer and lifecycle
57
-
58
- The markers share one custom layer, `tileflow-depth-markers` unless `id` names another, added with
59
- the first marker. `beforeId` places it under a layer, given as an id or as a function that finds the
60
- layer again after a style change. After a style change the layer puts itself back, and pictures
61
- are uploaded again. `dispose()` removes the layer and releases its GPU resources.
62
-
63
- Depth markers need MapLibre GL JS on WebGL 2 (GL JS 5 or later). They work on globe and Mercator
64
- projections. They draw pictures only: draw text and effects into the picture, or keep DOM markers
65
- for interactive content.