@tileflow/core 0.1.0-alpha.33 → 0.1.0-alpha.35

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -45,13 +45,37 @@ detail:
45
45
  - Lamps from World's `street_furniture` points whose `subclass` is `street_lamp`. Where the data
46
46
  gives them, a lamp's `height`, `lamp_mount`, `light_count`, `lamp_type` and `light_colour` set how
47
47
  high it stands, how far its light reaches and its colour: sodium lamps shine orange, LED and
48
- metal-halide lamps white. Other lamps take `color`, a warm white by default.
48
+ metal-halide lamps white. Other lamps take the map's light colour.
49
49
  - Streets in World's `transportation` layer, and pavements in its `sidewalk` layer, that the data
50
50
  maps as lit (`lit`). A street mapped as unlit never takes light.
51
51
 
52
52
  How much of this a city's data holds varies widely: some map most of their lamps or most of their
53
53
  lit streets, others almost none. Where the data says nothing, the streets stay dark.
54
54
 
55
+ ## Design the lights in the map
56
+
57
+ The map describes its street lights in its roads module, next to the other details drawn from
58
+ street furniture, and publishes them with its style for every theme; `stats.declared` tells whether
59
+ it does. Without a description the lights take the defaults below:
60
+
61
+ <!-- docs:check -->
62
+
63
+ ```ts
64
+ import {defineMap, refine} from '@tileflow/core';
65
+ import {streets} from '@tileflow/maps';
66
+
67
+ export default defineMap({
68
+ id: 'night-city',
69
+ name: 'Night city',
70
+ version: 1,
71
+ extends: streets,
72
+ modules: {
73
+ // Sodium-orange light, a little brighter, on every street of tiles that map few lamps.
74
+ roads: refine({streetLights: {color: '#ffb35c', intensity: 1.3, estimate: 'streets'}}),
75
+ },
76
+ });
77
+ ```
78
+
55
79
  `estimate` chooses where the layer adds light the data does not map as lamps:
56
80
 
57
81
  - `lit` (the default): along the streets and pavements mapped as lit.
@@ -63,12 +87,15 @@ lit streets, others almost none. Where the data says nothing, the streets stay d
63
87
  Estimated light keeps to the middle of its street and shows no lamp head, so it reads as a lit
64
88
  street rather than as lamps that may not stand there.
65
89
 
90
+ `color` is the light of the lamps whose data gives no colour, a warm white by default, and
91
+ `intensity` scales the light from 0 to 2 (1 by default); both may be theme tokens and mix during a
92
+ blend. `streetLights: {enabled: false}` gives the map none.
93
+
66
94
  ## Options
67
95
 
68
- `intensity` scales the light from 0 to 2 (1 by default). `minzoom` (14 by default) and `maxTiles`
69
- (25 by default, at most 64) bound where and how far the lights shine. `beforeId` places the layer
70
- before another one; `source` names a vector source other than `tileflow`; `transformRequest`
71
- applies the map's request rules to the tiles the layer reads.
96
+ The layer's own options say only how it is delivered: `minzoom` (14 by default) and `maxTiles` (25
97
+ by default, at most 64) bound where and how far the lights shine, `beforeId` places the layer before
98
+ another one, and `transformRequest` applies the map's request rules to the tiles it reads.
72
99
 
73
100
  Layers that read the same source share each tile's download, so the street lights and the 3D
74
101
  buildings fetch a tile once between them.
