@tileflow/core 0.1.0-alpha.34 → 0.1.0-alpha.36

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 (36) hide show
  1. package/README.md +24 -23
  2. package/THIRD_PARTY_NOTICES.md +100 -6
  3. package/dist/browser.d.ts +758 -388
  4. package/dist/browser.js +4598 -1169
  5. package/dist/build.d.ts +9 -5
  6. package/dist/build.js +2892 -1410
  7. package/dist/capture.js +1 -0
  8. package/dist/config-reference.json +3780 -2153
  9. package/dist/{icon-composition-DqoZWQuz.d.ts → icon-composition-sN3JkXdb.d.ts} +380 -96
  10. package/dist/index.d.ts +426 -13
  11. package/dist/index.js +4999 -1841
  12. package/dist/{manifest-types-hmzy2bBX.d.ts → manifest-types-jrBdBrES.d.ts} +71 -9
  13. package/dist/manifest.d.ts +2 -2
  14. package/dist/manifest.js +80 -2
  15. package/dist/native-profile.js +1 -0
  16. package/dist/native.d.ts +13 -3
  17. package/dist/native.js +714 -318
  18. package/dist/runtime.d.ts +1 -1
  19. package/dist/runtime.js +124 -6
  20. package/dist/worker/buildings-3d-worker.global.js +1 -0
  21. package/docs/authoring-model.md +12 -3
  22. package/docs/boundary-areas-and-choropleths.md +217 -0
  23. package/docs/buildings-3d.md +264 -34
  24. package/docs/cartographic-defaults.md +142 -0
  25. package/docs/compiled-style-performance.md +14 -3
  26. package/docs/data-is-separate-from-design.md +191 -7
  27. package/docs/hosted-session-authorization.md +2 -4
  28. package/docs/map-and-terrain-examples.md +10 -3
  29. package/docs/native-artifact-profile.md +5 -0
  30. package/docs/native-resource-urls.md +5 -3
  31. package/docs/street-lights.md +22 -6
  32. package/docs/sun-and-light.md +42 -2
  33. package/docs/theme-transitions-and-blends.md +50 -0
  34. package/docs/themes-and-module-styles.md +66 -0
  35. package/docs/trees-3d.md +85 -13
  36. package/package.json +5 -4
@@ -1,15 +1,22 @@
1
1
  # 3D buildings
2
2
 
3
- `createTileflowBuildings3D` raises the buildings of a Tileflow map's vector tiles round the camera
4
- and lights them from the theme controller's sun: walls and roofs take the light by how they face
5
- it, buildings cast shadows on each other and on the flat map, the ground darkens at the foot of the
6
- walls, and at night flood lights wash the foot of shops, busy places, landmarks and towers while
7
- the places busy at night light their ground floors. Showing, hiding and growing the city lays out
8
- no source, so it works as a 3D switch and as an animated height.
3
+ `createTileflowBuildings3D` raises the buildings of a Tileflow map's vector tiles round the camera.
4
+ By default it preserves the map's building colours, uses the scene sun, casts shadows, adds soft
5
+ contact shading and rounds exterior corners by 0.8 metres. An unspecified sun uses soft daylight
6
+ with directional intensity 0.2 and ambient intensity 0.8; authored theme lighting stays in control.
7
+ Showing, hiding and changing height are immediate. Illuminated outlines, random colour variation,
8
+ invented windows and night lights remain off. Every effect is independently overridable and
9
+ independent of the map's trees.
10
+
11
+ Tileflow landmarks and a map's own building models can stand in the same 3D scene, replacing
12
+ the buildings under their footprints.
9
13
 
10
14
  It is a custom WebGL2 layer for MapLibre GL JS, drawn above the map's buildings and below its
11
15
  labels. It needs WebGL2 and the Mercator projection, which MapLibre's globe turns into from about
12
- zoom 12; the buildings stand from zoom 14.5 by default.
16
+ zoom 12. On every map, buildings and placed models appear automatically from zoom 15,
17
+ with opacity rising linearly from 0 at 15 to 1 at 15.25. Zooming back out reverses the fade.
18
+ Their height stays unchanged throughout; shadows and contact shading fade with them.
19
+ `hide()` keeps them hidden at every zoom until `show()` is called.
13
20
 
