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

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.
@@ -92,9 +92,16 @@ then transition to Mercator between zoom 10 and 12 so detailed streets remain pl
92
92
  property, or set it to `'mercator'`, for a consistently flat map.
93
93
 
94
94
  Terrain keeps the original `'none' | 'hillshade' | '3d'` shorthand. The object form can style every
95
- MapLibre hillshade paint control, or generate vector contours in the browser from an explicit DEM
96
- tile template. Contours require `demUrl`, `demMaxZoom`, and zoom-indexed `[minor, index]`
97
- `thresholds`; the raster `url` remains the separate TileJSON endpoint. Set `mode: 'none'` to compile
95
+ MapLibre hillshade paint control. Raster terrain accepts either a TileJSON `url` or explicit `tiles`
96
+ templates, with optional `tileSize` (256 or 512) and native `minZoom`/`maxZoom` limits. The templates
97
+ must contain `{z}`, `{x}` and `{y}` and use HTTPS (HTTP is allowed for loopback development).
98
+ These source limits control DEM requests independently of `hillshade.minZoom`/`maxZoom` visibility;
99
+ the renderer reuses the highest available native resolution for closer views. Select resolution
100
+ limits supported by the provider and retain its required attribution.
101
+
102
+ Contours generate vector lines in the browser from their own explicit DEM tile template. They
103
+ require `demUrl`, `demMaxZoom`, and zoom-indexed `[minor, index]` `thresholds`; these are separate
104
+ from the raster source's `url` or `tiles`. Set `mode: 'none'` to compile
98
105
  contours without adding a raster source, hillshade, or 3D terrain:
99
106
 