@@ -258,3 +258,53 @@ export async function duskWhileFlying(
258
258
  The React, Vue, and Svelte maps take the same flag in `themeBlend`: pass
259
259
  `themeBlend={{hold: true, position, themes}}` while the camera moves and drop `hold` when it rests.
260
260
  The React Native `Map` blends natively and does not take `hold`.
261
+
262
+ ## Time of day
263
+
264
+ `timeOfDay` is an optional map capability. It maps a theme and an artistic preset (`day`,
265
+ `dawn`, `dusk`, `night`) to a complete theme already in `themes`:
266
+
267
+ <!-- docs:check -->
268
+
269
+ ```ts
270
+ import {defineMap} from '@tileflow/core';
271
+ import {streets, streetsThemes} from '@tileflow/maps';
272
+
273
+ export default defineMap({
274
+ id: 'city',
275
+ version: 1,
276
+ extends: streets,
277
+ themes: {
278
+ paper: streetsThemes.light,
279
+ sunrise: streetsThemes.dawn,
280
+ evening: streetsThemes.dusk,
281
+ ink: streetsThemes.dark,
282
+ },
283
+ defaultTheme: 'paper',
284
+ systemThemes: {light: 'paper', dark: 'ink'},
285
+ timeOfDay: {
286
+ paper: {day: 'paper', dawn: 'sunrise', dusk: 'evening', night: 'ink'},
287
+ },
288
+ });
289
+ ```
290
+
291
+ Applications keep `theme` and optionally add `timeOfDay`: `theme="paper" timeOfDay="dusk"`
292
+ selects `evening`. Compilation supports `createStyle(map, {theme: 'paper', timeOfDay: 'dusk'})`;
293
+ `resolveTileflowRuntimeTheme(map, theme, colorScheme, timeOfDay)` uses the same resolution.
294
+ Omitting the preset preserves the existing selection. `system` resolves its light/dark theme
295
+ first. A concrete variant such as `ink` also identifies its family when a preset is supplied.
296
+ Maps or themes without a family ignore this option. Within a declared family, an unavailable
297
+ preset is an error; controls should offer only declared presets. Families must include their own
298
+ name in their presets, and a concrete theme can belong to only one family.
299
+
300
+ The catalog inherits atomically; `timeOfDay: {}` disables it on an extension. Replacing `themes`
301
+ clears an inherited catalog. `defaultTheme`, `systemThemes`, and `themeTransition` retain their
302
+ behavior. An explicit `themeBlend` owns its concrete theme sequence and takes precedence over
303
+ `timeOfDay`. Changing the preset does not start a clock or call `setSun`; lighting is whatever the
304
+ selected theme already declares, and an application's sun override retains its existing behavior.
305
+ Captures, style URLs, and transition events use the resolved concrete theme name.
306
+
307
+ Maps with a catalog publish runtime manifest version 2. Updated browser and native readers accept
308
+ both versions. Old readers need a version 1 manifest: `createManifest(project, {manifestVersion: 1})`
309
+ omits the catalog and keeps every concrete theme available. Maps without a catalog still emit
310
+ version 1. Update readers before delivering a version 2 manifest.
@@ -0,0 +1,270 @@
1
+ # 3D trees
2
+
3
+ `createTileflowTrees3D` stands the trees of a Tileflow map's vector tiles round the camera as 3D
4
+ trees, lit by the theme controller's sun. Each tree takes the form and the size its data gives:
5
+ palms, conifers, columns and three broadleaf forms, at their mapped height and crown where the data
6
+ has them. Trees cast shadows on the ground and, with `createTileflowBuildings3D`, on the buildings,
7
+ and take the buildings' shadows; nearer buildings hide them.
8
+
9
+ The map describes how its trees look; the renderer only stands them. A map that declares
10
+ `vegetation({mode: '3d'})`, as Streets does, publishes its tree colours, scales and forms in the
11
+ style it compiles, for every theme, and the renderer follows theme changes and blends.
12
+
13
+ It is a custom WebGL2 layer for MapLibre GL JS, drawn above the map's buildings and below its
14
+ labels. It needs WebGL2 and the Mercator projection, and the trees stand from zoom 15 by default.
15
+ When model trees are requested, Capture, Static Maps, Native, and browsers without WebGL2 use
16
+ the module's `fallback`: `flat` by default, or `none` to omit trees. Static output uses the 2D view
17
+ for `auto` maps. Streets chooses `none`, so it never substitutes flat crowns
18
+ for its models unless an extension explicitly chooses `flat` or `auto`.
19
+
20
+ ## Visibility and representation
21
+
22
+ Use `createTileflowVegetation` for application controls. React, Vue and Svelte mount it automatically
23
+ in interactive maps. The map chooses `vegetation.mode` independently of visibility:
24
+
25
+ | Mode | 2D view | 3D view |
26
+ | -------------- | ------------------- | ------------------- |
27
+ | `3d` (default) | Model trees | Model trees |
28
+ | `flat` | Flat crowns/circles | Flat crowns/circles |
29
+ | `auto` | Flat crowns/circles | Model trees |
30
+
31
+ `viewMode` is explicit application state (`2d` by default), not a pitch threshold. Tilting the camera
32
+ does not change representation. Visibility defaults to `vegetation.visible ?? true`; OFF hides every
33
+ representation. Turning trees off releases model buffers and tile readers. Controls apply immediately,
34
+ without a grow/sink animation, and do not change building appearance or lighting.
35
+
36
+ <!-- docs:check -->
37
+
38
+ ```ts
39
+ import {createTileflowVegetation, type TileflowVegetationOptions} from '@tileflow/core/browser';
40
+
41
+ export function mountVegetation(options: TileflowVegetationOptions) {
42
+ const trees = createTileflowVegetation({...options, viewMode: '2d'});
43
+ trees.setVisible(false); // Hide all vegetation, including flat artwork.
44
+ trees.setVisible(undefined); // Restore the map's authored visibility.
45
+ trees.setViewMode('3d'); // Only auto maps change representation.
46
+ return trees; // Call dispose() before removing the map, or let its remove event clean up.
47
+ }
48
+ ```
49
+
50
+ Pass the application's HTTP tile `transformRequest` when it supplies credentials or request headers;
51
+ asynchronous transforms are supported. Use one vegetation controller per map and do not mount a second
52
+ low-level tree renderer alongside it. Style replacement and theme changes retain visibility and view mode.
53
+
54
+ To change an inherited map's policy while preserving its artwork:
55
+
56
+ <!-- docs:check -->
57
+
58
+ ```ts
59
+ import {defineMap, refine} from '@tileflow/core';
60
+ import {streets} from '@tileflow/maps';
61
+
62
+ export default defineMap({
63
+ id: 'adaptive-trees',
64
+ version: 1,
65
+ extends: streets,
66
+ modules: {vegetation: refine({mode: 'auto', fallback: 'flat'})},
67
+ });
68
+ ```
69
+
70
+ Use `refine({visible: false})` for initially hidden vegetation that can be enabled at runtime.
71
+ Use `vegetation: disable()` to remove the module entirely; a runtime toggle cannot restore a module
72
+ that is absent from the compiled map. Browser framework components accept `vegetation={false}` and
73
+ `viewMode="3d"` (Vue: `:vegetation="false"`, `view-mode="3d"`). Image mode uses the compiled static
74
+ fallback and does not apply these interactive controls.
75
+
76
+ ## Animate trees with the low-level renderer
77
+
78
+ This example assumes an existing MapLibre map showing a Tileflow map whose vector source is called
79
+ `tileflow`, as Tileflow's maps call World, and a theme controller for it:
80
+
81
+ <!-- docs:check -->
82
+
83
+ ```ts
84
+ import {
85
+ createTileflowTrees3D,
86
+ type TileflowThemeController,
87
+ type TileflowTrees3DMap,
88
+ } from '@tileflow/core/browser';
89
+
90
+ export function standTheTrees(map: TileflowTrees3DMap, controller: TileflowThemeController) {
91
+ const trees = createTileflowTrees3D({map, controller});
92
+ // The trees grow out of the ground over a second.
93
+ void trees.show({transition: {duration: 1000}});
94
+ return trees;
95
+ }
96
+ ```
97
+
98
+ `show()` grows the trees, `hide()` sinks them back into the ground, and `grow(scale)` eases them to
99
+ a share of their size, from 0 to 1. Each returns a promise that resolves once the motion ends; a
100
+ newer call takes over from where the trees stand. A browser preference for reduced motion changes at
101
+ once. `dispose()` takes the layer off the map.
102
+
103
+ This low-level API retains its flat-layer restoration behavior; use the vegetation controller for
104
+ a visibility toggle. While they stand, the 3D trees stand in for the map's flat trees: the layers the vegetation module
105
+ draws them with are hidden when the trees are shown, and come back as they were once the trees have
106
+ sunk or the layer leaves the map.
107
+
108
+ `stats.declared` tells whether the map's style describes 3D trees. When it is false the layer draws
109
+ nothing: declare `vegetation({mode: '3d'})` in the map. `stats` also counts the tiles, the trees
110
+ placed and the trees drawn in the last frame.
111
+
112
+ ## What the trees come from
113
+
114
+ The renderer reads the source's z15 tiles round the camera:
115
+
116
+ - World's `tree` layer: the trees OpenStreetMap maps one by one, as points, and its rows of trees,
117
+ as lines, along which a tree stands every 8 metres. A tree's `leaf_type`, `genus`, `species` and
118
+ `taxon` choose its form; its `height` and `diameter_crown` its size, and a trunk's
119
+ `circumference` sizes a tree with neither. A row's attributes apply to each of its trees.
120
+ - World's `canopy` layer, where the data has it: decorative points in urban parks and gardens, each
121
+ standing one to three trees by its canopy cover, inside the room it leaves before a path, a
122
+ building or the park's edge. They stand for measured canopy, not for mapped trees.
123
+
124
+ | Form | From the data |
125
+ | ---------- | ---------------------------------------------------------------------------- |
126
+ | `palm` | `leaf_type` `palm`, `taxon` `Arecaceae`, or a palm genus such as `Roystonea` |
127
+ | `columnar` | the cypress genera `Cupressus`, `Calocedrus` and `Thuja` |
128
+ | `conifer` | `leaf_type` `needleleaved`, or a conifer genus such as `Pinus` or `Cedrus` |
129
+ | `open` | spreading genera such as `Quercus`, `Olea`, `Ficus` or `Jacaranda` |
130
+ | `tall` | upright genera such as `Platanus`, `Populus`, `Ulmus` or `Ginkgo` |
131
+ | `round` | rounded genera such as `Acer`, `Tilia`, `Aesculus` or `Prunus` |
132
+
133
+ A tree whose data gives no form, and every canopy tree, takes one of the three broadleaf forms; a
134
+ tree the data calls broadleaved keeps to them. Each tree's form, size, turn and shade come from a
135
+ stable hash of where it stands, so it looks the same at every zoom and on every device. Points in a
136
+ tile's buffer belong to the neighbouring tile, so no tree stands twice.
137
+
138
+ ## Design the trees in the map
139
+
140
+ Colours come from the theme: `vegetation.tree.bark`, `vegetation.tree.broadleaf.a` to `.d`,
141
+ `vegetation.tree.conifer.a` to `.c` and `vegetation.tree.palm.a` to `.c`, sunlit, from darkest to
142
+ lightest. A crown facing a sunlit sky takes its colour; shade takes the sky's light, as on 3D
143
+ buildings. The vegetation module's `threeDimensional` options refine them for one map:
144
+
145
+ <!-- docs:check -->
146
+
147
+ ```ts
148
+ import {defineMap, token, vegetation} from '@tileflow/core';
149
+ import {streets} from '@tileflow/maps';
150
+
151
+ export default defineMap({
152
+ id: 'subtropical-city',
153
+ name: 'Subtropical city',
154
+ version: 1,
155
+ extends: streets,
156
+ modules: {
157
+ vegetation: vegetation({
158
+ mode: '3d',
159
+ threeDimensional: {
160
+ barkColor: token.color('vegetation.tree.bark'),
161
+ heightScale: 1.1,
162
+ finish: 'toon',
163
+ edges: 1,
164
+ // Along this coast most trees the data does not describe are palms.
165
+ regions: [{bounds: [-81.0, 24.4, -79.8, 26.9], unknownForms: {palm: 3, round: 1, open: 1}}],
166
+ },
167
+ }),
168
+ },
169
+ });
170
+ ```
171
+
172
+ `finish` sets how trees take the light: `faceted` (the default), low-poly facets softened towards a
173
+ round crown; `smooth`, round crowns; or `toon`, three bands of light and a crisp shadow, as a drawn
174
+ tree has them. `edges` rings each tree with an outline a constant width on screen, from 0 (none,
175
+ the default) to 1, in `edgeColor` or the crown's darkest colour deepened; buildings use the same
176
+ two words for their outlines.
177
+
178
+ `heightScale` and `crownScale` stretch every tree, and `forms` each form on top of them, such as
179
+ `forms: {palm: {heightScale: 1.2}}` for taller palms. `unknownForms` sets, as relative weights, the
180
+ forms of trees whose data gives none, and `regions` sets them inside `[west, south, east, north]`
181
+ bounds in degrees, the first region holding a tree applying. They are a choice of look, not data:
182
+ they never change a tree whose data gives its form, and as mapping improves they apply to fewer
183
+ trees.
184
+
185
+ ## Your own trees
186
+
187
+ A map can draw a form with its own models. Put glTF binaries (`.glb`) in a directory, declare it in
188
+ the map's `models`, and name the models a form draws by their file names:
189
+
190
+ <!-- docs:check -->
191
+
192
+ ```ts
193
+ import {defineMap, vegetation} from '@tileflow/core';
194
+ import {streets} from '@tileflow/maps';
195
+
196
+ export default defineMap({
197
+ id: 'palm-coast',
198
+ name: 'Palm coast',
199
+ version: 1,
200
+ extends: streets,
201
+ // ./models holds sabal-palm.glb and royal-palm.glb, and sabal-palm.far.glb for far trees.
202
+ models: ['./models'],
203
+ modules: {
204
+ vegetation: vegetation({
205
+ mode: '3d',
206
+ threeDimensional: {
207
+ forms: {palm: {models: ['sabal-palm', 'royal-palm'], heightScale: 1.1}},
208
+ },
209
+ }),
210
+ },
211
+ });
212
+ ```
213
+
214
+ Each tree of the form draws one of its models, one to four, by its stable hash, so a palm keeps its
215
+ model wherever it is read. The data still chooses each tree's form and size: a model replaces how a
216
+ form looks, not which trees take it.
217
+
218
+ How to prepare a model:
219
+
220
+ - **Materials name the parts.** Name each material `bark`, `leaves`, `fronds` or `stem` (an
221
+ exporter's `.001` suffix is ignored). Each part takes its theme colours, as Core's trees do; the
222
+ material's colour and any vertex colours set how light or dark each part of the model is within
223
+ them, so the model follows the theme from day to night.
224
+ - **The origin is where the trunk meets the ground**, with the tree above it. Tileflow stands the
225
+ model as a unit tree: its top is the tree's height, and its widest leaf half its crown.
226
+ - **Budgets.** A model has at most 1,000 triangles and 4,096 vertices; `<id>.far.glb`, drawn
227
+ beyond the nearest tiles, at most 120 triangles. Without a far model, the form's own far tree
228
+ stands in. A map draws at most 16 models.
229
+ - **What is read.** Triangles of the default scene with their node transforms; textures,
230
+ animations and compressed geometry (Draco or meshopt) are not. Export without compression.
231
+
232
+ `tileflow preview` and every build read the models, report a problem with the file and what to
233
+ change, and watch the directories. The models travel inside the compiled style, so they need no
234
+ files of their own when the map is deployed. A later directory's model replaces an earlier one of
235
+ the same name; omitting `models` inherits them, and `[]` declares none.
236
+
237
+ ## Light and shadows
238
+
239
+ The light comes from `controller.getSun()`: the theme's sun, mixed during a blend, or the sun an
240
+ application places by direction or by date and place (see
241
+ [sun and light](https://github.com/tileflow/tileflow-sdk/blob/main/packages/core/docs/sun-and-light.md)).
242
+ Leaves take light round their edges and glow a little against a low sun; the underside and the
243
+ inside of a crown stay darker.
244
+
245
+ Trees and 3D buildings on one map share one shadow map: trees cast shadows on the ground and on the
246
+ buildings, and stand in the buildings' shade. The flat map darkens under both. Without 3D buildings
247
+ the trees darken the map themselves.
248
+
249
+ ## Options
250
+
251
+ `minzoom` (15 by default) and `maxTiles` (25 by default, at most 64) bound where and how far trees
252
+ stand; trees thin out and fade with distance from the map's centre, and near trees take more detail
253
+ than far ones. `canopySource` names a vector source with canopy points when they are not in the
254
+ map's World source. `beforeId` places the layer before another one; `transformRequest` applies the
255
+ map's request rules to the tiles the layer reads. Layers that read the same source share each
256
+ tile's download.
257
+
258
+ ## Cost
259
+
260
+ A near tree has 60 to 120 triangles and a far one about 20, and each form is one instanced draw per
261
+ tile. The shadow map is drawn again only when the sun, the trees on screen or their size change, not
262
+ when the camera moves. Over the Champ de Mars in Paris, where OpenStreetMap maps every tree, about
263
+ 13,000 trees stand in a pitched view at zoom 16.6.
264
+
265
+ ## Limits
266
+
267
+ - Mercator only; the layer draws nothing below its `minzoom`.
268
+ - No terrain: trees stand on flat ground.
269
+ - No wind or seasons; one form per tree.
270
+ - Models replace a form's look; a model per genus or species is not available.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tileflow/core",
3
- "version": "0.1.0-alpha.33",
3
+ "version": "0.1.0-alpha.35",
4
4
  "license": "Apache-2.0",
5
5
  "description": "Typed Tileflow config and MapLibre style compiler.",
6
6
  "type": "module",
@@ -84,9 +84,10 @@
84
84
  "@mapbox/vector-tile": "3.0.0",
85
85
  "earcut": "3.2.4",
86
86
  "maplibre-contour": "0.1.0",
87
+ "meshoptimizer": "1.3.0",
87
88
  "pbf": "5.1.2",
88
- "pmtiles": "4.4.0",
89
- "whatwg-url": "15.1.0"
89
+ "pmtiles": "4.5.0",
90
+ "whatwg-url": "17.2.0"
90
91
  },
91
92
  "scripts": {
92
93
  "build": "tsup --config tsup.build.config.ts && tsup --config tsup.native-profile.config.ts && tsup src/browser.ts --format esm --dts --no-splitting --platform browser && tsup --config tsup.native.config.ts && cp ../../docs/modules-api-reference.json dist/config-reference.json",