@tileflow/core 0.1.0-alpha.30 → 0.1.0-alpha.32
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.
- package/README.md +11 -1
- package/dist/browser.d.ts +747 -15
- package/dist/browser.js +1015 -53
- package/dist/build.d.ts +62 -55
- package/dist/build.js +100 -53
- package/dist/{capture-scene-D6soWztE.d.ts → capture-scene-BaAJjGq7.d.ts} +11 -1
- package/dist/capture.d.ts +1 -1
- package/dist/capture.js +12 -0
- package/dist/config-reference.json +1411 -1367
- package/dist/{icon-composition-BiOTwrvD.d.ts → icon-composition-Befmn-zZ.d.ts} +40 -32
- package/dist/index.d.ts +13 -11
- package/dist/index.js +108 -53
- package/dist/{manifest-types-CTT2eLbp.d.ts → manifest-types-E7m23eXL.d.ts} +18 -1
- package/dist/manifest.d.ts +2 -2
- package/dist/native-profile.d.ts +25 -11
- package/dist/native-profile.js +21 -1
- package/dist/native.d.ts +134 -1
- package/dist/native.js +691 -1
- package/dist/runtime.d.ts +1 -1
- package/docs/authoring-model.md +20 -4
- package/docs/capture-scenes.md +1 -1
- package/docs/compiled-style-performance.md +4 -1
- package/docs/data-is-separate-from-design.md +103 -9
- package/docs/dynamic-style-images.md +100 -0
- package/docs/map-emphasis.md +89 -0
- package/docs/native-artifact-profile.md +5 -3
- package/docs/shared-visual-primitives.md +20 -0
- package/docs/style-states.md +73 -0
- package/docs/theme-transitions-and-blends.md +100 -5
- package/package.json +1 -1
package/dist/runtime.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { z as TileflowAnalytics, $ as TileflowManifestFetchOptions, a0 as TileflowManifestLoadOptions, a1 as TileflowMapMode, a2 as TileflowMapModeOptions, ae as TileflowResolvedRuntimeTheme, aB as TileflowRuntimeCenterLike, a as TileflowRuntimeColorScheme, T as TileflowRuntimeManifest, aC as TileflowRuntimeManifestMap, b as TileflowRuntimeManifestMapEntry, c as TileflowRuntimeManifestTheme, aD as TileflowRuntimeSource, aE as TileflowRuntimeSourceValidation, aF as TileflowRuntimeStyle, aG as TileflowRuntimeStyleOptions, d as TileflowRuntimeSystemThemes, aH as TileflowRuntimeViewOptions, aI as TileflowSessionController, aJ as TileflowSessionGrantResponse, aM as TileflowStyleFontFace,
|
|
1
|
+
export { z as TileflowAnalytics, $ as TileflowManifestFetchOptions, a0 as TileflowManifestLoadOptions, a1 as TileflowMapMode, a2 as TileflowMapModeOptions, ae as TileflowResolvedRuntimeTheme, aB as TileflowRuntimeCenterLike, a as TileflowRuntimeColorScheme, T as TileflowRuntimeManifest, aC as TileflowRuntimeManifestMap, b as TileflowRuntimeManifestMapEntry, c as TileflowRuntimeManifestTheme, aD as TileflowRuntimeSource, aE as TileflowRuntimeSourceValidation, aF as TileflowRuntimeStyle, aG as TileflowRuntimeStyleOptions, d as TileflowRuntimeSystemThemes, aH as TileflowRuntimeViewOptions, aI as TileflowSessionController, aJ as TileflowSessionGrantResponse, aM as TileflowStyleFontFace, b5 as TileflowThemeBlendSelection, bc as TileflowThemeSelection, bk as assertValidTileflowRuntimeSource, bl as clearTileflowManifestCache, bn as createTileflowSessionController, bo as createTileflowSessionId, bp as defaultTileflowManifestUrl, bq as defaultTileflowRuntimeView, bs as getTileflowAnalyticsApiUrl, bt as getTileflowStyleFontFaces, bu as inferTileflowAnalyticsFromStyleUrl, bv as loadTileflowManifest, bw as mergeTileflowAnalytics, bx as nearestTileflowBlendTheme, by as normalizeTileflowRuntimeCenter, bz as normalizeTileflowStaticImageSize, bX as normalizeTileflowSurfaceId, bA as normalizeTileflowUrl, bB as resolveTileflowAnalyticsRequestUrl, bC as resolveTileflowManifestMap, bD as resolveTileflowMapMode, bE as resolveTileflowRuntimeStyle, bF as resolveTileflowRuntimeTheme, bG as resolveTileflowRuntimeThemeBlend, bH as resolveTileflowRuntimeView, bI as resolveTileflowStaticImageUrl, bJ as shouldLoadTileflowManifest, bK as startTileflowSession, bM as tileflowStyleFontFaceLimits, bN as tileflowStyleFontFacesMetadataKey, bO as tileflowThemeBlendLimits, bR as validateTileflowRuntimeSource, bS as validateTileflowThemeBlendSelection, bT as validateTileflowThemeSelection } from './manifest-types-E7m23eXL.js';
|
package/docs/authoring-model.md
CHANGED
|
@@ -173,6 +173,21 @@ const numericIdentity = expr.abs(expr.toNumber(expr.id(), 0));
|
|
|
173
173
|
const decorativeBucket = expr.modulo(numericIdentity, 7);
|
|
174
174
|
```
|
|
175
175
|
|
|
176
|
+
`expr.featureState(name)` reads a value an application sets on one feature at runtime, such as a
|
|
177
|
+
selection or a hover. `expr.globalState(name)` reads a value an application sets once for the whole
|
|
178
|
+
style with MapLibre's `map.setGlobalStateProperty(name, value)`, such as whether anything is
|
|
179
|
+
selected. Both read `null` until set, so wrap them in a typed assertion with a fallback. MapLibre
|
|
180
|
+
applies a global-state change to paint properties at once, without the style's paint transition.
|
|
181
|
+
|
|
182
|
+
<!-- docs:check -->
|
|
183
|
+
|
|
184
|
+
```ts
|
|
185
|
+
import {expr} from '@tileflow/core';
|
|
186
|
+
|
|
187
|
+
const selecting = expr.toBoolean(expr.globalState('selection'), false);
|
|
188
|
+
const captionInk = expr.case([{when: selecting, value: '#1f2a44'}], '#ffffff');
|
|
189
|
+
```
|
|
190
|
+
|
|
176
191
|
The `land` module exposes stable land-use targets for `cemetery`, `civic`, `commercial`,
|
|
177
192
|
`education`, `government`, `industrial`, `medical`, `military`, `parking`, `railway`,
|
|
178
193
|
`recreation`, and `residential`. Its land-cover taxonomy distinguishes physical cover from authored
|
|
@@ -205,10 +220,11 @@ The `vegetation` module binds individual-tree points from the optional OpenMapTi
|
|
|
205
220
|
extension. Its default `mode: '3d'` emits a portable circle fallback plus metadata for runtimes
|
|
206
221
|
that can upgrade the points to instanced 3D trees. `mode: 'flat'` keeps the MapLibre circles, and
|
|
207
222
|
`flat` exposes the complete `CircleStyle` fallback. `threeDimensional` controls bark color,
|
|
208
|
-
broadleaf and conifer palettes, and independent height and crown scales.
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
223
|
+
broadleaf and conifer palettes, and independent height and crown scales. `flat.minZoom` sets the
|
|
224
|
+
first zoom with trees. The binding includes height, crown diameter, genus, leaf type, and species
|
|
225
|
+
fields so compatible runtimes can preserve source measurements and botanical form without
|
|
226
|
+
hard-coding raw property names. The module does not draw the unrelated, decorative `canopy` layer;
|
|
227
|
+
render passes can, as in the [canopy grove recipe](data-is-separate-from-design.md). The
|
|
212
228
|
pitched-scene stack follows physical height: pedestrian and transport surfaces, transport markings,
|
|
213
229
|
buildings, then vegetation, so street paint never floats over 3D geometry. Text draws last: road
|
|
214
230
|
names, route shields, junction numbers, and place, water, aerodrome, and POI annotations stay above
|
package/docs/capture-scenes.md
CHANGED
|
@@ -25,7 +25,7 @@ export default defineMap({
|
|
|
25
25
|
});
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
-
Use `tileflow preview --scene madrid-desktop`
|
|
28
|
+
Use `tileflow preview --scene madrid-desktop` for live review and
|
|
29
29
|
`tileflow capture madrid-desktop` for exact pixels and a schema-version-4 receipt containing the
|
|
30
30
|
concrete theme plus Streets, data, style, renderer, and image identities. World capture resolves the selected TileJSON
|
|
31
31
|
once and records the exact `world-v1` release plus descriptor/archive/data-contract hashes;
|
|
@@ -237,7 +237,10 @@ canonical `poi` layer exposes `category`, `type`, `icon`, `min_zoom`, `filter_ra
|
|
|
237
237
|
Core validates that complete contract and never reconstructs those decisions from OpenMapTiles
|
|
238
238
|
`class`, `subclass`, or `rank`. `filter_rank` is an
|
|
239
239
|
integer from 0 through 5, with lower values reserved for the strongest candidates. The numeric
|
|
240
|
-
`density` option is an inclusive threshold from 1 through 5 and defaults to 3.
|
|
240
|
+
`density` option is an inclusive threshold from 1 through 5 and defaults to 3. World publishes
|
|
241
|
+
every eligible POI at z15, its last POI zoom, and higher zooms overzoom those tiles with more room,
|
|
242
|
+
so the threshold rises by one rank at z16 and by two from z17. At the default density every rank is
|
|
243
|
+
eligible from z17, and collision placement keeps what fits. `size_rank` is an
|
|
241
244
|
integer from 0 through 16 used to order eligible candidates before MapLibre performs final
|
|
242
245
|
collision placement.
|
|
243
246
|
|
|
@@ -128,9 +128,7 @@ published, immutable glyph collection independent of World data. The older `/fon
|
|
|
128
128
|
remains a mutable compatibility URL and does not make a resolved map byte-reproducible. The global
|
|
129
129
|
glyph `assetSetSha256` is not the same-domain value as the per-map `assetSetSha256` in
|
|
130
130
|
`build-manifest.json`.
|
|
131
|
-
|
|
132
|
-
reference the exact local faces `Oxanium Medium` and `Oxanium SemiBold`. Baedeker and Siegfried
|
|
133
|
-
each own a packaged directory containing Cormorant Garamond Regular, SemiBold, and Italic.
|
|
131
|
+
Baedeker and Siegfried each own a packaged directory containing Cormorant Garamond Regular, SemiBold, and Italic.
|
|
134
132
|
|
|
135
133
|
Name the official generation deliberately, or use another OpenMapTiles-compatible vector source:
|
|
136
134
|
|
|
@@ -320,21 +318,117 @@ explicit because the data contract cannot assume a sprite name. They require pos
|
|
|
320
318
|
`markings` evidence and reject `crossing=no`; both fields are semantic bindings, so custom schemas
|
|
321
319
|
can remap them without weakening that rule.
|
|
322
320
|
|
|
321
|
+
Datasets may also publish the optional, decorative `canopy` layer: points inside urban parks and
|
|
322
|
+
gardens, at most one per cell of a grid of about 20 m that measured tree canopy covers. Each point
|
|
323
|
+
tells how much room a drawn grove of tree crowns has and how dense the canopy around it is. The
|
|
324
|
+
points are not surveyed trees, are unrelated to the optional `tree` layer, and are not search or
|
|
325
|
+
interaction targets. `openMapTiles()` and `tileflowWorldV1Schema()` bind the layer as `canopy` and
|
|
326
|
+
its numeric fields as follows:
|
|
327
|
+
|
|
328
|
+
- `canopyRank` (`rank`): a stable integer from 0 through 15. Lower ranks appear at lower zooms, and
|
|
329
|
+
the rank can vary artwork.
|
|
330
|
+
- `canopyClearanceAtZoom15` (`clearance_px_z15`): 4, 6, 8, 12, 16, or 24. This is the lower bound
|
|
331
|
+
of the distance from the point to the nearest edge a crown must not cover, such as a park edge,
|
|
332
|
+
water, a building, a plaza, a pitch, or a roadway, in pixels of a 512-pixel tile at zoom 15. A
|
|
333
|
+
grove fits at zoom `z` when its drawn reach in screen pixels is at most
|
|
334
|
+
`clearance_px_z15 × 2^(z − 15)`.
|
|
335
|
+
- `canopyCoverPercent` (`cover_pct`): 3, 30, or 60, the lower bound of the share, in percent, of
|
|
336
|
+
canopy taller than 3 m within 10 m of the point. The field is absent where no measurement exists;
|
|
337
|
+
treat absence as a middle density.
|
|
338
|
+
|
|
339
|
+
Features are MultiPoints, one per identical set of attributes. Native zoom 15 holds every point,
|
|
340
|
+
zoom 14 holds the points whose rank is below 8 at the same positions, and clients overzoom zoom 15.
|
|
341
|
+
`validateTileflowWorldV1Tilejson(...)` accepts a TileJSON without `canopy`; once declared, the layer
|
|
342
|
+
must occur exactly once, declare native z14–z15, and advertise Number `rank`, `clearance_px_z15`, and
|
|
343
|
+
`cover_pct`. No module draws the layer. A source without it remains valid, and render passes bound
|
|
344
|
+
to it draw nothing. `capabilities: {canopy: false}` removes the binding, after which a pass with
|
|
345
|
+
`feature: 'canopy'` fails with a missing-binding error.
|
|
346
|
+
|
|
347
|
+
Draw groves with symbol render passes, one grove of whole crowns per point, sized to its room:
|
|
348
|
+
|
|
349
|
+
<!-- docs:check -->
|
|
350
|
+
|
|
351
|
+
```ts
|
|
352
|
+
import {expr, field, fixed, renderPass, token, vegetation, withRenderStack} from '@tileflow/core';
|
|
353
|
+
|
|
354
|
+
// The grove artwork reaches 32 pixels from its center at icon size 1.
|
|
355
|
+
const clearance = expr.get(field('canopyClearanceAtZoom15'));
|
|
356
|
+
// The size that fills the room at one zoom, never larger than the artwork itself.
|
|
357
|
+
const fit = (divisor: number) => expr.min(expr.divide(clearance, divisor), 1);
|
|
358
|
+
|
|
359
|
+
const groves = (minZoom: number, fromRank: number, toRank: number) =>
|
|
360
|
+
renderPass({
|
|
361
|
+
placement: 'above-vegetation',
|
|
362
|
+
feature: 'canopy',
|
|
363
|
+
renderer: 'symbol',
|
|
364
|
+
selector: {
|
|
365
|
+
kind: 'all',
|
|
366
|
+
selectors: [
|
|
367
|
+
{field: 'canopyRank', kind: 'compare', operator: 'gte', value: fromRank},
|
|
368
|
+
{field: 'canopyRank', kind: 'compare', operator: 'lt', value: toRank},
|
|
369
|
+
],
|
|
370
|
+
},
|
|
371
|
+
style: {
|
|
372
|
+
minZoom,
|
|
373
|
+
icon: {
|
|
374
|
+
ignorePlacement: true,
|
|
375
|
+
image: token.image('vegetation.canopy.grove'),
|
|
376
|
+
overlap: 'always',
|
|
377
|
+
size: expr.interpolate({base: 2, kind: 'exponential'}, expr.zoom(), [
|
|
378
|
+
[14, fit(64)],
|
|
379
|
+
[15, fit(32)],
|
|
380
|
+
[16, fit(16)],
|
|
381
|
+
[17, fit(8)],
|
|
382
|
+
[18, fit(4)],
|
|
383
|
+
]),
|
|
384
|
+
},
|
|
385
|
+
text: {
|
|
386
|
+
field: 'o',
|
|
387
|
+
font: token.font('default'),
|
|
388
|
+
opacity: fixed(0, {reason: 'The core text only decides placement'}),
|
|
389
|
+
overlap: 'cooperative',
|
|
390
|
+
size: fixed(8, {reason: 'Matches the solid core of the grove artwork'}),
|
|
391
|
+
},
|
|
392
|
+
},
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
export const canopyGroves = withRenderStack(vegetation(), {
|
|
396
|
+
groves14: groves(14, 0, 4),
|
|
397
|
+
groves15: groves(15, 4, 8),
|
|
398
|
+
groves16: groves(16, 8, 16),
|
|
399
|
+
});
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
- Give each rank range its own pass with `minZoom`, so its points appear whole at their entry zoom.
|
|
403
|
+
MapLibre evaluates expressions that combine zoom and feature data at whole zoom levels and
|
|
404
|
+
interpolates between them, so a fade per feature would leave symbols half transparent. Zoom 14
|
|
405
|
+
tiles carry the ranks below 8, so the first pass may cover ranks 0–7.
|
|
406
|
+
- Size each grove from `canopyClearanceAtZoom15`. Here the size is `clearance / 32` at zoom 15 and
|
|
407
|
+
doubles with each zoom until the grove reaches the artwork's size, which keeps the reach within
|
|
408
|
+
the room.
|
|
409
|
+
- Collision tests an icon's whole image, including the transparent margin around the crowns. A small
|
|
410
|
+
invisible text the size of the grove's core therefore decides placement with
|
|
411
|
+
`overlap: 'cooperative'`, while the icon draws with `overlap: 'always'` and `ignorePlacement: true`.
|
|
412
|
+
Groves may overlap each other but give way to labels, which draw above the vegetation band and are
|
|
413
|
+
therefore placed first, with the default `never`.
|
|
414
|
+
|
|
415
|
+
Every theme of the map must define the image token, and the map's icon directories must provide the
|
|
416
|
+
artwork. Native artifacts replace `cooperative` with `allowOverlap: true`, so there groves can cover
|
|
417
|
+
labels; see [symbol overlap modes](shared-visual-primitives.md#symbol-overlap-modes).
|
|
418
|
+
|
|
323
419
|
The optional `business_corridor` extension contains activity-selected source footprints below
|
|
324
420
|
buildings. Its `activity_score`, `rank`, `min_zoom`, and `confidence` fields control a quiet local
|
|
325
421
|
warm tint without drawing POI-radius circles. Current building footprints may carry sparse
|
|
326
422
|
`building_tone=commercial|destination|active`; absence means the neutral base building. The three
|
|
327
423
|
values preserve why the warm tone was selected even when a theme maps them to the same color.
|
|
328
|
-
Building visibility is fixed by the layer zoom contract and never by optional semantics.
|
|
329
|
-
|
|
330
|
-
compatibility path, but new candidates do not emit those fields. The land module also exposes
|
|
424
|
+
Building visibility is fixed by the layer zoom contract and never by optional semantics. The land
|
|
425
|
+
module also exposes
|
|
331
426
|
`medical`, `education`, and `government` independently instead of collapsing their already distinct
|
|
332
427
|
source classes into one civic color.
|
|
333
428
|
|
|
334
429
|
World and text assets are independent contracts. `tileflowWorld()` selects `world-v1/current` or an
|
|
335
|
-
exact `releaseId + descriptorSha256`; a `glyphs` declaration contains its own complete URL.
|
|
336
|
-
|
|
337
|
-
remain usable because each official map owns or inherits a URL or packaged-font provider.
|
|
430
|
+
exact `releaseId + descriptorSha256`; a `glyphs` declaration contains its own complete URL. Every
|
|
431
|
+
official map compiles as imported because it owns or inherits a URL or packaged-font provider.
|
|
338
432
|
URL-backed maps become exact-byte reproducible
|
|
339
433
|
when the immutable global base-asset set is published and their explicit URL is updated to its
|
|
340
434
|
`assetSetSha256` path; no World or compiler fallback participates in that rollout. The global
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# Dynamic style images
|
|
2
|
+
|
|
3
|
+
Use `registerImage` on an existing `TileflowThemeController` from `@tileflow/core/browser` when an
|
|
4
|
+
application draws RGBA pixels that depend on a theme or style state. The controller owns the image's
|
|
5
|
+
registration, refresh and restoration. The application owns its drawing and prepares asynchronous
|
|
6
|
+
assets before registration. This browser API is tested with MapLibre GL JS 6.13; it is not a Native
|
|
7
|
+
or Static Maps image contract.
|
|
8
|
+
|
|
9
|
+
Start with the [Core guide](https://github.com/tileflow/tileflow-sdk/blob/main/packages/core/README.md)
|
|
10
|
+
and [theme controller setup](theme-transitions-and-blends.md#use-the-core-controller).
|
|
11
|
+
The map's symbol layer must already refer to the same image ID in every theme. Register before its
|
|
12
|
+
first placement. Choose an ID that does not exist in the sprite or belong to another application
|
|
13
|
+
resource. Registering an image does not create a layer or add points to a source.
|
|
14
|
+
|
|
15
|
+
## Draw an alert from state
|
|
16
|
+
|
|
17
|
+
This helper runs after browser mounting, with the controller for the existing map. The image is
|
|
18
|
+
64 physical pixels at pixel ratio 2, so it occupies 32 logical pixels before the layer's icon scale.
|
|
19
|
+
|
|
20
|
+
<!-- docs:check -->
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
import type {TileflowThemeController} from '@tileflow/core/browser';
|
|
24
|
+
|
|
25
|
+
export function registerAlert(controller: TileflowThemeController) {
|
|
26
|
+
const canvas = document.createElement('canvas');
|
|
27
|
+
canvas.width = canvas.height = 64;
|
|
28
|
+
const context = canvas.getContext('2d', {willReadFrequently: true});
|
|
29
|
+
if (!context) throw new Error('Canvas 2D is unavailable.');
|
|
30
|
+
|
|
31
|
+
return controller.registerImage('live-alert', {
|
|
32
|
+
width: 64,
|
|
33
|
+
height: 64,
|
|
34
|
+
pixelRatio: 2,
|
|
35
|
+
states: ['alert'],
|
|
36
|
+
render({theme, states}) {
|
|
37
|
+
context.clearRect(0, 0, 64, 64);
|
|
38
|
+
context.beginPath();
|
|
39
|
+
context.arc(32, 32, 24, 0, Math.PI * 2);
|
|
40
|
+
context.fillStyle = states.alert ? '#ee624e' : '#edbd53';
|
|
41
|
+
context.fill();
|
|
42
|
+
context.strokeStyle = theme === 'night' ? '#ffffff' : '#253b42';
|
|
43
|
+
context.lineWidth = 4;
|
|
44
|
+
context.stroke();
|
|
45
|
+
return context.getImageData(0, 0, 64, 64).data;
|
|
46
|
+
},
|
|
47
|
+
onError(error) {
|
|
48
|
+
console.error('Alert image could not refresh:', error);
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export async function activateAlert(controller: TileflowThemeController) {
|
|
54
|
+
await controller.setState('alert', true);
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
The resource receives the states listed in `states`, including intermediate values during numeric
|
|
59
|
+
state transitions. `theme` is the committed theme, or the nearest theme of a blend. `blend` exposes
|
|
60
|
+
its position and ordered theme names for continuous artwork interpolation. States not set through
|
|
61
|
+
this controller remain undefined; an explicit null is distinct from an unset state.
|
|
62
|
+
|
|
63
|
+
## Update and release
|
|
64
|
+
|
|
65
|
+
Theme changes and changes to declared states schedule a refresh. Identical inputs do no work;
|
|
66
|
+
several changes before the next animation frame coalesce. The `setState` promise describes the
|
|
67
|
+
state transition, not completion of the next image frame. There is no independent animation clock.
|
|
68
|
+
For camera values, new application data or another external input, call the returned handle's
|
|
69
|
+
`refresh()`. Read those values inside `render`, and return `undefined` when the existing pixels are
|
|
70
|
+
still correct. Undefined output on the first render creates a transparent image.
|
|
71
|
+
|
|
72
|
+
`render` is synchronous and returns exactly `width * height * 4` RGBA bytes as a `Uint8Array` or
|
|
73
|
+
`Uint8ClampedArray`. The controller copies the buffer. Width and height must be integers from 1
|
|
74
|
+
through 2048; pixel ratio must be positive and finite. All three remain fixed until disposal.
|
|
75
|
+
Changing pixel ratio while keeping physical size would change logical size and collision geometry,
|
|
76
|
+
so choose it at registration rather than following device pixel ratio changes automatically.
|
|
77
|
+
|
|
78
|
+
A style reload restores the last valid image before placement. WebGL context loss suspends image
|
|
79
|
+
work; restoration replays pixels and processes pending inputs. `handle.dispose()` removes only
|
|
80
|
+
that resource's image and cancels its pending work. `controller.dispose()` releases all of its
|
|
81
|
+
images. Map removal cancels work and releases listeners. Disposal is idempotent.
|
|
82
|
+
|
|
83
|
+
Invalid registration throws before claiming a resource. Later render failures keep the last valid
|
|
84
|
+
pixels and call `onError`, or `console.error` when omitted. A later refresh can retry. Another image
|
|
85
|
+
owner is never silently overwritten. The application must not call `addImage`, `updateImage` or
|
|
86
|
+
`removeImage` for a registered ID. Dynamic IDs must stay identical across blended themes; mixing
|
|
87
|
+
them with theme-specific atlas artwork is rejected before the blend replaces the style.
|
|
88
|
+
|
|
89
|
+
## Placement and scope
|
|
90
|
+
|
|
91
|
+
This API changes image pixels only. It does not resize icons, split layers, change padding,
|
|
92
|
+
anchors, sort order, overlap, filters or source features. Native symbol placement and collisions
|
|
93
|
+
remain governed by the authored style. Transparent pixels still occupy the image's existing
|
|
94
|
+
geometry: this API does not define separate visual and collision bounds.
|
|
95
|
+
|
|
96
|
+
All features using an ID share its pixels and state. Independent per-feature animation, SDF
|
|
97
|
+
generation, image loading, GPU composition and automatic framework props are outside this API.
|
|
98
|
+
Keep every selectable icon in the same symbol layer RGBA, including fallbacks; mixing these images
|
|
99
|
+
with SDF icons does not become supported through registration.
|
|
100
|
+
Profile the application's drawing cost and visible symbol rendering on its target devices.
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# Map emphasis
|
|
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
|
+
An application that draws its own content over a Tileflow map, such as a route, a selected place, or
|
|
6
|
+
search results, often needs the map to step back so that content reads first. Map emphasis does
|
|
7
|
+
that on the map's own MapLibre instance: chosen modules recede towards the map's ground colour, and
|
|
8
|
+
the map's labels and icons fade, except inside an area the application keeps at full strength. The
|
|
9
|
+
change eases in and out, stays when the theme changes, and follows a theme blend.
|
|
10
|
+
|
|
11
|
+
Emphasis needs a browser document and MapLibre GL JS. It changes only the layers a Tileflow map
|
|
12
|
+
compiled, so an application's own layers, such as the route it draws, keep their appearance.
|
|
13
|
+
|
|
14
|
+
## Show an emphasis
|
|
15
|
+
|
|
16
|
+
Create the theme controller for the map, then ask for an emphasis. This example assumes an existing
|
|
17
|
+
MapLibre map named `map` that shows the `dark` theme of a published Tileflow map, and a route the
|
|
18
|
+
application has already drawn:
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
import type {Map as MapLibreMap} from 'maplibre-gl';
|
|
22
|
+
import {createTileflowThemeController} from '@tileflow/core/browser';
|
|
23
|
+
|
|
24
|
+
export function focusOnRoute(map: MapLibreMap, styleUrl: string) {
|
|
25
|
+
const controller = createTileflowThemeController({
|
|
26
|
+
initial: {style: styleUrl, theme: 'dark'},
|
|
27
|
+
map,
|
|
28
|
+
});
|
|
29
|
+
void controller.setEmphasis(
|
|
30
|
+
{
|
|
31
|
+
labels: 0.6,
|
|
32
|
+
recede: {boundaries: 0.55, roads: 0.5, transit: 0.5},
|
|
33
|
+
},
|
|
34
|
+
{transition: {duration: 560}},
|
|
35
|
+
);
|
|
36
|
+
return () => controller.setEmphasis(undefined, {transition: {duration: 560}});
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Roads, transit, and boundaries move halfway or a little more towards the ground colour, labels and
|
|
41
|
+
icons keep 60 % of their opacity, and the change eases in over 560 milliseconds. Calling
|
|
42
|
+
`setEmphasis(undefined)` eases the map back to its design. Each call supersedes the previous one;
|
|
43
|
+
the promise of a superseded call resolves with `status: 'superseded'`.
|
|
44
|
+
|
|
45
|
+
## Choose what steps back
|
|
46
|
+
|
|
47
|
+
- `recede` maps module names to an amount from 0 (as designed) to 1 (the ground colour itself). The
|
|
48
|
+
modules are the semantic domains a map compiles, such as `roads`, `transit`, `boundaries`,
|
|
49
|
+
`water`, `land`, `buildings`, and `vegetation`. Every colour of a receding layer moves towards the
|
|
50
|
+
ground colour, including the colours inside zoom curves and data cases. Artwork that cannot be
|
|
51
|
+
recoloured, such as a pattern, fades by the same amount instead.
|
|
52
|
+
- `labels` is the share of their opacity that the map's labels and icons keep, from 0 to 1.
|
|
53
|
+
- `keep` is a GeoJSON `Polygon` or `MultiPolygon`. Labels and icons whose features fall inside it
|
|
54
|
+
keep their full strength. It can hold up to 4,096 positions. A corridor round a route is a
|
|
55
|
+
typical keep area.
|
|
56
|
+
|
|
57
|
+
The ground colour is the map's background colour. During a blend it is the blended background, so
|
|
58
|
+
receding colours follow the hour as the blend moves.
|
|
59
|
+
|
|
60
|
+
## How it changes over time
|
|
61
|
+
|
|
62
|
+
Values that depend only on the camera, such as a colour or an opacity with a zoom curve, ease every
|
|
63
|
+
frame. A value that MapLibre evaluates per feature, such as a road colour chosen by class or any
|
|
64
|
+
label opacity once a keep area is given, would make MapLibre lay its source out again on every
|
|
65
|
+
change. Such values change once, at the start of the change, under a snapshot of the map that fades
|
|
66
|
+
out as the transition runs. When the browser asks for reduced motion, every change happens at once.
|
|
67
|
+
|
|
68
|
+
An emphasis stays through `setTheme` and `setBlend`: after a new style is applied, the emphasis is
|
|
69
|
+
written again under the same cross-fade, and each blend position recomputes the receded colours.
|
|
70
|
+
|
|
71
|
+
## How it finds modules
|
|
72
|
+
|
|
73
|
+
Every compiled layer names its semantic module in its metadata:
|
|
74
|
+
|
|
75
|
+
```json
|
|
76
|
+
{"id": "tileflow-road-major-fill", "metadata": {"tileflow:domain": "roads"}}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Layers that a theme blend splits or copies keep the same entry. Applications can read the key from
|
|
80
|
+
`tileflowLayerDomainMetadataKey` in `@tileflow/core/browser`. Physical layer IDs remain diagnostic;
|
|
81
|
+
use the module name instead. Styles compiled before this metadata existed have no module names, so
|
|
82
|
+
an emphasis leaves them unchanged.
|
|
83
|
+
|
|
84
|
+
## Cost
|
|
85
|
+
|
|
86
|
+
Each eased frame writes only the values that changed, with `setPaintProperty` and without
|
|
87
|
+
validation. For a detailed city map with roads, transit, and boundaries receding, that is about 150
|
|
88
|
+
values a frame on a desktop computer. Writing values directly is faster than reading a MapLibre
|
|
89
|
+
global state from every value, because a global state change re-evaluates every layer that reads it.
|
|
@@ -72,8 +72,10 @@ Dev's `native-lowering-v1` preparation policy explicitly converts a fixed `{type
|
|
|
72
72
|
projection to Native's implicit Mercator before this validator runs. This is a recorded projection
|
|
73
73
|
change, not globe support or visual equivalence. One authored map serves web and native preparation;
|
|
74
74
|
no native-specific Streets definition is used. Preparation adapts only finite `line-cap` and
|
|
75
|
-
`line-dasharray` decisions into constant-property layers
|
|
76
|
-
|
|
75
|
+
`line-dasharray` decisions into constant-property layers, and replaces constant `icon-overlap` and
|
|
76
|
+
`text-overlap` modes with their `*-allow-overlap` equivalent. Other unsupported properties have no
|
|
77
|
+
implicit fallback. The raw validator continues to reject unlowered unsupported expressions and
|
|
78
|
+
overlap modes. See the [preparation contract](https://github.com/tileflow/tileflow-sdk/blob/main/packages/dev/docs/native-artifacts.md)
|
|
77
79
|
for the grammar, expansion limits and pending native visual checks. The lowering checks establish
|
|
78
80
|
per-feature selection and cap/dash values, not global feature draw order. A copied `line-sort-key`
|
|
79
81
|
cannot preserve ordering between simultaneous physical branches; overlapping features may produce
|
|
@@ -81,7 +83,7 @@ a different composition even when every property value is retained.
|
|
|
81
83
|
|
|
82
84
|
`native-build.json` uses strict schema version 2 and `preparationVersion: 'native-lowering-v1'`.
|
|
83
85
|
Its `transformations` array is ordered by map/theme and binds the input/lowered style hashes,
|
|
84
|
-
projection policy
|
|
86
|
+
projection policy, physical layer spans and symbol overlap replacements. The runtime manifest remains version 1. An old v1
|
|
85
87
|
build-record decoder must reject v2 rather than silently ignore the preparation evidence.
|
|
86
88
|
|
|
87
89
|
Text must use a valid glyph template containing `{fontstack}` and `{range}`, or a complete static
|
|
@@ -22,6 +22,26 @@ Compounds express common cartographic structures: `AreaStyle` has `fill` and `ou
|
|
|
22
22
|
optional `text`, `icon`, and `marker`. Geographic selection remains owned by semantic modules;
|
|
23
23
|
ordinary visual styles intentionally do not accept raw source filters.
|
|
24
24
|
|
|
25
|
+
## Symbol overlap modes
|
|
26
|
+
|
|
27
|
+
`text.allowOverlap` and `icon.allowOverlap` either hide a part that collides with a symbol placed
|
|
28
|
+
before it or always show it. `overlap` selects one of three modes instead:
|
|
29
|
+
|
|
30
|
+
- `never`: hide the part when it collides.
|
|
31
|
+
- `always`: show the part even when it collides.
|
|
32
|
+
- `cooperative`: show the part unless it collides with a symbol placed with `never`. It may overlap
|
|
33
|
+
symbols placed with `always` or `cooperative`.
|
|
34
|
+
|
|
35
|
+
MapLibre places symbols from the top layer down, so a symbol placed before another is usually higher
|
|
36
|
+
in the stack. `overlap` takes precedence over `allowOverlap` on the same part, including an
|
|
37
|
+
`allowOverlap` default supplied by a module or already present on a refined target, so the compiled
|
|
38
|
+
layer carries only `text-overlap` or `icon-overlap`. MapLibre Native has no overlap modes, so native
|
|
39
|
+
artifact preparation replaces the mode with the matching `text-allow-overlap` or
|
|
40
|
+
`icon-allow-overlap`: `false` for `never` and `true` for `always` and `cooperative`. There a
|
|
41
|
+
cooperative part no longer gives way to symbols placed with `never`. The
|
|
42
|
+
[canopy grove recipe](data-is-separate-from-design.md) uses a cooperative text to decide where
|
|
43
|
+
icons draw.
|
|
44
|
+
|
|
25
45
|
## Line-fitted text backgrounds
|
|
26
46
|
|
|
27
47
|
`text.background` draws one opaque strip behind each shaped line of a label, fitted to that line's
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# Style states
|
|
2
|
+
|
|
3
|
+
A theme can read a value that the application sets once for the whole style, such as whether
|
|
4
|
+
anything is selected. The theme reads it with `expr.globalState(name)`; the application sets it with
|
|
5
|
+
the theme controller's `setState(name, value)`. MapLibre calls these values global state (GL JS 5.6
|
|
6
|
+
or later).
|
|
7
|
+
|
|
8
|
+
## Read a state in a theme
|
|
9
|
+
|
|
10
|
+
A state reads `null` until the application sets it, so wrap it in a typed assertion with a fallback.
|
|
11
|
+
To let a value move smoothly, make the state a number and interpolate on it:
|
|
12
|
+
|
|
13
|
+
<!-- docs:check -->
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import {expr} from '@tileflow/core';
|
|
17
|
+
|
|
18
|
+
// 0 shows the standing highlight, 1 the quiet plate; values between mix the two.
|
|
19
|
+
const selection = expr.toNumber(expr.globalState('selection'), 0);
|
|
20
|
+
export const captionPlate = expr.interpolate({kind: 'linear'}, selection, [
|
|
21
|
+
[0, '#ef5f57'],
|
|
22
|
+
[1, '#fff9ee'],
|
|
23
|
+
]);
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
A boolean works too, through `expr.case`, but it can only switch.
|
|
27
|
+
|
|
28
|
+
## Set a state from the application
|
|
29
|
+
|
|
30
|
+
This example assumes an existing MapLibre map named `map` that shows the `light` theme of a
|
|
31
|
+
published Tileflow map:
|
|
32
|
+
|
|
33
|
+
<!-- docs:check -->
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import {createTileflowThemeController, type TileflowStyleSwitchMap} from '@tileflow/core/browser';
|
|
37
|
+
|
|
38
|
+
// A MapLibre map satisfies TileflowStyleSwitchMap.
|
|
39
|
+
export function selectionStates(map: TileflowStyleSwitchMap, styleUrl: string) {
|
|
40
|
+
const controller = createTileflowThemeController({
|
|
41
|
+
initial: {style: styleUrl, theme: 'light'},
|
|
42
|
+
map,
|
|
43
|
+
});
|
|
44
|
+
void controller.setState('selection', 0);
|
|
45
|
+
return {
|
|
46
|
+
select: () => controller.setState('selection', 1, {transition: {duration: 300}}),
|
|
47
|
+
clear: () => controller.setState('selection', 0, {transition: {duration: 300}}),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
A number eases from the value the map shows to the new one over the transition, so the plate mixes
|
|
53
|
+
smoothly from one colour to the other. Booleans, strings, `null`, and a number set for the first
|
|
54
|
+
time change at once, so set a starting value before easing it. A browser preference for
|
|
55
|
+
reduced motion changes at once. Each call supersedes an easing of the same state still in progress,
|
|
56
|
+
which resolves with `status: 'superseded'`; the new easing starts from where the old one stood.
|
|
57
|
+
|
|
58
|
+
States stay through `setTheme` and every `setBlend`: the controller writes them again after each
|
|
59
|
+
new style. `getState(name)` returns the value set last.
|
|
60
|
+
|
|
61
|
+
## Keep eased values free of feature data
|
|
62
|
+
|
|
63
|
+
Keep a value that eases on a state free of feature data and feature state. MapLibre evaluates such
|
|
64
|
+
a value for the whole layer at each frame. A value that also reads a feature's data or state is
|
|
65
|
+
evaluated per feature when its tiles are laid out, so each change of the state lays them out
|
|
66
|
+
again: the value follows late, in steps, and each frame costs far more. Put the per-feature choice
|
|
67
|
+
in another layer, or let the application draw the feature it marks.
|
|
68
|
+
|
|
69
|
+
## Cost
|
|
70
|
+
|
|
71
|
+
Each frame of an easing sets the state once, and MapLibre then evaluates again every paint value
|
|
72
|
+
that reads it. A state read by a few layers costs little; a state read by most layers of a map can
|
|
73
|
+
cost more per frame than the blend itself on slower devices.
|