100
107
  ```ts
@@ -0,0 +1,200 @@
1
+ # Nautical marks
2
+
3
+ The `nautical` module draws navigation marks from a `nautical-v1` vector source: buoys, beacons,
4
+ lighthouses, lights, rocks and other hazards, wrecks, reefs, navigation areas, routes, cables,
5
+ harbours, ports, and a restrained indication of where a provider publishes marks. It is reference
6
+ cartography for general-purpose maps. It is not a nautical chart, and a map that draws it always
7
+ shows a "Not for navigation" notice.
8
+
9
+ ## Enable Nautical on a map
10
+
11
+ Nautical is opt-in through the map's `marine` option. `'nautical'` adds it alone, `'chart'` adds it
12
+ with Bathymetry, and the object form selects or replaces each member. The official Soundings map
13
+ enables it with its own chart symbols; every other official map draws it with the module's default
14
+ symbology once the map opts in.
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: 'harbour-guide',
24
+ version: 1,
25
+ extends: streets,
26
+ marine: 'nautical',
27
+ view: {center: [-1.3, 50.77], zoom: 12},
28
+ });
29
+ ```
30
+
31
+ `marine` is atomic: declaring it replaces the inherited value. Without a URL, the source is the
32
+ TileJSON at `/tiles/nautical/tiles.json` on the map's API base, with the style source ID
33
+ `tileflow-nautical`. Compiling a map that names this URL does not establish that a hosted Nautical
34
+ source is available to it. To use any schema-compatible source, name its TileJSON or PMTiles
35
+ archive and, when the TileJSON does not carry one, its attribution:
36
+
37
+ <!-- docs:check -->
38
+
39
+ ```ts
40
+ import {defineMap} from '@tileflow/core';
41
+ import {streets} from '@tileflow/maps';
42
+
43
+ export default defineMap({
44
+ id: 'harbour-guide',
45
+ version: 1,
46
+ extends: streets,
47
+ marine: {
48
+ bathymetry: false,
49
+ nautical: {
50
+ url: 'https://tiles.example.com/nautical/tiles.json',
51
+ attribution: '© Example © OpenStreetMap contributors',
52
+ },
53
+ },
54
+ });
55
+ ```
56
+
57
+ The selected TileJSON owns the provider credit. Core never invents a nautical provider or
58
+ attribution, and an explicit `attribution` replaces the one the TileJSON supplies.
59
+
60
+ ## The "Not for navigation" notice
61
+
62
+ When any layer of the compiled style reads the Nautical source, the style declares a notice in its
63
+ `tileflow:notices` metadata:
64
+
65
+ ```json
66
+ [
67
+ {
68
+ "id": "nautical-not-for-navigation",
69
+ "sources": ["tileflow-nautical"],
70
+ "text": "Not for navigation"
71
+ }
72
+ ]
73
+ ```
74
+
75
+ React, Vue, Svelte, the development preview, and standalone capture render it as a compact,
76
+ always-visible control beside the attribution, and keep it in step with style replacements. A map
77
+ that does not draw Nautical, including one whose `nautical` module is disabled, declares no notice.
78
+ The notice is separate from the source attribution, so the provider credit stays complete.
79
+
80
+ A direct MapLibre integration attaches the same control after constructing the map:
81
+
82
+ ```ts
83
+ import maplibregl from 'maplibre-gl';
84
+ import {attachTileflowNotices} from '@tileflow/core/browser';
85
+
86
+ const map = new maplibregl.Map({
87
+ container: 'map',
88
+ style: '/tileflow/styles/harbour-guide/light.json',
89
+ });
90
+ const detach = attachTileflowNotices(map, {position: 'bottom-right'});
91
+ // Call detach() to remove the control early; removing the map also removes it.
92
+ ```
93
+
94
+ `readTileflowNotices(style.metadata)` returns the bounded notices of a style for an application
95
+ that draws its own legend or exports images by other means. Show the notice wherever such a map is
96
+ displayed.
97
+
98
+ ## Source layers and fields
99
+
100
+ Every feature carries `provider`, `provenance`, `edition`, `coverage`, `licence`, and `min_zoom`,
101
+ the first zoom at which the feature appears. `rank` (1 is the most important feature of its layer)
102
+ and `name` are optional. Colours are S-57 colour names, and multi-valued colours and categories are
103
+ comma-separated in source order, for example `black,yellow`. `tileflowNauticalV1Schema` lists the
104
+ field names, layers, and geometry types.
105
+
106
+ | Source layer | Geometry | Fields the module reads |
107
+ | ----------------- | -------------------------- | -------------------------------------------------------- |
108
+ | `aid` | Point | `class`, `subclass`, `category`, `color`, `rank`, `name` |
109
+ | `light` | Point | `class` (`major` or `minor`), `color`, `label`, `rank` |
110
+ | `hazard` | Point, LineString, Polygon | `class`, `water_level`, `name` |
111
+ | `wreck` | Point, Polygon | `class`, `name` |
112
+ | `reef` | Polygon | `name` |
113
+ | `navigation_area` | Point, LineString, Polygon | `class`, `size`, `rank`, `name` |
114
+ | `coverage` | Polygon | `families` |
115
+ | `sounding` | Point | `depth` |
116
+
117
+ Every layer filters on `min_zoom`, so a compatible source that keeps features in lower-zoom tiles
118
+ still shows each one from its own first zoom. Point groups place a lower `rank` first.
119
+
120
+ ## Semantic groups
121
+
122
+ Each group is a style option of `nautical(...)`, with the same marker, symbol, area, and line style
123
+ objects as other modules.
124
+
125
+ | Option | Draws | Default symbology |
126
+ | ----------------- | ------------------------------------------------------------------------------- | --------------------------------------------------------- |
127
+ | `buoys` | `aid` buoys, light floats, and light vessels | Circle in the first colour, ringed with the second |
128
+ | `beacons` | `aid` beacons and daymarks | Small circle in the first colour |
129
+ | `lighthouses` | `aid` lighthouses | Ink circle and name |
130
+ | `aids` | Other `aid` classes, such as moorings, fog signals, and racons | Small hollow circle |
131
+ | `lights` | `light` points, one per sector | Flare in the light's first colour and its chart `label` |
132
+ | `hazards` | `hazard` points | `+` for rocks, `*` for rocks that show; circled obstacles |
133
+ | `hazardAreas` | `hazard` polygons; the outline also strokes linear hazards | Accent wash with a dotted outline |
134
+ | `wrecks` | `wreck` points | `Wk`, in the accent colour when dangerous |
135
+ | `wreckAreas` | `wreck` polygons | Ink wash with a dashed outline |
136
+ | `reefs` | `reef` polygons | Sand wash with a dotted outline |
137
+ | `navigationAreas` | `navigation_area` polygons | Dashed outlines; accent for operational areas |
138
+ | `routes` | `navigation_area` lines of class `route` and `traffic-separation` | Dashed line; accent for traffic separation |
139
+ | `cables` | `navigation_area` lines of class `cable`, `pipeline`, and their area classes | Fine dashed accent line |
140
+ | `harbours` | `navigation_area` points of class `harbour` | Ink circle and name |
141
+ | `ports` | `navigation_area` points of class `port` | Ink circle and name sized by `size`, from low zooms |
142
+ | `coverage` | `coverage` polygons that cover every family | Faint tint at low zooms only |
143
+ | `soundings` | `sounding` points | Depth figure |
144
+ | `labels` | Names inside hazard, navigation-area, reef, and wreck polygons; coverage opt-in | Collision-aware text |
145
+
146
+ Operational areas are restricted, military, anchorage, anchor-berth, dumping-ground, and
147
+ traffic-separation areas. Marks keep their conventional colours in every theme; a white light is
148
+ drawn as a warm yellow so it remains visible on pale water. Ink, halo, and accent come from the
149
+ theme's `hydro.label`, `hydro.water`, and `poi.transport` colours, so a theme can restyle Nautical
150
+ without new options. Override any group like any other module style:
151
+
152
+ <!-- docs:check -->
153
+
154
+ ```ts
155
+ import {defineMap, fixed, nautical} from '@tileflow/core';
156
+ import {streets} from '@tileflow/maps';
157
+
158
+ export default defineMap({
159
+ id: 'harbour-guide',
160
+ version: 1,
161
+ extends: streets,
162
+ marine: 'nautical',
163
+ modules: {
164
+ nautical: nautical({
165
+ buoys: {minZoom: 12},
166
+ cables: {visible: false},
167
+ navigationAreas: {
168
+ fill: {opacity: fixed(0.04, {reason: 'Keep operational areas quiet on the base map'})},
169
+ },
170
+ labels: {coverage: {visible: true}},
171
+ }),
172
+ },
173
+ });
174
+ ```
175
+
176
+ ## Chart symbols from the theme
177
+
178
+ A theme that declares the image tokens below replaces a point group's circle marker with that icon.
179
+ Buoy variants fall back to `nautical.buoy`. `tileflowNauticalImageRoles` lists the tokens.
180
+
181
+ | Token | Group |
182
+ | ------------------------- | ------------------------------------------ |
183
+ | `nautical.buoy` | `buoys` |
184
+ | `nautical.buoy.port` | Lateral buoys of a port-hand category |
185
+ | `nautical.buoy.starboard` | Lateral buoys of a starboard-hand category |
186
+ | `nautical.buoy.cardinal` | Cardinal buoys |
187
+ | `nautical.beacon` | `beacons` |
188
+ | `nautical.lighthouse` | `lighthouses` |
189
+ | `nautical.light` | `lights` |
190
+ | `nautical.aid` | `aids` |
191
+ | `nautical.wreck` | `wrecks` |
192
+ | `nautical.harbour` | `harbours` |
193
+ | `nautical.port` | `ports` |
194
+
195
+ Buoys, beacons, other aids, and lights colour their icon with the mark's own colour, so declare
196
+ them as [SDF icons](https://github.com/tileflow/tileflow-sdk/blob/main/packages/dev/docs/native-icons.md)
197
+ that share one representation within each group. The defaults size icons for the SDF profile,
198
+ whose drawing occupies the central 12 of 24 pixels; set `icon.size` for other artwork. Soundings
199
+ declares these tokens with its own buoy, light, lighthouse, wreck, and harbour symbols and keeps
200
+ beacons as coloured dots.
@@ -79,13 +79,16 @@ import {
79
79
  attachTileflowAtmosphere,
80
80
  attachTileflowFairUseNotice,
81
81
  attachTileflowMapLifecycle,
82
+ attachTileflowNotices,
82
83
  createTileflowSessionStarter,
83
84
  createTileflowTransformRequest,
84
85
  registerTileflowWorldRequestBridge,
85
86
  } from '@tileflow/core/browser';
86
87
  ```
87
88
 
88
- The browser entry is not re-exported from the root. It is SSR-safe to import, has no MapLibre
89
+ `attachTileflowNotices(map)` renders the notices a compiled style declares, such as Nautical's
90
+ "Not for navigation", as a compact map control; see
91
+ [Nautical marks](nautical-marks.md). The browser entry is not re-exported from the root. It is SSR-safe to import, has no MapLibre
89
92
  runtime dependency, and reads no browser global during module evaluation. See the
90
93
  [framework browser runtime contract](https://github.com/tileflow/tileflow-sdk/blob/main/docs/contracts/framework-browser-runtime.md) and the
91
94
  [cartographic authoring contract](https://github.com/tileflow/tileflow-sdk/blob/main/docs/contracts/cartographic-authoring.md). Exact field
@@ -6,6 +6,9 @@ glowing head. Streets and pavements the data maps as lit, but whose lamps it doe
6
6
  soft light along them that claims no lamp's place. As the theme controller's sun darkens the map,
7
7
  the lights switch on one by one; they go out as day comes.
8
8
 
9
+ Street lights are off by default in the SDK and in Streets. A compiled map must explicitly set
10
+ `roads.streetLights.enabled: true` before the renderer or preview will show lamp glows.
11
+
9
12
  It is a custom WebGL2 layer for MapLibre GL JS, drawn above the map's buildings and below its
10
13
  labels. When `createTileflowBuildings3D` raises the buildings, they hide the lamps behind them. It
11
14
  needs WebGL2 and the Mercator projection, and shines from zoom 14 by default.
@@ -13,7 +16,8 @@ needs WebGL2 and the Mercator projection, and shines from zoom 14 by default.
13
16
  ## Light the streets
14
17
 
15
18
  This example assumes an existing MapLibre map showing a Tileflow map whose vector source is called
16
- `tileflow`, as Tileflow's maps call World, and a theme controller for it:
19
+ `tileflow`, as Tileflow's maps call World, with `roads.streetLights.enabled: true` in its map
20
+ definition and a theme controller for it:
17
21
 
18
22
  <!-- docs:check -->
19
23
 
@@ -56,12 +60,12 @@ lit streets, others almost none. Where the data says nothing, the streets stay d
56
60
 
57
61
  The map describes its street lights in its roads module, next to the other details drawn from
58
62
  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:
63
+ it does. Without an enabled description the renderer stays off and mounts no layer:
60
64
 
61
65
  <!-- docs:check -->
62
66
 
63
67
  ```ts
64
- import {defineMap, refine} from '@tileflow/core';
68
+ import {defineMap, fixed, refine} from '@tileflow/core';
65
69
  import {streets} from '@tileflow/maps';
66
70
 
67
71
  export default defineMap({
@@ -71,7 +75,14 @@ export default defineMap({
71
75
  extends: streets,
72
76
  modules: {
73
77
  // 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'}}),
78
+ roads: refine({
79
+ streetLights: {
80
+ enabled: true,
81
+ color: fixed('#ffb35c', {reason: 'Constant sodium-lamp color.'}),
82
+ intensity: 1.3,
83
+ estimate: 'streets',
84
+ },
85
+ }),
75
86
  },
76
87
  });
77
88
  ```
@@ -89,11 +100,16 @@ street rather than as lamps that may not stand there.
89
100
 
90
101
  `color` is the light of the lamps whose data gives no colour, a warm white by default, and
91
102
  `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.
103
+ blend. These settings apply only after `enabled: true`; `streetLights: {enabled: false}` gives
104
+ the map none.
93
105
 
94
106
  ## Options
95
107
 
96
- The layer's own options say only how it is delivered: `minzoom` (14 by default) and `maxTiles` (25
108
+ The layer follows the compiled map by default. `createTileflowStreetLights({map, controller,
109
+ enabled: true})` explicitly enables the fallback design for a style without street-light metadata;
110
+ `enabled: false` keeps the renderer off even when the map declares lights.
111
+
112
+ The delivery options are `minzoom` (14 by default) and `maxTiles` (25
97
113
  by default, at most 64) bound where and how far the lights shine, `beforeId` places the layer before
98
114
  another one, and `transformRequest` applies the map's request rules to the tiles it reads.
99
115
 
@@ -15,8 +15,8 @@ application's own WebGL layers to draw, from the values `getSun()` returns.
15
15
  ## Describe a theme's sun
16
16
 
17
17
  Set `lighting.sun` and `lighting.ambient` in the theme. Every value is optional; unset values take
18
- the defaults in `tileflowDefaultSunPreset` (azimuth 210°, altitude 60°, white light at 0.5, full
19
- shadows, a white sky at 0.5):
18
+ the defaults in `tileflowDefaultSunPreset` (azimuth 210°, altitude 60°, white light at 0.2, full
19
+ shadow strength, a white sky at 0.8 for soft daylight shadows):
20
20
 
21
21
  <!-- docs:check -->
22
22
 
@@ -43,6 +43,46 @@ others are inherited. The compiled style carries the resolved sun in its metadat
43
43
  direction in `lighting.position` no longer apply to a theme with a sun. A theme without `sun` or
44
44
  `ambient` compiles exactly as before.
45
45
 
46
+ ## Replace inherited lighting
47
+
48
+ `defineTheme(base, definition)` normally merges lighting field by field. An empty `lighting`,
49
+ `sun`, or `ambient` object therefore preserves inherited values. Use the existing `reset()`
50
+ operation to remove a preset explicitly while keeping the base theme's palette and typography:
51
+
52
+ <!-- docs:check -->
53
+
54
+ ```ts
55
+ import {defineTheme, reset} from '@tileflow/core';
56
+ import {streetsThemes} from '@tileflow/maps';
57
+
58
+ export const studio = defineTheme(streetsThemes.light, {
59
+ id: 'studio',
60
+ version: 1,
61
+ colorScheme: 'light',
62
+ lighting: {
63
+ sun: reset(),
64
+ ambient: reset(),
65
+ anchor: 'viewport',
66
+ position: [1.15, 180, 40],
67
+ color: '#FFFFFF',
68
+ intensity: 0.15,
69
+ },
70
+ });
71
+ ```
72
+
73
+ Removing both presets lets the native extrusion light use the authored `anchor` and `position`.
74
+ Resetting only `sun` keeps an inherited ambient preset, and resetting only `ambient` keeps the sun;
75
+ the missing counterpart then uses the SDK defaults. `lighting: reset()` clears all inherited
76
+ lighting properties. A direct property such as `position`, `color`, or `intensity` can also be
77
+ reset. The result is a complete theme with those properties absent; it contains no reset nodes.
78
+ To replace a whole sun or ambient object, first derive a theme that resets that object, then derive
79
+ the replacement values from the cleared theme.
80
+
81
+ Removing a preset does not turn off the renderer's lighting, cast shadows, or ambient occlusion.
82
+ The SDK's 3D scene uses its fallback light when no preset is published. For a graphic building
83
+ style, choose the building module's `lighting: 'style'`, `shadows: false`, and `occlusion: false`
84
+ explicitly. A single static theme needs no time-of-day family or running clock.
85
+
46
86
  ## Read the sun in an application
47
87
 
48
88
  `themeController.getSun()` returns the sun the map shows:
@@ -15,6 +15,61 @@ same token keys, so changing a theme can never change map structure or silently
15
15
  role. `defineTheme(base, definition)` is an authoring convenience that returns a fully materialized
16
16
  document; it does not leave an inheritance edge in the resolved config.
17
17
 
18
+ ## Start with one appearance
19
+
20
+ A map can have one appearance for its entire lifetime. Neither `systemThemes` nor `timeOfDay` is
21
+ required. This complete theme supplies the twelve base color roles the semantic compiler needs;
22
+ copy it and change its colors to start an independent design without inheriting an official map:
23
+
24
+ <!-- docs:check -->
25
+
26
+ ```ts
27
+ import {defineTheme} from '@tileflow/core';
28
+
29
+ export const paper = defineTheme({
30
+ id: 'paper',
31
+ version: 1,
32
+ colorScheme: 'light',
33
+ tokens: {
34
+ color: {
35
+ 'surface.background': '#F6F7F3',
36
+ 'surface.land': '#F1F3ED',
37
+ 'surface.water': '#A9D3F5',
38
+ 'surface.park': '#CDE8B5',
39
+ 'surface.building': '#E6E3DA',
40
+ 'roads.default': '#FFFFFF',
41
+ 'roads.major': '#F4C95D',
42
+ 'roads.casing': '#D9DEE2',
43
+ 'boundaries.default': '#C9D1D9',
44
+ 'labels.primary': '#3C4043',
45
+ 'labels.muted': '#727B84',
46
+ 'labels.halo': '#FFFFFF',
47
+ },
48
+ },
49
+ });
50
+ ```
51
+
52
+ An existing map definition can select this appearance with `themes: {paper}` and
53
+ `defaultTheme: 'paper'`. Supply that map's data and font assets separately; the default typography
54
+ uses `Noto Sans Regular`. A theme without these base roles is a valid appearance document but cannot
55
+ compile a semantic map. Colors above are an editable starting point, including for monochrome,
56
+ historical, dark-only, or imagined worlds; `colorScheme` describes the appearance for consumers and
57
+ does not generate a palette or choose a time of day.
58
+
59
+ The compiler derives more specific roles when they are absent. For example, primary and motorway
60
+ roads initially use `roads.major`; declaring `roads.motorway` replaces only that derived color.
61
+ Land-use fills, road casings, and label colors have their own semantic roles, so an explicit role
62
+ always wins over its fallback. Use `color.mix()` and `token.color()` in a theme when the relationship
63
+ itself should be authored and inspectable. Derivations resolve during compilation, without a clock
64
+ or a per-frame palette calculation.
65
+
66
+ Add named appearances when the map needs them. A day/night pair does not need dawn or dusk, and a
67
+ theme named `paper` or `fantasy` does not need a temporal family. Declare `timeOfDay` only when the
68
+ application should offer those artistic moments; see
69
+ [time-of-day selection](https://github.com/tileflow/tileflow-sdk/blob/main/packages/core/docs/theme-transitions-and-blends.md#time-of-day).
70
+
71
+ ## Compose and replace appearance values
72
+
18
73
  Some optional tokens are for the application rather than the map's own layers. Route inks
19
74
  (`route.line`, `route.casing`, `route.core`, `route.glow`, `route.alternative`,
20
75
  `route.glow.opacity` and `route.core.opacity`) set how an application draws its routes; a theme
@@ -83,6 +138,17 @@ deterministic OKLCH `color.mix()` keep derived palette logic inspectable. Unknow
83
138
  cross-category refs, token-schema drift, and unresolved visual nodes fail before Style JSON is
84
139
  emitted.
85
140
 
141
+ Theme lighting also supports explicit removal during `defineTheme` composition. Use
142
+ `lighting: reset()` to remove the entire inherited lighting configuration, or
143
+ `lighting: {sun: reset(), ambient: reset()}` to remove those presets while retaining other light
144
+ properties. The same operation can remove a direct property such as `position` or `color`.
145
+ Omitting a property, or supplying an empty object, continues to inherit it. Reset operations are
146
+ materialized immediately and never enter the resolved theme or Style JSON. Removing a lighting
147
+ preset restores the renderer's fallback; it does not disable 3D shadows or occlusion. Those remain
148
+ explicit building settings. See
149
+ [sun and light](https://github.com/tileflow/tileflow-sdk/blob/main/packages/core/docs/sun-and-light.md#replace-inherited-lighting)
150
+ for a complete example.
151
+
86
152
  There is no physical-layer override for the Tileflow basemap or separate official-map authoring
87
153
  surface. Basemap behavior belongs to a typed control or owner-local render stack. Every
88
154
  official and application map therefore follows the same schema, inheritance rules, diagnostics,
package/docs/trees-3d.md CHANGED
@@ -3,7 +3,7 @@
3
3
  `createTileflowTrees3D` stands the trees of a Tileflow map's vector tiles round the camera as 3D
4
4
  trees, lit by the theme controller's sun. Each tree takes the form and the size its data gives:
5
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,
6
+ has them. The data's hedges stand beside them as clipped green walls. Trees cast shadows on the ground and, with `createTileflowBuildings3D`, on the buildings,
7
7
  and take the buildings' shadows; nearer buildings hide them.
8
8
 
9
9
  The map describes how its trees look; the renderer only stands them. A map that declares
@@ -107,7 +107,7 @@ sunk or the layer leaves the map.
107
107
 
108
108
  `stats.declared` tells whether the map's style describes 3D trees. When it is false the layer draws
109
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.
110
+ placed, the pieces of hedge placed and the trees drawn in the last frame.
111
111
 
112
112
  ## What the trees come from
113
113
 
@@ -120,6 +120,10 @@ The renderer reads the source's z15 tiles round the camera:
120
120
  - World's `canopy` layer, where the data has it: decorative points in urban parks and gardens, each
121
121
  standing one to three trees by its canopy cover, inside the room it leaves before a path, a
122
122
  building or the park's edge. They stand for measured canopy, not for mapped trees.
123
+ - World's `traffic_control` layer: its hedges (`subclass` `hedge`), the lines OpenStreetMap maps and
124
+ the hedges it maps as areas. A hedge stands as pieces 1.2 metres apart along each line and round
125
+ each area, 0.8 metres thick, at its `height` or 1.3 metres, a metre higher where `hedge` says
126
+ `hedge_bank`. The style still draws the hedge flat underneath, and fences and walls stay flat.
123
127
 
124
128
  | Form | From the data |
125
129
  | ---------- | ---------------------------------------------------------------------------- |
@@ -139,8 +143,12 @@ tile's buffer belong to the neighbouring tile, so no tree stands twice.
139
143
 
140
144
  Colours come from the theme: `vegetation.tree.bark`, `vegetation.tree.broadleaf.a` to `.d`,
141
145
  `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:
146
+ lightest. Hedges take `hedgeColors`, the two darkest broadleaf colours unless the map gives its own;
147
+ Streets gives its flat hedge's colours, so a hedge keeps its colour as it rises. `hedges: false`
148
+ leaves hedges flat. A crown facing a sunlit sky takes its colour; shade takes the sky's light, as on 3D
149
+ buildings. At night a neutral lift keeps crown tops visible; grey materials stay grey under neutral
150
+ lighting, while coloured scene lighting can tint them. The vegetation module's `threeDimensional`
151
+ options refine them for one map:
144
152
 
145
153
  <!-- docs:check -->
146
154
 
@@ -239,16 +247,20 @@ the same name; omitting `models` inherits them, and `[]` declares none.
239
247
  The light comes from `controller.getSun()`: the theme's sun, mixed during a blend, or the sun an
240
248
  application places by direction or by date and place (see
241
249
  [sun and light](https://github.com/tileflow/tileflow-sdk/blob/main/packages/core/docs/sun-and-light.md)).
250
+ Without a controller, trees read the compiled style's sun and light direction, falling back to
251
+ SDK daylight, just as 3D buildings do. This cached light refreshes when the style changes.
242
252
  Leaves take light round their edges and glow a little against a low sun; the underside and the
243
253
  inside of a crown stay darker.
244
254
 
245
255
  Trees and 3D buildings on one map share one shadow map: trees cast shadows on the ground and on the
246
256
  buildings, and stand in the buildings' shade. The flat map darkens under both. Without 3D buildings
247
- the trees darken the map themselves.
257
+ the trees darken the map themselves. The depth pass uses the same visible tree ranks, near/far
258
+ models and distance fade as the colour pass, so hidden trees do not leave full shadows behind.
259
+ Shadow direction stops lowering at 15° above the horizon; the actual scene light keeps its angle.
248
260
 
249
261
  ## Options
250
262
 
251
- `minzoom` (15 by default) and `maxTiles` (25 by default, at most 64) bound where and how far trees
263
+ `minzoom` (15 by default, fading to full coverage at 15.25 like buildings) and `maxTiles` (25 by default, at most 64) bound where and how far trees
252
264
  stand; trees thin out and fade with distance from the map's centre, and near trees take more detail
253
265
  than far ones. `canopySource` names a vector source with canopy points when they are not in the
254
266
  map's World source. `beforeId` places the layer before another one; `transformRequest` applies the
@@ -258,9 +270,10 @@ tile's download.
258
270
  ## Cost
259
271
 
260
272
  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.
273
+ tile. The shared shadow map concentrates resolution on the view when its public camera methods
274
+ provide a safe planar fit. It is drawn again when that focused area, the light, visible tree
275
+ selection, fading or geometry changes. Unchanged views reuse it. Over the Champ de Mars in Paris,
276
+ where OpenStreetMap maps every tree, about 13,000 trees stand in a pitched view at zoom 16.6.
264
277
 
265
278
  ## Limits
266
279
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tileflow/core",
3
- "version": "0.1.0-alpha.35",
3
+ "version": "0.1.0-alpha.37",
4
4
  "license": "Apache-2.0",
5
5
  "description": "Typed Tileflow config and MapLibre style compiler.",
6
6
  "type": "module",
@@ -90,7 +90,7 @@
90
90
  "whatwg-url": "17.2.0"
91
91
  },
92
92
  "scripts": {
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",
93
+ "build": "tsup --config tsup.build.config.ts && tsup --config tsup.native-profile.config.ts && tsup --config tsup.browser.config.ts && tsup --config tsup.native.config.ts && cp ../../docs/modules-api-reference.json dist/config-reference.json",
94
94
  "reference:check": "tsx scripts/generate-config-reference.ts --check",
95
95
  "reference:generate": "tsx scripts/generate-config-reference.ts --write",
96
96
  "verify": "pnpm run build && tsx --test test/*.test.ts",