14
21
  ## Show the buildings
15
22
 
@@ -27,23 +34,26 @@ import {
27
34
 
28
35
  export function raiseTheCity(map: TileflowBuildings3DMap, controller: TileflowThemeController) {
29
36
  const buildings = createTileflowBuildings3D({map, controller});
30
- // The buildings slide out of the ground over a second.
31
- void buildings.show({transition: {duration: 1000}});
37
+ // Enabled on creation; hide() and show() override visibility.
32
38
  return buildings;
33
39
  }
34
40
  ```
35
41
 
36
- `show()` raises the buildings, `hide()` sinks them back into the flat map, and `grow(scale)` eases
37
- them to a share of their height, from 0 to 1. Each returns a promise that resolves once the motion
38
- ends; a newer call takes over from where the buildings stand. A browser preference for reduced
42
+ `show()`, `hide()` and `grow(scale)` change the height immediately by default (duration 0).
43
+ `show({transition: {duration: 800, mode: 'fade'}})` reveals full-height buildings with a
44
+ smooth opacity fade. The default animation mode is `grow`; both modes respect reduced-motion settings.
45
+ To animate, pass `{transition: {duration: 800}}` explicitly; `grow(scale)` accepts a height share
46
+ from 0 to 1. Each returns a promise that resolves once the change ends; a newer call takes over from where the buildings stand. A browser preference for reduced
39
47
  motion changes at once. `dispose()` takes the layer off the map.
40
48
 
41
49
  The renderer reads the source's z15 tiles round the camera (z15 is where World's buildings carry
42
50
  their heights; a source that stops earlier is read at its own maximum zoom), up to `maxTiles`
43
51
  tiles (25 by default), and meshes them a few milliseconds per frame. It reads World's
44
- `render_height`, `render_min_height`, `hide_3d`, `building_tone` and `night_use`, and the places in
45
- World's `poi` layer (see [night light](#night-light)). `stats.declared` tells whether the map's
46
- style describes its 3D buildings; a style compiled before this description gets the defaults below.
52
+ `render_height`, `render_min_height`, `hide_3d`, `height_source`, `building_tone`, `class`,
53
+ `night_use` and the roof fields (see [roofs](#roofs)), and the places in World's `poi` layer (see
54
+ [night light](#night-light)). `stats.declared` tells whether the map's
55
+ style describes its 3D buildings. Recompile existing maps to pick up the new defaults: previously
56
+ compiled metadata keeps explicit settings, including `false`, `0` and `lighting: 'style'`.
47
57
 
48
58
  ## Design the buildings in the map
49
59
 
@@ -66,7 +76,16 @@ export default defineMap({
66
76
  extends: streets,
67
77
  modules: {
68
78
  buildings: refine({
69
- threeDimensional: {windows: true, nightLight: 0.6, edgeColor: '#2b2a33'},
79
+ threeDimensional: {
80
+ lighting: 'sun',
81
+ shadows: true,
82
+ occlusion: true,
83
+ edges: 1,
84
+ colorVariation: 0.06,
85
+ windows: true,
86
+ nightLight: 0.6,
87
+ edgeColor: '#2b2a33',
88
+ },
70
89
  renderStack: {
71
90
  // Core's 3D buildings take the colour of the map's 3D building pass.
72
91
  tints: refineRenderTarget({
@@ -91,18 +110,200 @@ export default defineMap({
91
110
  set of themes changes, and the themes either side of a blend's position mix on the GPU, so a
92
111
  moving blend costs nothing per frame.
93
112
  - `threeDimensional.shadows` and `occlusion` (the band at the foot of the walls and the darker tight
94
- corners) default to true. `windows: true` adds invented night windows, lit by rooms; they are
95
- decoration, not data, and are off by default.
113
+ corners) default to true. Set either to false independently. `windows: true` adds invented night windows, lit by rooms; they are
114
+ decoration, not data, and are off by default. Where World says what a building is used for
115
+ (`class`), sheds, garages, barns, factories, churches and service buildings get no rows of
116
+ windows, homes light more of theirs at night, in a warm light, and offices, schools and hospitals
117
+ fewer, in a cooler one.
96
118
  - `threeDimensional.edges` and `edgeColor` set the [edges](#edges), and `nightLight` the
97
119
  [night light](#night-light). Numbers and colours may be theme tokens.
120
+ - `threeDimensional.edgeRadius` rounds convex exterior corners in the mesh and softens the
121
+ roof/wall lighting transition. It accepts 0–1 metres and defaults to 0.8; use 0 for sharp corners.
122
+ Courtyard corners and tile cuts stay unchanged. The authored roof shape is preserved; the top
123
+ edge is softened through lighting, not a geometric roof bevel. Rounding adds a bounded number
124
+ of vertices at convex corners, with no extra render pass. Changes to this radius rebuild the
125
+ resident building meshes.
126
+
127
+ In both `style` and `sun` lighting, the vertical gradient darkens the foot of a facade by ten percent, fading smoothly over
128
+ at most three metres (half the height for low buildings). It is evaluated per fragment, so a tall
129
+ building does not acquire a full-height dark ramp. Disabling the authored extrusion's
130
+ `verticalGradient` disables this shading. Ground contact shadow is controlled by `occlusion`, enabled by default. It extends three metres
131
+ from grounded walls, darkening the ground by at most 16%, with a smooth distance falloff and round
132
+ corners. Every map uses these defaults when it enables `occlusion`. The reach is measured in world
133
+ metres, so its screen width scales continuously with zoom and becomes subtle at distant views.
134
+ Overlaps use the strongest
135
+ shade once, so shared walls, courtyards and tile joins do not accumulate dark bands. Raised building
136
+ parts do not cast contact shade. Facade occlusion darkens the base by at most 14% and fades
137
+ exponentially with a three-metre decay radius: about 5% remains one floor up and 2% two floors up.
138
+ It follows height above ground, so raised parts do not restart the contact gradient at their base.
139
+ The facade gradient, contact and inner-corner shade use the strongest contribution once, avoiding
140
+ an extra dark strip where they overlap. Both lighting modes use the same falloff at every zoom.
141
+ The ground mask uses one byte per drawing-buffer pixel while enabled and is released when disabled
142
+ or the renderer is removed; it resizes with the drawing buffer.
143
+
144
+ ## Landmarks
145
+
146
+ Maps on Tileflow World show Tileflow's landmarks, such as the Eiffel Tower, as 3D models in place of
147
+ the buildings under them. They come from a landmarks tileset kept apart from the map's data: each of
148
+ its tiles carries the models standing in it, with their levels of detail and footprints, and each
149
+ region's index names its tiles by their hash. The renderer reads the tileset's TileJSON once the
150
+ buildings show in 3D, then only the indexes of the regions round the camera and the landmark tiles
151
+ near it, so a landmark in Paris costs nothing in Madrid. New and improved landmarks reach every map
152
+ without a new build of the map. A tileset that cannot be read is tried again after five minutes,
153
+ and the buildings stay drawn meanwhile.
154
+
155
+ A map switches them off, or names the TileJSON of another landmarks tileset:
156
+
157
+ <!-- docs:check -->
158
+
159
+ ```ts
160
+ import {defineMap, refine} from '@tileflow/core';
161
+ import {streets} from '@tileflow/maps';
162
+
163
+ export default defineMap({
164
+ id: 'no-landmarks',
165
+ name: 'Streets without landmarks',
166
+ version: 1,
167
+ extends: streets,
168
+ modules: {
169
+ buildings: refine({threeDimensional: {landmarks: false}}),
170
+ },
171
+ });
172
+ ```
173
+
174
+ The map's own building models come first: a landmark with the same id as one of them, or whose
175
+ footprint meets one's, is not drawn.
176
+
177
+ ## Building models
178
+
179
+ A map can place its own 3D models of buildings in the city: a landmark the extruded footprints draw
180
+ badly, or the buildings an application presents. Each model stands where the map says, in the same
181
+ light, shadows and theme as the buildings round it, and the buildings under its footprint are no
182
+ longer raised while it stands. Put glTF binaries (`.glb`) in a directory, declare it in the map's
183
+ `models`, and say where each model stands:
184
+
185
+ <!-- docs:check -->
186
+
187
+ ```ts
188
+ import {defineMap, refine} from '@tileflow/core';
189
+ import {streets, streetsModels} from '@tileflow/maps';
190
+
191
+ export default defineMap({
192
+ id: 'paris-landmarks',
193
+ name: 'Paris landmarks',
194
+ version: 1,
195
+ extends: streets,
196
+ // ./models holds eiffel-tower.glb, and eiffel-tower.mid.glb and eiffel-tower.far.glb for
197
+ // farther views.
198
+ // Keep Streets' tree models alongside the building models.
199
+ models: [streetsModels, './models'],
200
+ modules: {
201
+ buildings: refine({
202
+ threeDimensional: {
203
+ models: [{model: 'eiffel-tower', at: [2.294481, 48.858258], heading: 0}],
204
+ modelColors: {surface: '#7a5c45', accent: '#5b4332'},
205
+ },
206
+ }),
207
+ },
208
+ });
209
+ ```
210
+
211
+ `at` is the longitude and latitude of the model's origin and `heading` the degrees clockwise from
212
+ north its own north is turned to. A map places up to 256 models; one model may stand in several
213
+ places. A placement may instead name a prepared model file served elsewhere with `url`, its HTTPS
214
+ address; the map then needs no `.glb` of its own for it.
215
+
216
+ How to prepare a model:
217
+
218
+ - **Materials name the parts.** Name each material `surface`, `secondary`, `glass`, `accent` or
219
+ `roof` (an exporter's `.001` suffix is ignored); a model whose materials name a tree's parts is a
220
+ [tree model](https://github.com/tileflow/tileflow-sdk/blob/main/packages/core/docs/trees-3d.md)
221
+ instead. Each part takes its colour from `modelColors`, or from the buildings' colour when the
222
+ design gives it none, so the model follows the theme from day to night; vertex colours lighter or
223
+ darker than the rest of their part shade it within that colour.
224
+ - **Metres round an origin on the ground.** The origin is where the model stands, with the model
225
+ above it and its own north along +y once read Z up; nothing may lie more than half a metre below
226
+ it.
227
+ - **What it stands in for.** A mesh whose material is named `footprint` is never drawn: it lays
228
+ out on the ground what the model stands in for, such as a landmark's whole base. Without one, the
229
+ model's own outline on the ground stands in, courtyards included.
230
+ - **Levels of detail.** `<id>.mid.glb` and `<id>.far.glb`, when present, are drawn farther from the
231
+ camera's centre, from about 900 m and 2.6 km.
232
+ - **Budgets.** A level has at most 200,000 triangles and 200,000 vertices, and a map at most 64
233
+ building models. Triangles of the default scene are read with their node transforms; textures,
234
+ animations and compressed geometry (Draco or meshopt) are not.
235
+
236
+ The build reads each model, reports a problem with the file and what to change, works out its
237
+ footprint and writes it beside the style as a prepared model file: a glTF binary whose meshes are
238
+ quantised (`KHR_mesh_quantization`) and compressed by meshoptimizer (`EXT_meshopt_compression`),
239
+ which any glTF viewer opens. The renderer downloads a model only when it stands near the tiles round
240
+ the camera, keeps the nearest dozen, and builds the tiles under it again without the buildings that
241
+ reach more than half a metre into its footprint; a neighbour that only touches it stays. A model
242
+ that cannot be downloaded leaves those buildings standing. Reading a prepared model file loads
243
+ meshoptimizer's WebAssembly decoder, so a page with a Content Security Policy allows
244
+ `'wasm-unsafe-eval'` in its `script-src`.
245
+
246
+ ## Roofs
247
+
248
+ A building whose tiles carry a roof shape (World's `roof_shape`) gets that roof: `gabled`,
249
+ `hipped`, `half_hipped`, `pyramidal`, `skillion`, `gambrel`, `mansard` and `saltbox` roofs, and
250
+ faceted `dome`, `onion` and `round` ones. The ridge follows the building's long side, or
251
+ `roof_orientation` and `roof_direction` when the tiles state them, and the walls rise to the roof,
252
+ so gable ends are walls. Each slope takes the sun, shadows and edges like any face: the ridge and
253
+ the hips are drawn as edges too.
254
+
255
+ The roof's height is `roof_height` where the tiles state it, taken from the building's height.
256
+ Otherwise the roof keeps the pitch of its shape (30° for a gabled or hipped roof, a half sphere for a
257
+ dome); it stands on the walls when the height came from levels or a fallback, and reaches the
258
+ building's height when that was measured (`height_source` `height`). A building without a roof
259
+ shape, a flat roof, a building with courtyards or one far from a rectangle (an L, a U) keeps a flat
260
+ roof, and so does a building a tile's buffer cuts short.
98
261
 
99
262
  ## Light and shadows
100
263
 
101
- The light comes from `controller.getSun()`: the theme's sun, mixed during a blend, or the sun an
264
+ Sun lighting and cast shadows are SDK defaults, including for Streets. Each map can choose its
265
+ own finish. For native extrusion lighting, sharp corners and no shadows in an extending map:
266
+
267
+ <!-- docs:check -->
268
+
269
+ ```ts
270
+ import {defineMap, refine} from '@tileflow/core';
271
+ import {streets} from '@tileflow/maps';
272
+
273
+ export default defineMap({
274
+ id: 'graphic-streets',
275
+ name: 'Graphic streets',
276
+ version: 1,
277
+ extends: streets,
278
+ modules: {
279
+ buildings: refine({
280
+ threeDimensional: {lighting: 'style', edgeRadius: 0, shadows: false, occlusion: false},
281
+ }),
282
+ },
283
+ });
284
+ ```
285
+
286
+ Select `timeOfDay` through your map adapter to change the authored direction, colour and strength
287
+ of direct and ambient light. Streets includes moonlight and shadows in its night presets. These
288
+ presets are independent of the user's clock and location. In DevTools, use the 3D view and
289
+ **Map → Buildings → Building light / Cast shadows**. The theme's `lighting.sun.shadowIntensity`
290
+ controls shadow strength; `lighting.ambient` fills the shade. Use `controller.setSun({date,
291
+ location})` for an astronomical direction instead, as described in
292
+ [sun and light](https://github.com/tileflow/tileflow-sdk/blob/main/packages/core/docs/sun-and-light.md).
293
+
294
+ `threeDimensional.lighting: 'style'` uses MapLibre's `light` position,
295
+ anchor, colour and intensity, together with the authored extrusion's `verticalGradient`.
296
+ Changing the scene sun does not change this base lighting. The style's native lighting defaults
297
+ apply when it declares no light.
298
+
299
+ With the default `lighting: 'sun'`, the light comes from `controller.getSun()`: the theme's sun, mixed during a blend, or the sun an
102
300
  application places by direction or by date and place (see
103
301
  [sun and light](https://github.com/tileflow/tileflow-sdk/blob/main/packages/core/docs/sun-and-light.md)).
302
+ Without a controller, the renderer reads the compiled style's sun, falling back to the style's
303
+ light direction and then the SDK daylight. It refreshes this cached light on style changes.
104
304
  A sunlit roof keeps the building's colour, a wall turned to a low sun glows, and shade takes the
105
- sky's colour. As the sun sets, shadows lengthen while they fade; a sun below the horizon casts none.
305
+ sky's colour. Shadows lengthen as the sun lowers, with their projected direction limited to 15°
306
+ above the horizon. The actual light retains its authored altitude; a sun below the horizon casts none.
106
307
 
107
308
  A wall sees half the sky, so at night, when the ground gives back little light, walls in shade
108
309
  stay darker than roofs. A theme's night `lighting.sun` set high (60° or more) lights roofs more than
@@ -113,15 +314,16 @@ buildings, it multiplies the ground by the shadows and by a band at the foot of
113
314
  and icons drawn above it keep their colours. Layers kept under 3D content with
114
315
  `createTileflowGroundLayers` are darkened with the ground. Other Core 3D layers on the map, such as
115
316
  [3D trees](https://github.com/tileflow/tileflow-sdk/blob/main/packages/core/docs/trees-3d.md), share
116
- the buildings' shadows.
317
+ the buildings' shadows when `shadows: true`. With it off, buildings neither cast nor receive
318
+ scene shadows, even when another 3D layer uses them.
117
319
 
118
320
  ## Edges
119
321
 
120
322
  Where one building ends and the next starts stays legible at any distance: each roof's outline and
121
- each building's outer corners catch the light in a line one CSS pixel wide, whatever the zoom, and
122
- each building is a little lighter or darker than its colour (up to 6%), so neighbours of one colour
123
- stay apart. The lines are strongest at night; diagonals inside a roof, inner corners and the cuts at
124
- a tile's edge draw none. `edges` sets their strength from 0 (none) to 1, the default.
323
+ each building's outer corners catch the light in a line one CSS pixel wide, whatever the zoom, when enabled. `colorVariation` independently varies building brightness by up to the specified
324
+ fraction (0–1); it defaults to 0, so authored colours remain unchanged. Use 0.06 for the earlier
325
+ 6% variation. The lines are strongest at night; diagonals inside a roof, inner corners and the cuts at
326
+ a tile's edge draw none. `edges` sets their strength from 0 (the default, none) to 1.
125
327
 
126
328
  `edgeColor` gives the lines a colour of the map's own, per theme through a token: a dark outline as
127
329
  on a printed map, or a neon one at night. It is drawn as it is, unlit, at the full `edges` strength
@@ -129,8 +331,8 @@ by day and night, mixed between the themes either side of a blend's position.
129
331
 
130
332
  ## Night light
131
333
 
132
- At night (as the sun's `brightness` falls), the buildings light the street from data, quietly by
133
- default:
334
+ With `nightLight` above 0, the buildings light the street from data at night (as the sun's
335
+ `brightness` falls):
134
336
 
135
337
  - Landmarks (World's `building_tone` `destination`) and towers of 40 m or more, one in two, are
136
338
  flood-lit at their foot, on the wall and the ground around it.
@@ -144,7 +346,7 @@ The night use comes from World's `night_use` on each building. In tiles whose bu
144
346
  carry it, the renderer finds it in the tile's places: a place inside a building, or within a few
145
347
  metres of its facade, gives the building its use.
146
348
 
147
- `nightLight` sets how bright all of this shines, from 0 (none) to 2; it defaults to 1.
349
+ `nightLight` sets how bright all of this shines, from 0 (none) to 2; it defaults to 0.
148
350
 
149
351
  For the streets themselves, see
150
352
  [street lights](https://github.com/tileflow/tileflow-sdk/blob/main/packages/core/docs/street-lights.md),
@@ -153,20 +355,48 @@ download.
153
355
 
154
356
  ## Options
155
357
 
156
- The renderer's own options say only how it is delivered: `minzoom` (14.5 by default), `maxTiles`,
358
+ The renderer's own options say only how it is delivered: `minzoom` (15 by default, with a 0.25-zoom opacity ramp), `maxTiles`,
157
359
  `beforeId` to place the layer before another one, and `transformRequest` to apply the map's request
158
360
  rules to the tiles it reads.
159
361
 
160
362
  ## Cost
161
363
 
162
- The shadow map (up to 4096 × 4096) is drawn again only when the sun, the buildings or their height
163
- change, not when the camera moves. In a campaign over Miami at 1512 × 860 at 2×, a city lit this
164
- way kept the frame pacing of the same city drawn without shadows.
364
+ During partial opacity, a depth-only geometry pass resolves the nearest surfaces before colour
365
+ blending, preventing rear walls from bleeding through. At full opacity only the normal geometry
366
+ pass runs; zoom fading schedules no extra animation frames or per-zoom mesh rebuilds.
367
+
368
+ The browser bundle prepares building meshes in one worker per renderer and transfers the finished
369
+ vertex and index buffers. No worker URL or extra asset hosting is needed. A Content Security Policy
370
+ must allow `worker-src blob:` to use this path; if workers are unavailable or blocked, the renderer
371
+ falls back to local meshing. The worker is terminated on `dispose()`. Pending tile updates retain
372
+ the previous mesh until their replacement is ready; cancelled and obsolete results are discarded.
373
+
374
+ Mercator rendering batches buildings spatially and skips batches outside the camera. Shadow
375
+ casting uses the light's bounds independently, so an offscreen building can still cast a visible
376
+ shadow. Ground contact and flood bands retain their physical reach when testing visibility.
377
+ Rounded corners use fewer segments when their screen radius is small and retain the original
378
+ tessellation up close. Latitude, radius and pitch inform this choice; very oblique views keep full
379
+ detail. Changes are prepared asynchronously; heights, feature colours,
380
+ and the contact-shadow settings stay the same. GPU uploads remain limited to two tiles per frame.
381
+
382
+ The shared shadow map is at most 4096 × 4096. On a planar view it concentrates resolution on visible
383
+ ground and raised surfaces, while every loaded caster can still project into that area. A snapped
384
+ Mercator texel grid stays stable during small pans, including changes of latitude, without fixing
385
+ the meshes' physical metre scale. One filtered depth comparison keeps edges sharp.
386
+ Missing camera capabilities, terrain or a view near the horizon use the full loaded area instead.
387
+ The depth pass runs again when its focused area, light or casting geometry changes and is reused
388
+ when they stay unchanged.
165
389
 
166
390
  ## Limits
167
391
 
168
392
  - Mercator only; the layer draws nothing below its `minzoom`.
169
393
  - No terrain: buildings stand on flat ground.
170
- - One building colour per feature and theme; facades, roof shapes and building parts as separate
171
- shapes are not drawn.
394
+ - One building colour per feature and theme, on its roof and walls alike; facades are not drawn.
395
+ - Roofs follow the building's smallest enclosing rectangle: a building with courtyards or far from a
396
+ rectangle keeps a flat roof, and `sawtooth` roofs are drawn flat.
172
397
  - A colour expression is evaluated at zoom 16.
398
+ - Building models and landmarks take no edges or night light of their own yet, and cannot be
399
+ picked or styled by state.
400
+ - A hosted map takes up to 64 building models of its own, 32 MiB once prepared; `tileflow deploy`
401
+ uploads them with the map. A deploy with `--with-native` does not take them, since MapLibre Native
402
+ draws no models. Models named by `url` deploy as they are.
@@ -0,0 +1,142 @@
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 below buildings, with text aligned to the map surface.
81
+ Shields and junction numbers remain in the foreground. Ground placement does not require the
82
+ buildings module: street names still draw when buildings are disabled.
83
+
84
+ This module fragment keeps the normal language selection and road-class filters while opting
85
+ into ground names. Individual symbol styles can override the default text alignment:
86
+
87
+ <!-- docs:check -->
88
+
89
+ ```ts
90
+ import {labels} from '@tileflow/core';
91
+
92
+ export const groundLabels = labels({
93
+ roadPlacement: 'ground',
94
+ collisionPriority: 'navigation',
95
+ language: 'local',
96
+ roads: 'all',
97
+ styles: {
98
+ roads: {
99
+ primary: {text: {pitchAlignment: 'viewport'}},
100
+ },
101
+ },
102
+ });
103
+ ```
104
+
105
+ MapLibre offers foreground symbols first when resolving collisions. Ground names therefore yield
106
+ to POIs and other foreground symbols, even with `collisionPriority: 'navigation'`. Navigation
107
+ priority still orders the ground road classes relative to one another; it does not make their
108
+ collision priority independent of drawing order. Foreground placement retains the existing
109
+ navigation priority over ordinary POIs. Neither mode turns collision detection off.
110
+
111
+ Use a refinement on `labels.roads.<class>` to move an individual class after this default has been
112
+ applied. Symbol size, zoom range, spacing, priority, fonts and halos remain available in
113
+ `styles.roads`. The same names, language fallbacks and class selectors are used in both modes.
114
+
115
+ ## Discover and override decisions
116
+
117
+ `tileflow describe labels --json` exposes the installed options, and `tileflow inspect --json`
118
+ shows resolved inheritance and merge provenance. `tileflow explain --inspection --json` adds
119
+ compiled semantic targets and physical provenance. These commands describe the authored map;
120
+ they do not rewrite explicit style choices.
121
+
122
+ `tileflow describe themes --json` lists the twelve required base color roles and composition
123
+ rules. A [single independent theme](https://github.com/tileflow/tileflow-sdk/blob/main/packages/core/docs/themes-and-module-styles.md)
124
+ needs no time-of-day catalog. Optional color roles derive from that theme and accept explicit
125
+ replacements. Removing inherited lighting uses `reset()` inside `defineTheme`; it restores the
126
+ SDK fallback, while building shadows and occlusion have separate on/off controls.
127
+
128
+ `createStyleResult()` and `tileflow inspect compiled --json` include `report.decisions`: actual
129
+ color roles and selected road, label and 3D-building policies, each with its value, source,
130
+ reason and override guidance. Query `--path /report/decisions --limit 80` to bound this output.
131
+ `default` means an SDK fallback, `map` a value in the resolved map, `theme` a role defined by the
132
+ selected theme and `derived` an optional role computed by Core. Use config inspection's merge
133
+ provenance to distinguish inherited map values from leaf overrides. These decisions describe
134
+ module policies and the theme's color context; later module styles and render-stack refinements
135
+ can override them. They are not a trace of every final paint property or a visual-quality score.
136
+ Simple `createStyle()` and browser drawing do not collect this report.
137
+
138
+ Replace an inherited module with its constructor, patch it with `refine()`, restore an inherited
139
+ value with `reset()`, or remove the whole domain with `disable()`. See the
140
+ [authoring model](https://github.com/tileflow/tileflow-sdk/blob/main/packages/core/docs/authoring-model.md)
141
+ for these composition rules. Capture contrasting designs and compare them at matching cameras to
142
+ check both the default result and intentional departures from it.
@@ -249,8 +249,19 @@ The closed category vocabulary is `arts-entertainment`, `education`, `food-drink
249
249
  `transport`, and `visitor-amenity`. Producer `type` and `icon` values use stable snake_case names.
250
250
  When an `icon` sprite exists it is used directly; an unknown or absent icon safely falls back to the
251
251
  category's themed image role. `labels` and `icons` are booleans. Category styles control only
252
- presentation and may hide or delay candidates, never promote them above the producer's selection.
253
- `placement.coupleIconAndLabel` keeps a POI's icon and label atomic during collision placement.
252
+ presentation and never promote candidates above the producer's selection.
253
+ `placement.coupleIconAndLabel` keeps a POI's icon and label atomic during collision placement:
254
+ both are required.
255
+
256
+ MapLibre orders symbol placement only within a layer, so the module compiles every category into
257
+ one layer per representation and orders it by `filter_rank` and `size_rank`: a station is placed
258
+ before a cash machine whatever their categories. Per-category appearance becomes a `match` on
259
+ `category`; zoom curves are hoisted above it so each category keeps its exact curve. Categories that
260
+ need different layer-wide properties use `placement.order: 'categories'`, one layer per category in
261
+ listed order. `placement.prominentRank` places the most important ranks in a second layer above road
262
+ and place labels, which keeps landmarks visible on maps whose labels give street names priority.
263
+ When icons and labels are both drawn, unnamed POIs are omitted except the self-explanatory types in
264
+ `placement.iconOnlyTypes` (parking and public toilets by default), which draw only their pictogram.
254
265
 
255
266
  ```ts
256
267
  poi({
@@ -263,4 +274,4 @@ poi({
263
274
 
264
275
  The same contract drives regular POI layers, custom HUD treatments, semantic interactions, and
265
276
  feature inspection. There is intentionally no compatibility path for the former class mapping,
266
- rank ceilings, or named density/detail presets.
277
+ rank ceilings, per-category densities, or named density/detail presets.