@tileflow/core 0.1.0-alpha.42 → 0.1.0-alpha.44

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 (50) hide show
  1. package/README.md +56 -69
  2. package/THIRD_PARTY_NOTICES.md +3 -3
  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 +425 -50
  6. package/dist/browser.d.ts +95 -5
  7. package/dist/browser.js +866 -101
  8. package/dist/build.d.ts +8 -4
  9. package/dist/build.js +250 -3
  10. package/dist/config-reference.json +3395 -2140
  11. package/dist/{icon-composition-B1A2udKb.d.ts → icon-composition-Bbw9umUA.d.ts} +37 -2
  12. package/dist/index.d.ts +13 -4
  13. package/dist/index.js +193 -3
  14. package/dist/{manifest-types-B0lQjabX.d.ts → manifest-types-CwjHd4qc.d.ts} +8 -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.js +789 -462
  19. package/dist/runtime.d.ts +1 -1
  20. package/dist/worker/camera-tile-worker.global.js +2 -2
  21. package/package.json +5 -6
  22. package/docs/application-tile-reading.md +0 -86
  23. package/docs/authoring-model.md +0 -269
  24. package/docs/boundary-areas-and-choropleths.md +0 -230
  25. package/docs/buildings-3d.md +0 -624
  26. package/docs/camera-moves-and-tile-cache.md +0 -112
  27. package/docs/capture-scenes.md +0 -40
  28. package/docs/capture-shots.md +0 -173
  29. package/docs/cartographic-defaults.md +0 -214
  30. package/docs/compiled-style-performance.md +0 -286
  31. package/docs/data-is-separate-from-design.md +0 -610
  32. package/docs/depth-markers.md +0 -65
  33. package/docs/dynamic-style-images.md +0 -100
  34. package/docs/globe-atmosphere.md +0 -113
  35. package/docs/hosted-session-authorization.md +0 -106
  36. package/docs/map-and-terrain-examples.md +0 -156
  37. package/docs/map-emphasis.md +0 -89
  38. package/docs/native-artifact-profile.md +0 -152
  39. package/docs/native-resource-urls.md +0 -369
  40. package/docs/nautical-marks.md +0 -308
  41. package/docs/public-api-and-browser-subpath.md +0 -101
  42. package/docs/route-drawing.md +0 -191
  43. package/docs/shared-visual-primitives.md +0 -101
  44. package/docs/street-lights.md +0 -127
  45. package/docs/street-sky.md +0 -156
  46. package/docs/style-states.md +0 -73
  47. package/docs/sun-and-light.md +0 -167
  48. package/docs/theme-transitions-and-blends.md +0 -317
  49. package/docs/themes-and-module-styles.md +0 -156
  50. package/docs/trees-3d.md +0 -295
@@ -1,112 +0,0 @@
1
- # Camera moves and the tile cache
2
-
3
- A long camera move outruns the network: the new view's tiles take longer to arrive than the move
4
- takes. Some examples are a flight from a street to a region and back, a guided tour, or a scripted
5
- opening. MapLibre keeps the tiles it no longer shows in an out-of-view cache, so a camera that
6
- comes back finds them ready. This guide covers what keeps that cache useful and what empties it,
7
- as MapLibre GL JS 6 behaves.
8
-
9
- ## What the cache keeps
10
-
11
- The out-of-view cache holds about five views' worth of tiles per source:
12
-
13
- - **Size.** It is the tiles across the map times `maxTileCacheZoomLevels`, 5 by default.
14
- `maxTileCacheSize` caps it.
15
- - **Too small for a flight.** A move that crosses many zooms (a street, the region, the street
16
- again) needs more. Raise `maxTileCacheZoomLevels` on the map, for example to 12, so tiles
17
- loaded early in a sequence are still there at its end.
18
- - **Exact tiles only.** While a tile the camera needs is loading, MapLibre falls back on its loaded
19
- parents or children only among the tiles it shows. It does not fall back on tiles it keeps out
20
- of view. A cached tile helps only when it is the very tile a frame needs.
21
-
22
- ## What empties it
23
-
24
- Changes that make MapLibre lay a source's tiles out again also empty that source's out-of-view
25
- cache. The tiles in view are parsed again; the cached ones are dropped and come back from the
26
- network when the camera returns.
27
-
28
- **Changes that lay tiles out:**
29
-
30
- - Any layout property, `visibility` included, through `setLayoutProperty`.
31
- - A filter, through `setFilter`.
32
- - A new value, through `setPaintProperty`, for a paint property whose value reads feature data,
33
- such as `['get', ...]` or `['feature-state', ...]`.
34
- - `setStyle` with layers that differ. A style that is not diffed starts every source afresh.
35
- - In a theme blend, a change of the nearest theme over values like these. `setBlend({hold: true})`
36
- keeps them back during a move; see
37
- [theme transitions and blends](https://github.com/tileflow/tileflow-sdk/blob/main/packages/core/docs/theme-transitions-and-blends.md).
38
-
39
- **Changes that keep the cache:**
40
-
41
- - A constant paint value, or one that reads only the zoom.
42
- - `setFeatureState`, which rewrites the paint of the features it names.
43
- - Global state read by values that read no feature data.
44
-
45
- During a camera move, change only what keeps the cache. Make layout changes, filters and theme
46
- crossings before the move starts or after it ends. To mark features during a move, prefer feature
47
- state over a filter.
48
-
49
- ## Warm cameras before a move
50
-
51
- MapLibre GL JS 6 has no public way to load a camera's tiles without showing it. Until it does,
52
- warm cameras while something covers the map, such as a title card or a loading screen. Visit each
53
- camera with `jumpTo`, wait until its tiles are in, then go on to the next:
54
-
55
- <!-- docs:check -->
56
-
57
- ```ts
58
- type WarmableMap = {
59
- jumpTo(options: {
60
- center: [number, number];
61
- zoom: number;
62
- bearing?: number;
63
- pitch?: number;
64
- }): unknown;
65
- loaded(): boolean;
66
- on(event: 'render', listener: () => void): unknown;
67
- off(event: 'render', listener: () => void): unknown;
68
- triggerRepaint(): void;
69
- };
70
-
71
- type Camera = {center: [number, number]; zoom: number; bearing?: number; pitch?: number};
72
-
73
- /** Resolves once the tiles of the camera on screen are in, or false after `ms`. */
74
- function tilesIn(map: WarmableMap, ms: number): Promise<boolean> {
75
- return new Promise((resolve) => {
76
- const check = () => {
77
- if (map.loaded()) done(true);
78
- };
79
- const done = (ok: boolean) => {
80
- clearTimeout(timer);
81
- map.off('render', check);
82
- resolve(ok);
83
- };
84
- const timer = setTimeout(() => done(false), ms);
85
- map.on('render', check);
86
- map.triggerRepaint();
87
- });
88
- }
89
-
90
- export async function warmCameras(map: WarmableMap, cameras: readonly Camera[], rest: Camera) {
91
- for (const camera of cameras) {
92
- map.jumpTo(camera);
93
- await tilesIn(map, 1500);
94
- }
95
- map.jumpTo(rest);
96
- }
97
- ```
98
-
99
- Four details matter:
100
-
101
- - **Check after a frame.** `map.loaded()` can read false on the frame in which the last tile
102
- arrived. So check it after a frame, on `render`, not once right after `jumpTo`.
103
- - **Do not wait for `idle`.** `idle` also waits for labels to fade in, which adds time and loads
104
- nothing.
105
- - **Visit every zoom the move crosses.** A cached tile helps only when it is the exact tile a frame
106
- needs, so warm each zoom, not just the destination.
107
- - **Make cache-emptying changes first.** Make the changes listed above before the first visit.
108
- Otherwise the warm-up is lost.
109
-
110
- Mapbox GL JS offers `preloadOnly` on `jumpTo`, `easeTo` and `flyTo`, which loads a move's tiles
111
- without showing it. A MapLibre release with an equivalent would replace the visits above with one
112
- call per move.
@@ -1,40 +0,0 @@
1
- # Capture scenes
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
- Commit named `scenes` on the singular exported map. A scene implicitly targets that map, so it does
6
- not repeat a `map` selector. Scene metadata does not inherit and is removed before cartographic
7
- compilation; it affects visual evidence, not style or manifest identity.
8
-
9
- ```ts
10
- import {defineMap} from '@tileflow/core';
11
- import {streets} from '@tileflow/maps';
12
-
13
- export default defineMap({
14
- id: 'madrid',
15
- name: 'Madrid',
16
- version: 1,
17
- extends: streets,
18
- scenes: {
19
- 'madrid-desktop': {
20
- theme: 'dark',
21
- camera: {type: 'center', center: [-3.7038, 40.4168], zoom: 12},
22
- viewport: {width: 1280, height: 800, dpr: 1},
23
- },
24
- },
25
- });
26
- ```
27
-
28
- Use `tileflow preview --scene madrid-desktop` for live review and
29
- `tileflow capture madrid-desktop` for exact pixels and a schema-version-4 receipt containing the
30
- concrete theme plus Streets, data, style, renderer, and image identities. World capture resolves the selected TileJSON
31
- once and records the exact `world-v1` release plus descriptor/archive/data-contract hashes;
32
- external fixtures may identify their explicit revision.
33
-
34
- Every exact World V1 release ID is already canonical at the producer boundary: 12–128 characters
35
- matching `^world-v1-[a-z0-9][a-z0-9._-]*[a-z0-9]$`. Core validates the literal value and never trims,
36
- lowercases, or upgrades it to another World generation.
37
-
38
- To record a timed camera move instead of one image, commit a
39
- [capture shot](https://github.com/tileflow/tileflow-sdk/blob/main/packages/core/docs/capture-shots.md)
40
- and render it with `tileflow capture --shot <name>`.
@@ -1,173 +0,0 @@
1
- # Capture shots
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
- A shot is a short, timed camera move committed on the singular exported map. Capture renders it
6
- frame by frame into a WebM video or a directory of numbered PNG frames. Like
7
- [capture scenes](https://github.com/tileflow/tileflow-sdk/blob/main/packages/core/docs/capture-scenes.md),
8
- shots implicitly target their map, do not inherit, and are removed before cartographic compilation;
9
- they affect visual evidence, not style or manifest identity.
10
-
11
- ## Commit a shot
12
-
13
- This shot moves from central Madrid toward the east over four seconds while the map passes from
14
- the `light` theme to the `dark` theme:
15
-
16
- <!-- docs:check -->
17
-
18
- ```ts
19
- import {defineMap} from '@tileflow/core';
20
- import {streets} from '@tileflow/maps';
21
-
22
- export default defineMap({
23
- id: 'madrid',
24
- version: 1,
25
- extends: streets,
26
- shots: {
27
- 'dusk-flyover': {
28
- theme: 'light',
29
- viewport: {width: 1280, height: 720, dpr: 1},
30
- fps: 30,
31
- keyframes: [
32
- {
33
- at: 0,
34
- camera: {type: 'center', center: [-3.7038, 40.4168], zoom: 12},
35
- blend: {themes: ['light', 'dark'], position: 0},
36
- },
37
- {
38
- at: 4000,
39
- camera: {type: 'center', center: [-3.68, 40.42], zoom: 14, bearing: 30, pitch: 45},
40
- blend: {themes: ['light', 'dark'], position: 1},
41
- },
42
- ],
43
- },
44
- },
45
- });
46
- ```
47
-
48
- A shot has these fields:
49
-
50
- - `theme`: a concrete theme of the map; `system` is invalid. The map loads this theme first, and a
51
- shot without a blend shows it throughout.
52
- - `viewport`: CSS pixels and a `dpr` of 1 or 2, with the same limits as a scene viewport.
53
- - `fps`: 24, 25, 30, 50, or 60 frames per second. The default is 30.
54
- - `easing`: `smooth` (the default) eases into and out of each keyframe; `linear` moves at a constant
55
- rate between keyframes.
56
- - `keyframes`: 2 to 64 entries. `at` is the whole number of milliseconds from the start of the
57
- shot. The first keyframe is at 0, every later keyframe is later than the one before it, and the
58
- last is at most 60000.
59
-
60
- Keyframe cameras use the scene `center` form: `[longitude, latitude]`, zoom, and optional bearing
61
- and pitch. The `bounds` form is not accepted, because the camera that fits bounds depends on the
62
- renderer's viewport and projection, and a shot interpolates between exact cameras.
63
-
64
- ## Timing and interpolation
65
-
66
- A shot renders one frame at the start and one every `1 / fps` seconds up to its last keyframe:
67
- `floor(lastKeyframe.at × fps / 1000) + 1` frames, at most 3600. Frame `i` shows the shot at
68
- `i × 1000 / fps` milliseconds, so the last keyframe is itself a frame when its time is a whole
69
- number of frame intervals, as 4000 ms is at 30 frames per second.
70
-
71
- Between two keyframes the center moves in Web Mercator coordinates, along the shorter way around
72
- the world when it crosses the antimeridian. Zoom and pitch change linearly, and bearing turns along
73
- the shorter arc. Easing applies to every value in the same way. A frame exactly at a keyframe shows
74
- that keyframe's camera.
75
-
76
- `tileflowCaptureShotFrames` returns the camera and blend position of every frame without rendering
77
- anything, so tools can inspect a shot before capture:
78
-
79
- <!-- docs:check -->
80
-
81
- ```ts
82
- import {tileflowCaptureShotFrames, type TileflowMapShot} from '@tileflow/core';
83
-
84
- const shot = {
85
- theme: 'light',
86
- viewport: {width: 640, height: 360},
87
- keyframes: [
88
- {at: 0, camera: {type: 'center', center: [-3.7038, 40.4168], zoom: 12}},
89
- {at: 1000, camera: {type: 'center', center: [-3.68, 40.42], zoom: 13}},
90
- ],
91
- } satisfies TileflowMapShot;
92
-
93
- const frames = tileflowCaptureShotFrames(shot);
94
- console.log(frames.length); // 31
95
- console.log(frames[15]?.camera.zoom); // 12.5
96
- ```
97
-
98
- ## Blend themes along a shot
99
-
100
- Add `blend` to every keyframe, or to none. Each blend lists the same two to eight concrete themes in
101
- the same order, and a `position` from 0 to one less than the number of themes, as in a
102
- [theme blend](https://github.com/tileflow/tileflow-sdk/blob/main/packages/core/docs/theme-transitions-and-blends.md).
103
- The position moves between keyframes with the same easing as the camera.
104
-
105
- Capture prepares the blend once with Core's theme controller and moves its position on every frame.
106
- The controller applies a new position once it differs by at least a four-hundredth of a theme, so a
107
- very slow blend changes the ground colours every few frames. Labels and icons change at the nearest
108
- theme, without a cross-fade, because capture renders with reduced motion.
109
-
110
- ## Render a shot
111
-
112
- Render committed shots with the CLI. The first command writes
113
- `.tileflow/captures/shots/dusk-flyover.webm` and its receipt; the second writes numbered PNG frames
114
- instead:
115
-
116
- ```sh
117
- npx tileflow capture --shot dusk-flyover --json
118
- npx tileflow capture --shot dusk-flyover --format frames --json
119
- ```
120
-
121
- Each frame waits until MapLibre has loaded the tiles and resources it needs, so a slow machine or
122
- network makes the render take longer but never drops or repeats frames. See the
123
- [capture guide](https://github.com/tileflow/tileflow-sdk/blob/main/packages/capture/README.md) for
124
- the Node API and the
125
- [capture contract](https://github.com/tileflow/tileflow-sdk/blob/main/docs/contracts/local-visual-capture.md)
126
- for output paths, encoding, and the shot receipt.
127
-
128
- ## Play a shot in the browser
129
-
130
- `playTileflowShot(map, shot, options)` from `@tileflow/core/browser` plays the same shot live. The
131
- camera follows the keyframes frame by frame, with the same interpolation Capture renders, so the
132
- page and the video show the same film. This example assumes an existing MapLibre map named `map`,
133
- a theme controller, and the runtime styles of the blended themes:
134
-
135
- <!-- docs:check -->
136
-
137
- ```ts
138
- import {
139
- playTileflowShot,
140
- type TileflowShotMotion,
141
- type TileflowShotPlayerMap,
142
- type TileflowThemeController,
143
- } from '@tileflow/core/browser';
144
- import type {TileflowRuntimeStyle} from '@tileflow/core/runtime';
145
-
146
- export function playDuskFlyover(
147
- map: TileflowShotPlayerMap,
148
- controller: TileflowThemeController,
149
- themes: readonly TileflowRuntimeStyle[],
150
- shot: TileflowShotMotion,
151
- showTitle: () => void,
152
- ) {
153
- return playTileflowShot(map, shot, {
154
- blend: (position) => void controller.setBlend({themes, position}),
155
- cues: [{at: 2500, run: showTitle}],
156
- });
157
- }
158
- ```
159
-
160
- - **Blend.** `blend` receives the blend position at each frame of a shot that blends themes.
161
- - **Cues.** `cues` run once each, in time order, as the shot reaches them, for application events
162
- such as drawing a route or showing a title.
163
- - **Stopping.** `stop()` or an aborted `signal` stops the shot where it is; `finished` resolves
164
- `true` once the shot reaches its last keyframe and `false` when it was stopped first.
165
- - **Reduced motion.** With reduced motion requested, the shot shows its last keyframe at once and
166
- runs every cue in order.
167
-
168
- `tileflowShotStateAt(shot, at)` returns the camera and blend position at any time, for players of
169
- one's own.
170
-
171
- Live playback runs at the browser's pace and does not wait for tiles, unlike a render. Load the
172
- tiles of the cameras a shot crosses before playing it; see
173
- [camera moves and the tile cache](https://github.com/tileflow/tileflow-sdk/blob/main/packages/core/docs/camera-moves-and-tile-cache.md).
@@ -1,214 +0,0 @@
1
- # Cartographic defaults
2
-
3
- Start with the [Core guide](https://github.com/tileflow/tileflow-sdk/blob/main/packages/core/README.md)
4
- for installation and a complete map. Semantic modules supply a coordinated starting appearance;
5
- module styles and owner-local render targets can replace those decisions.
6
-
7
- ## Roads and roundabouts
8
-
9
- `roads({rampOrder: 'below-roads'})` draws ramp fills before receiving carriageways in the same
10
- surface, bridge or tunnel band. This prevents a rounded ramp endpoint covering the receiving
11
- road. Choose `'by-class'` to retain class-based fill order instead. Explicit render-stack moves
12
- remain available; ramp fills have targets `roads.classes.<class>.<structure>.rampFill` when the
13
- separate ramp order is active. A selector refinement replaces that target's complete filter, so
14
- retain its ramp eligibility when writing a custom selector. Module class fill styles apply to
15
- both passes; a refinement of a specific `fill` or `rampFill` target changes only that pass.
16
-
17
- Core owns circular-road geometry. Physical inner and outer radii take precedence when provided
18
- by the source. Otherwise the ring follows the enabled class's effective surface width, including
19
- width treatments. The default border follows the actual casing width and gap. Hidden classes and
20
- surface fills do not leave orphan rings. Ring colors remain semantic palette choices, independently
21
- replaceable through `roundabouts.fill.strokeColor` and `roundabouts.casing.strokeColor`; they do
22
- not copy arbitrary per-class color curves.
23
-
24
- Metric scaling and base-2 width curves retain their native interpolation. Other width curves are
25
- sampled into the shared geometry curve: numeric widths target 0.1-pixel error, stopping refinement
26
- once intervals reach 1/32 zoom; very large widths can exceed that target. Feature-dependent width
27
- expressions retain authored and integer zoom stops without adaptive refinement, keeping expression
28
- size bounded. Their interpolation between stops is approximate, with no universal pixel-error bound.
29
- Step changes complete at the authored zoom, with the transition confined to the preceding 0.000001
30
- zoom interval. Explicit circle radius and stroke-width expressions bypass this sampling when exact
31
- independent behavior is required.
32
-
33
- `roundabouts.fallbackWidth` replaces only the fallback carriageway width in pixels;
34
- `roundabouts.borderWidth` replaces the total extra width around the ring. Existing circle styles
35
- can independently replace radius, stroke width, color, opacity and zoom range. For example, this
36
- module fragment retains geometric defaults but gives rings a deliberate fixed border:
37
-
38
- <!-- docs:check -->
39
-
40
- ```ts
41
- import {roads} from '@tileflow/core';
42
-
43
- export const customRoads = roads({
44
- rampOrder: 'by-class',
45
- roundabouts: {borderWidth: 0},
46
- });
47
- ```
48
-
49
- ## Ground surfaces
50
-
51
- The land module draws broad cover and park surfaces before mapped grass, meadow, scrub and
52
- flowerbed details. Parking, sports pitches, tracks, playgrounds and zoos follow those details so
53
- they remain visible inside broad green polygons. Both Tileflow World and OpenMapTiles' combined
54
- park source follow this order. These defaults do not change the map's colors, opacity or textures.
55
-
56
- A thematic map can give a park tint more prominence through the existing render stack. This
57
- module fragment is ready to use as `modules.land` in a map:
58
-
59
- <!-- docs:check -->
60
-
61
- ```ts
62
- import {land, refineRenderTarget, withRenderStack} from '@tileflow/core';
63
-
64
- export const thematicLand = withRenderStack(land(), {
65
- parkTint: refineRenderTarget({
66
- target: 'land.landcover.urbanPark.fill',
67
- placement: 'above-land',
68
- }),
69
- });
70
- ```
71
-
72
- Refinements target contributions that the map actually draws. Disabling a target and retaining
73
- a refinement for it is an authoring error. Disabling the whole module removes its contributions
74
- and its render stack. The built-in surface order needs no target anchors and works when individual
75
- surfaces are hidden.
76
-
77
- ## Street names on the ground or in the foreground
78
-
79
- `labels({roadPlacement: 'foreground'})` is the default. Names draw above buildings and vegetation.
80
- Use `roadPlacement: 'ground'` to draw them above flat building footprints and below raised
81
- buildings, with text aligned to the map surface. Flat footprints must not clip the glyphs.
82
- Shields and junction numbers remain in the foreground. Ground placement does not require the
83
- buildings module: street names still draw when buildings are disabled.
84
-
85
- This module fragment keeps the normal language selection and road-class filters while opting
86
- into ground names. Individual symbol styles can override the default text alignment:
87
-
88
- <!-- docs:check -->
89
-
90
- ```ts
91
- import {labels} from '@tileflow/core';
92
-
93
- export const groundLabels = labels({
94
- roadPlacement: 'ground',
95
- collisionPriority: 'navigation',
96
- language: 'local',
97
- roads: 'all',
98
- styles: {
99
- roads: {
100
- primary: {text: {pitchAlignment: 'viewport'}},
101
- },
102
- },
103
- });
104
- ```
105
-
106
- MapLibre offers foreground symbols first when resolving collisions. Ground names therefore yield
107
- to POIs and other foreground symbols, even with `collisionPriority: 'navigation'`. Navigation
108
- priority still orders the ground road classes relative to one another; it does not make their
109
- collision priority independent of drawing order. Foreground placement retains the existing
110
- navigation priority over ordinary POIs. Neither mode turns collision detection off.
111
-
112
- When compatible road-name layers share a physical bucket, their top-to-bottom collision priority
113
- and each class's authored feature priority are retained. Overlap-enabled names retain drawing order.
114
-
115
- `labels({roadGrouping: 'network'})` instead shares placement and repeated-name suppression across
116
- compatible classes, in source feature order (or their common authored priority). It retains each
117
- class's zoom thresholds and size curve. Different fonts, spacing, colors or priorities stay separate,
118
- as do explicit ordered passes and fractional zoom thresholds. Streets uses this for its ordinary
119
- roads and a separate pedestrian/path group. The default `'class'` retains road-class priority.
120
-
121
- Use a refinement on `labels.roads.<class>` to move an individual class after this default has been
122
- applied. Symbol size, zoom range, spacing, priority, fonts and halos remain available in
123
- `styles.roads`. The same names, language fallbacks and class selectors are used in both modes.
124
-
125
- ## Compact road names
126
-
127
- `labels({roadNameFormat: 'compact'})` abbreviates recognized Spanish and French road-type
128
- prefixes and English suffixes after language selection: `Calle de Alcalá` becomes `C. de Alcalá`,
129
- `Avenue Paul Doumer` becomes `Av. Paul Doumer`, and `West 34th Street` becomes `W 34th St`.
130
- The default, `full`, preserves source names. Proper names, unknown types and route shields stay
131
- unchanged. An explicit `styles.roads.<class>.text.field` takes precedence over this formatting.
132
-
133
- ## Label density and perspective
134
-
135
- The SDK starts with local street names (`labels.roads: 'streets'`), POI density 2 and a
136
- 3-pixel text collision margin. Street names follow enabled road geometry and their zoom thresholds;
137
- set `roads: 'major'` to retain a map with only arterial names, and `density: 3` for more POIs.
138
-
139
- `poi({density: 2, densityByCategory: {landmark: 3, lodging: 1}})` changes category eligibility
140
- while retaining one importance order across categories. Unspecified categories inherit `density`.
141
- Each value is 1–5; eligible rank increases by one at z16 and two at `detailZoom` (default 17;
142
- Streets uses 18). `detailZoom` must exceed 16 and cannot exceed 24. Source rank meanings
143
- and category coverage differ between datasets, so equal settings need not produce equal counts.
144
-
145
- `poi({maxRankByType: {garden: 1}})` adds a fixed rank ceiling for selected World POI types.
146
- Values are 0–5; unspecified types retain their usual eligibility. The ceiling applies to markers,
147
- icons and labels in both placement orders and does not increase with zoom or category density.
148
-
149
- MapLibre owns perspective scaling, distant-symbol rejection, collision placement and fading.
150
- These native behaviors apply without a module switch. The SDK does not change feature filters
151
- when panning, rotating, tilting or ending a gesture. Category and zoom eligibility remain compiled
152
- into the style; explicit application filter edits remain application-owned. The experimental
153
- `perspective` module option and `attachTileflowLabelPerspective` helper have been removed.
154
-
155
- Default POIs place their pictogram and name together, with four alternative text anchors and
156
- native collision detection. Parking and public toilets retain their icon-only representation.
157
- Set `placement.coupleIconAndLabel: false` to place pictograms and names independently. Maps refining
158
- separate `poi.icon` or `poi.label` targets should set this explicitly; coupled POIs use `poi`.
159
- Explicit anchors, offsets and variable anchors replace the SDK's automatic text clearance.
160
-
161
- Balanced foreground priority offers place names and route references first, followed by major
162
- road names, ordinary POIs, minor road names and house numbers. `collisionPriority: 'navigation'`
163
- also promotes minor road names above ordinary POIs. `placement.prominentRank` can promote selected
164
- POI ranks above the orientation labels. Eligibility still depends on zoom, enabled classes and
165
- source data. Feature priority sorts within a layer; it does not override the order between layers.
166
- Explicit render-stack placements take precedence over these defaults.
167
-
168
- With `roadPlacement: 'ground'`, the browser keeps road names above the 3D scene below 30° pitch,
169
- so roofs cannot cut letters in narrow streets viewed from above. At 30° and above, names return
170
- below solid raised geometry. Tileflow trees mount below these names so foliage cannot cut
171
- individual letters. This policy follows `roadPlacement` alone and does not affect camera tilt.
172
- Explicit render-stack moves opt a target out of automatic ordering; explicit tree `beforeId`
173
- placement remains application-owned.
174
-
175
- React, Vue, Svelte, preview and standalone capture attach the shared placement controller.
176
- Applications constructing MapLibre maps directly call `attachTileflowLabelPlacement(map)` from
177
- `@tileflow/core/browser` once after map construction. The returned function detaches its listeners
178
- and restores ground ordering; map removal detaches without further map writes. Replacement styles
179
- and late-mounted 3D layers are discovered automatically. Static style JSON and native renderers
180
- retain compiled eligibility and hierarchy, but do not execute this browser scene-order controller.
181
-
182
- Native perspective rejection is not a configurable per-category distance curve. The SDK does not
183
- emulate Mapbox's `pitch` or `distance-from-center` symbol filters, which the supported MapLibre
184
- style specification does not provide. Runtime fade duration and cross-source collisions continue
185
- to use MapLibre defaults unless overridden through the adapter's `mapOptions`.
186
-
187
- ## Discover and override decisions
188
-
189
- `tileflow describe labels --json` exposes the installed options, and `tileflow inspect --json`
190
- shows resolved inheritance and merge provenance. `tileflow explain --inspection --json` adds
191
- compiled semantic targets and physical provenance. These commands describe the authored map;
192
- they do not rewrite explicit style choices.
193
-
194
- `tileflow describe themes --json` lists the twelve required base color roles and composition
195
- rules. A [single independent theme](https://github.com/tileflow/tileflow-sdk/blob/main/packages/core/docs/themes-and-module-styles.md)
196
- needs no time-of-day catalog. Optional color roles derive from that theme and accept explicit
197
- replacements. Removing inherited lighting uses `reset()` inside `defineTheme`; it restores the
198
- SDK fallback, while building shadows and occlusion have separate on/off controls.
199
-
200
- `createStyleResult()` and `tileflow inspect compiled --json` include `report.decisions`: actual
201
- color roles and selected road, label and 3D-building policies, each with its value, source,
202
- reason and override guidance. Query `--path /report/decisions --limit 80` to bound this output.
203
- `default` means an SDK fallback, `map` a value in the resolved map, `theme` a role defined by the
204
- selected theme and `derived` an optional role computed by Core. Use config inspection's merge
205
- provenance to distinguish inherited map values from leaf overrides. These decisions describe
206
- module policies and the theme's color context; later module styles and render-stack refinements
207
- can override them. They are not a trace of every final paint property or a visual-quality score.
208
- Simple `createStyle()` and browser drawing do not collect this report.
209
-
210
- Replace an inherited module with its constructor, patch it with `refine()`, restore an inherited
211
- value with `reset()`, or remove the whole domain with `disable()`. See the
212
- [authoring model](https://github.com/tileflow/tileflow-sdk/blob/main/packages/core/docs/authoring-model.md)
213
- for these composition rules. Capture contrasting designs and compare them at matching cameras to
214
- check both the default result and intentional departures from it.