tileflow 0.1.0-alpha.22 → 0.1.0-alpha.23
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 +26 -20
- package/REFERENCE.md +429 -0
- package/dist/build-RQF35KW6.js +21 -0
- package/dist/chunk-43VQMAQO.js +52 -0
- package/dist/chunk-5U4J6E6Y.js +4911 -0
- package/dist/{chunk-5QK6JCLT.js → chunk-EXDH7EHK.js} +726 -68
- package/dist/chunk-JVTGXPGV.js +382 -0
- package/dist/{cli-main-HRFGWUWR.js → cli-main-K3PKVCIP.js} +1099 -351
- package/dist/errors-C3VZMCM5.js +20 -0
- package/dist/index.js +2 -2
- package/dist/inspect-source-5CD3AV5J.js +227 -0
- package/dist/recipe-3BPMNDPX.js +23 -0
- package/dist/{tileset-entry-ITUSGCM3.js → tileset-entry-V5RUQGZG.js} +4 -1
- package/package.json +14 -5
- package/schemas/tileset-recipe.v1.json +171 -0
package/README.md
CHANGED
|
@@ -197,12 +197,12 @@ Only `visual update` changes baseline PNG/receipt pairs. `visual diff` writes wo
|
|
|
197
197
|
approval. Changed pixels normally exit 0; `--fail-on changed` exits 2. Missing or incompatible
|
|
198
198
|
baselines and operational failures exit 1. Never run `visual update` automatically to make CI pass.
|
|
199
199
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
200
|
+
`visual analyze <scene> --reference <png>` checks an external screenshot. `visual compare` runs an
|
|
201
|
+
authenticated two-style review with matched camera, viewport, renderer, and data. Neither approves a
|
|
202
|
+
baseline. Capture watch mode emits NDJSON generation events rather than a single JSON document.
|
|
203
|
+
`visual report` and `visual approve` read and approve a pull request's visual review.
|
|
204
204
|
|
|
205
|
-
|
|
205
|
+
See `--help` on each command for installed options.
|
|
206
206
|
|
|
207
207
|
## Publish data and deploy a map
|
|
208
208
|
|
|
@@ -261,27 +261,40 @@ refuse to overwrite hosted manifests by default, and deploy refuses to replace a
|
|
|
261
261
|
manifest without explicit authorization. Keep separate outputs, or use the documented overwrite
|
|
262
262
|
option only for an intentional migration.
|
|
263
263
|
|
|
264
|
-
Data publication is separate from map deployment
|
|
264
|
+
Data publication is separate from map deployment. Build PMTiles locally from GeoJSON, CSV,
|
|
265
|
+
Shapefile, GeoPackage, KML or GPX:
|
|
265
266
|
|
|
266
267
|
```sh
|
|
268
|
+
npx tileflow tileset build ./data/stores.geojson -o ./data/stores.pmtiles --json
|
|
267
269
|
npx tileflow tileset inspect ./data/stores.pmtiles --json
|
|
268
270
|
npx tileflow tileset publish ./data/stores.pmtiles --id stores --team @acme --attribution 'Store data © Example' --json
|
|
269
271
|
npx tileflow tileset status stores --team @acme --json
|
|
270
272
|
```
|
|
271
273
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
+
See the
|
|
275
|
+
[reference](https://github.com/tileflow/tileflow-sdk/blob/main/packages/cli/REFERENCE.md) for inputs,
|
|
276
|
+
CRS rules, limits and upload behavior. Publish your own data with Team data authority and attribution.
|
|
277
|
+
Interrupted uploads resume; published versions become
|
|
274
278
|
current at stable URLs. Purge requires confirmation and may be blocked by retained deployments.
|
|
275
279
|
|
|
276
|
-
Hashing reuses a 16 MiB buffer. Up to four parts stream concurrently with reusable buffers.
|
|
277
|
-
|
|
278
|
-
Administratively disabled uploads return `TF_TILESET_UPLOAD_DISABLED` without retrying.
|
|
279
|
-
Temporary service failures retain bounded retries.
|
|
280
|
-
|
|
281
280
|
`inspect features` samples bounded vector-source features. `icons list` and `icons diff` inspect
|
|
282
281
|
prepared icon identities; they do not turn package directories into a separate hosted library.
|
|
283
282
|
Use the installed command's help for exact arguments and supported JSON output.
|
|
284
283
|
|
|
284
|
+
## Restore an earlier version
|
|
285
|
+
|
|
286
|
+
Restoring a deployment publishes its content again as a new version of the Map. Restoring a tileset
|
|
287
|
+
swaps its current and previous versions for every Map that uses it:
|
|
288
|
+
|
|
289
|
+
```bash
|
|
290
|
+
npx tileflow deployments list --map-id map_AbCdEfGhIjKlMnOp
|
|
291
|
+
npx tileflow deployments restore 12 --map-id map_AbCdEfGhIjKlMnOp
|
|
292
|
+
npx tileflow tileset restore stores --team @acme
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
See the [reference](https://github.com/tileflow/tileflow-sdk/blob/main/packages/cli/REFERENCE.md#restore)
|
|
296
|
+
for refusals, clearing an abandoned upload and JSON output.
|
|
297
|
+
|
|
285
298
|
## Team Icon Sets and repository locks
|
|
286
299
|
|
|
287
300
|
Two intentionally separate families cover shared icons. `icon-set` manages the remote Team catalog
|
|
@@ -303,13 +316,6 @@ revisions. `purge <slug> --version <n>` additionally requires `--idempotency-key
|
|
|
303
316
|
`--confirm @team/set@version`, and `--acknowledge-unknown-locks`, because repository locks that were
|
|
304
317
|
never deployed are not discoverable and already-downloaded public bytes cannot be revoked.
|
|
305
318
|
|
|
306
|
-
The lock commands inspect the `iconSet()` references your config declares. They are the only
|
|
307
|
-
commands that resolve `latest`, and the only ones that write the lock. `install` fills in declared
|
|
308
|
-
references that have no pin, `update` moves selected references to their current latest revision,
|
|
309
|
-
and `pin` selects one exact revision. Every requested reference is resolved before a single
|
|
310
|
-
compare-and-swap write of the whole snapshot, so a concurrent writer fails the command instead of
|
|
311
|
-
losing its pins. No lock command rewrites `tileflow.config.ts`.
|
|
312
|
-
|
|
313
319
|
`validate`, `build`, `preview`, `deploy`, `icons list` and `icons diff` read the lock and never
|
|
314
320
|
resolve a catalog head. `--cache-dir` chooses the verified artifact cache root and `--offline` fails
|
|
315
321
|
a cache miss instead of hydrating it. Publishing a newer revision changes nothing in a repository
|
package/REFERENCE.md
ADDED
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
# CLI reference
|
|
2
|
+
|
|
3
|
+
Detailed contracts for the `tileflow` CLI. Start with the
|
|
4
|
+
[README](https://github.com/tileflow/tileflow-sdk/blob/main/packages/cli/README.md) to install it.
|
|
5
|
+
For step-by-step guides, see the [Tileflow documentation](https://tileflow.dev/docs).
|
|
6
|
+
|
|
7
|
+
This reference ships with the package. Use the copy included in your installed release for
|
|
8
|
+
version-specific behavior; the GitHub `main` branch can contain unreleased changes.
|
|
9
|
+
|
|
10
|
+
## tileset build
|
|
11
|
+
|
|
12
|
+
`tileflow tileset build <input>` turns local vector data into a PMTiles archive ready for
|
|
13
|
+
`tileflow tileset publish`. It runs on your machine, needs no account and no extra tools, and sends
|
|
14
|
+
nothing anywhere. Success means the archive already passed the validation `publish` applies. Build
|
|
15
|
+
output is deterministic: the same input, options and CLI version produce the same tiles and metadata on any
|
|
16
|
+
system, and the same bytes as long as Node.js compresses the same way, so republishing unchanged data reports
|
|
17
|
+
`unchanged`.
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
npx tileflow tileset build ./data/stores.geojson -o ./data/stores.pmtiles --json
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### Inputs
|
|
24
|
+
|
|
25
|
+
| Format | Detected from | Notes |
|
|
26
|
+
| ------------------------- | ------------------------------------------------------------ | ---------------------------------------------------------------------------- |
|
|
27
|
+
| GeoJSON | `.geojson`, `.json` | FeatureCollection, Feature or geometry. Streamed. |
|
|
28
|
+
| Newline-delimited GeoJSON | `.ndjson`, `.geojsonl`, `.geojsons`, `.geojsonseq`, `.jsonl` | One Feature or geometry per line; RFC 8142 separators are accepted. |
|
|
29
|
+
| CSV | `.csv`, `.tsv` | UTF-8 with a header row. Comma, semicolon, tab or pipe delimited. |
|
|
30
|
+
| Shapefile | `.shp`, or `.zip` holding one | Reads `.dbf`, `.prj` and `.cpg` beside it. Z and M values are ignored. |
|
|
31
|
+
| GeoPackage | `.gpkg` | One feature table. Needs Node.js 22.13 or later. Z and M values are ignored. |
|
|
32
|
+
| KML | `.kml`, or `.kmz` holding one | Placemarks only. Altitude and styles are ignored. |
|
|
33
|
+
| GPX | `.gpx` | Waypoints, routes and tracks each become a feature. |
|
|
34
|
+
|
|
35
|
+
Pass `--format csv|geojson|geojsonseq|geopackage|gpx|kml|shapefile` when the extension is misleading. A
|
|
36
|
+
zip with several Shapefiles, or a KMZ with several KML documents and no `doc.kml`, needs
|
|
37
|
+
`--zip-entry <path>` to choose one. Other formats inside a zip are not read. Other formats, such as
|
|
38
|
+
FlatGeobuf, are not supported.
|
|
39
|
+
|
|
40
|
+
CSV coordinate columns are found by name: `lon`, `lng`, `long`, `longitude`, `lat`, `latitude`, in
|
|
41
|
+
any case. Use `--lon-column` and `--lat-column` for anything else. Columns are typed as a whole, so a
|
|
42
|
+
value like `01234` keeps a whole column as text, and empty cells are omitted from the feature.
|
|
43
|
+
|
|
44
|
+
A GeoPackage with several feature tables needs `--input-layer <table>`, and the error lists them. The
|
|
45
|
+
source-layer name then defaults to that table unless you pass `--layer`. The integer primary key
|
|
46
|
+
becomes the feature ID, and attributes keep their SQLite types. BLOB and generated columns are skipped
|
|
47
|
+
with a warning. SQL views, curved geometries and the extended GeoPackage binary encoding are not read.
|
|
48
|
+
The file is only read, and edits still pending in a write-ahead log are included.
|
|
49
|
+
|
|
50
|
+
KML features get `name`, `description`, `address`, `phoneNumber`, `timestamp`, `begin`, `end`, and
|
|
51
|
+
`folder` for the nearest enclosing Folder, plus every `ExtendedData` value. Values stay text unless a
|
|
52
|
+
`Schema` declares a numeric or Boolean type, so `--rank-by` needs a declared numeric field. If an
|
|
53
|
+
`ExtendedData` value repeats one of those names, the last one is kept with a warning. A
|
|
54
|
+
`MultiGeometry` stays one feature when its parts share a type. Overlays and network links are ignored
|
|
55
|
+
with a warning, and remote content is never fetched.
|
|
56
|
+
|
|
57
|
+
GPX features get `name`, `desc`, `cmt`, `src`, `sym`, `type`, `time`, and the numeric `ele` and
|
|
58
|
+
`number`. Elevation and time on individual route and track points are ignored with a warning.
|
|
59
|
+
|
|
60
|
+
### Coordinate reference systems
|
|
61
|
+
|
|
62
|
+
The build never guesses a CRS and never reprojects without `--reproject`:
|
|
63
|
+
|
|
64
|
+
```sh
|
|
65
|
+
npx tileflow tileset build ./data/parcels.shp --reproject --json
|
|
66
|
+
npx tileflow tileset build ./data/sites.csv --lon-column x --lat-column y --source-crs EPSG:25830 --reproject --json
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
| Input | Treated as |
|
|
70
|
+
| ----------------------------------- | ----------------------------------------------------------- |
|
|
71
|
+
| GeoJSON without a `crs` member | WGS84 longitude and latitude, as RFC 7946 requires |
|
|
72
|
+
| GeoJSON with a WGS84 `crs` member | WGS84 |
|
|
73
|
+
| GeoJSON with another `crs` member | Refused. Restate it with `--source-crs`, plus `--reproject` |
|
|
74
|
+
| CSV with standard or named columns | WGS84. Any other coordinates fail the range check |
|
|
75
|
+
| CSV with no recognizable columns | Refused until you name the columns |
|
|
76
|
+
| Shapefile with a WGS84 `.prj` | WGS84 |
|
|
77
|
+
| Shapefile with another `.prj` | Refused until you add `--reproject` |
|
|
78
|
+
| Shapefile without a `.prj` | Refused until you pass `--source-crs` |
|
|
79
|
+
| GeoPackage layer in WGS84 | WGS84 |
|
|
80
|
+
| GeoPackage layer in another CRS | Refused until you add `--reproject` |
|
|
81
|
+
| GeoPackage layer with no usable CRS | Refused until you pass `--source-crs` |
|
|
82
|
+
| KML and GPX | WGS84, as both formats define |
|
|
83
|
+
|
|
84
|
+
A GeoPackage layer's CRS comes from `gpkg_spatial_ref_sys`. The reserved `srs_id` values -1 and 0 mean
|
|
85
|
+
undefined, so they count as no usable CRS. A row whose `srs_id` differs from its layer stops the build
|
|
86
|
+
with `TF_TILESET_BUILD_CRS_MIXED`, unless `--source-crs` states the CRS for every row.
|
|
87
|
+
|
|
88
|
+
`--source-crs` states what the coordinates are, and overrides anything the file declares. It accepts
|
|
89
|
+
`EPSG:4326`, `EPSG:3857`, `EPSG:4258`, `EPSG:4269`, UTM zones on WGS84 (`EPSG:326xx`, `EPSG:327xx`),
|
|
90
|
+
ETRS89 (`EPSG:258xx`) or NAD83 (`EPSG:269xx`), a proj string, WKT, or the path to a `.prj` file. Other
|
|
91
|
+
EPSG codes need one of the last three forms.
|
|
92
|
+
|
|
93
|
+
Conversion uses [proj4js](https://github.com/proj4js/proj4js), which applies 3- and 7-parameter datum
|
|
94
|
+
shifts but has no grid shifts:
|
|
95
|
+
|
|
96
|
+
- Datums within about 2 m of WGS84 (ETRS89, RGF93, NAD83, GDA94, GDA2020, NZGD2000, SIRGAS, JGD2000
|
|
97
|
+
and JGD2011) are treated as coincident, with a warning.
|
|
98
|
+
- Datums with a known shift convert with that shift. Accuracy is a few metres at best.
|
|
99
|
+
- Any other datum, such as NAD27 or ED50 without parameters, fails with
|
|
100
|
+
`TF_TILESET_BUILD_DATUM_UNSUPPORTED` rather than misplacing features by up to hundreds of metres.
|
|
101
|
+
Pass a proj string with `+towgs84=` if you know the shift, or reproject first.
|
|
102
|
+
|
|
103
|
+
Every coordinate is range-checked after any conversion. The first coordinate more than 0.001° outside
|
|
104
|
+
longitude ±180 or latitude ±90 stops the build and names the feature, row or record; anything closer
|
|
105
|
+
is moved to the limit and counted in `warnings`. A prime meridian other than Greenwich counts as a
|
|
106
|
+
non-WGS84 CRS. This catches most, but not
|
|
107
|
+
all, wrong statements: a small projected extent can fall in range, and swapped axes are only caught
|
|
108
|
+
past latitude ±90.
|
|
109
|
+
|
|
110
|
+
To make those visible, whenever you state a CRS or the build converts one, `warnings` says where the
|
|
111
|
+
features ended up. Every build also warns if all features sit within 1° of longitude 0, latitude 0.
|
|
112
|
+
Human output always prints the bounds, and `--json` reports them as `archive.bounds`.
|
|
113
|
+
|
|
114
|
+
### Options
|
|
115
|
+
|
|
116
|
+
| Option | Meaning |
|
|
117
|
+
| ----------------------- | ---------------------------------------------------------------- |
|
|
118
|
+
| `-o, --output <path>` | Archive path. Defaults to the input name with `.pmtiles` |
|
|
119
|
+
| `--force` | Replace an existing output |
|
|
120
|
+
| `--layer <name>` | Source-layer name. Defaults to the input file name |
|
|
121
|
+
| `--minzoom <zoom>` | Lowest zoom to write. Defaults to 0 |
|
|
122
|
+
| `--maxzoom <zoom>` | Highest zoom to write, up to 16. Chosen from the data by default |
|
|
123
|
+
| `--include <fields>` | Keep only these comma-separated attributes, by source name |
|
|
124
|
+
| `--exclude <fields>` | Drop these comma-separated attributes, by source name |
|
|
125
|
+
| `--rank-by <field>` | Thin the lowest values of this numeric attribute first |
|
|
126
|
+
| `--rank-ascending` | With `--rank-by`, thin the highest values first |
|
|
127
|
+
| `--zip-entry <path>` | The `.shp` or `.kml` to read from an archive holding several |
|
|
128
|
+
| `--input-layer <name>` | The feature table to read from a GeoPackage holding several |
|
|
129
|
+
| `--lon-column <name>` | CSV longitude column |
|
|
130
|
+
| `--lat-column <name>` | CSV latitude column |
|
|
131
|
+
| `--source-crs <crs>` | State the input CRS |
|
|
132
|
+
| `--reproject` | Convert a stated non-WGS84 CRS to WGS84 |
|
|
133
|
+
| `--name <name>` | Archive display name |
|
|
134
|
+
| `--description <text>` | Archive description |
|
|
135
|
+
| `--attribution <text>` | Attribution stored in the archive |
|
|
136
|
+
| `--format <format>` | Override format detection |
|
|
137
|
+
| `--feature-id <field>` | Use this attribute as the feature ID |
|
|
138
|
+
| `--hash-feature-id` | With `--feature-id`, hash each value into an integer ID |
|
|
139
|
+
| `--recipe <path>` | Read every build setting from a recipe file |
|
|
140
|
+
| `--print-recipe` | Print the recipe these options make, without building |
|
|
141
|
+
| `--progress <format>` | Report progress on stderr as `text` or `ndjson` |
|
|
142
|
+
| `--inspect` | Report format, CRS and fields as JSON, without tiling |
|
|
143
|
+
| `--sample-features <n>` | Features `--inspect` reads. Defaults to 10000 |
|
|
144
|
+
| `--json` | Print one schema-version-1 JSON document on stdout |
|
|
145
|
+
|
|
146
|
+
`--json` turns text progress off unless you also pass `--progress`. With `--progress ndjson`, each stderr
|
|
147
|
+
line is one object with `stage` (`reading`, `tiling`, `archiving` or `validating`), `message`, and
|
|
148
|
+
`features` or `tiles` when known.
|
|
149
|
+
|
|
150
|
+
`--inspect` reads a sample and prints what is still undecided, instead of failing on the first thing
|
|
151
|
+
a build would refuse. It always prints JSON: `crs.status` is `wgs84`, `needs-reproject`, `unknown`, `unsupported` or
|
|
152
|
+
`out-of-range`; `sample.sourceBounds` and up to 2,000 `sample.coordinates` hold the positions as stored in the file; `fields` and a suggested `recipe`
|
|
153
|
+
follow. A zip or GeoPackage holding several candidates returns `choices`, and a CSV without
|
|
154
|
+
recognizable coordinate columns returns `columns`. Nothing is written beside the input.
|
|
155
|
+
|
|
156
|
+
### Recipes
|
|
157
|
+
|
|
158
|
+
A recipe is a JSON file that holds every build setting, so a repeatable build can be reviewed in a
|
|
159
|
+
pull request and run the same way anywhere. Options are a shorthand for a recipe:
|
|
160
|
+
|
|
161
|
+
```sh
|
|
162
|
+
npx tileflow tileset build ./data/parcels.shp --reproject --include ref,area_m2 --print-recipe > ./data/parcels.tileset.json
|
|
163
|
+
npx tileflow tileset build ./data/parcels.shp --recipe ./data/parcels.tileset.json --json
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
```json
|
|
167
|
+
{
|
|
168
|
+
"$schema": "https://tileflow.dev/schemas/tileset-recipe/v1.json",
|
|
169
|
+
"layers": {
|
|
170
|
+
"parcels": {
|
|
171
|
+
"featureId": {"field": "gid"},
|
|
172
|
+
"fields": {"include": ["ref", "area_m2"]},
|
|
173
|
+
"maxzoom": "auto",
|
|
174
|
+
"minzoom": 0,
|
|
175
|
+
"rank": {"by": "area_m2", "order": "descending"},
|
|
176
|
+
"source": "parcels"
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
"metadata": {"attribution": "© Example", "name": "Parcels"},
|
|
180
|
+
"sources": {
|
|
181
|
+
"parcels": {
|
|
182
|
+
"crs": {"from": "EPSG:25830", "reproject": true},
|
|
183
|
+
"zipEntry": "cadastre/parcels.shp"
|
|
184
|
+
}
|
|
185
|
+
},
|
|
186
|
+
"version": 1
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
| Recipe field | Option |
|
|
191
|
+
| --------------------------------------------- | ------------------------------------------ |
|
|
192
|
+
| Key of `layers` | `--layer` |
|
|
193
|
+
| `layers.*.minzoom`, `maxzoom` | `--minzoom`, `--maxzoom` |
|
|
194
|
+
| `layers.*.fields.include`, `exclude` | `--include`, `--exclude` |
|
|
195
|
+
| `layers.*.rank` | `--rank-by`, `--rank-ascending` |
|
|
196
|
+
| `layers.*.featureId` | `--feature-id`, `--hash-feature-id` |
|
|
197
|
+
| `sources.*.format`, `layer`, `zipEntry` | `--format`, `--input-layer`, `--zip-entry` |
|
|
198
|
+
| `sources.*.csv.lon`, `lat` | `--lon-column`, `--lat-column` |
|
|
199
|
+
| `sources.*.crs.from`, `reproject` | `--source-crs`, `--reproject` |
|
|
200
|
+
| `metadata.name`, `description`, `attribution` | `--name`, `--description`, `--attribution` |
|
|
201
|
+
|
|
202
|
+
The input file stays on the command line. `sources` and `layers` each hold exactly one entry in version 1.
|
|
203
|
+
Unknown fields are refused, and `--recipe` cannot be combined with an option from this table.
|
|
204
|
+
`sources.*.crs.from` accepts `EPSG:<code>`, a proj string or WKT, never a path. `--print-recipe` replaces a
|
|
205
|
+
`.prj` path with its contents. The schema ships at `schemas/tileset-recipe.v1.json` in this package.
|
|
206
|
+
|
|
207
|
+
Every build reports its resolved recipe as `recipe` in the JSON output, with `maxzoom` as a number. Reusing that
|
|
208
|
+
recipe gives the same zoom even if automatic zoom changes in a later version. The archive metadata records
|
|
209
|
+
`tileflow_recipe_hash` and `tileflow_source_hash`, and `source.sha256` in the JSON output holds the source
|
|
210
|
+
digest. For a Shapefile outside a zip, the digest covers the `.shp`, `.dbf`, `.prj` and `.cpg`.
|
|
211
|
+
|
|
212
|
+
### Zooms and thinning
|
|
213
|
+
|
|
214
|
+
The automatic maximum zoom is the lowest that resolves most vertices and keeps the densest tile
|
|
215
|
+
manageable, from zoom 10 for points and zoom 4 for lines and polygons, up to zoom 14. `--json` reports
|
|
216
|
+
it as `archive.maxzoom` with `archive.maxzoomSource`.
|
|
217
|
+
|
|
218
|
+
Rings with fewer than three distinct positions and lines with a single position are skipped and
|
|
219
|
+
counted in `warnings`. Every other feature reaches the maximum zoom. Below it, tiles stay within about 500 KiB compressed:
|
|
220
|
+
|
|
221
|
+
- Points that land on the same pixel keep only the first, so sparse areas stay complete while dense
|
|
222
|
+
areas thin out. "First" is input order, or the best value with `--rank-by`. Polygons too small for
|
|
223
|
+
a zoom render as a tiny mark and thin the same way.
|
|
224
|
+
- Lines and polygons are simplified to the tile resolution, and lines smaller than that drop out.
|
|
225
|
+
- A tile still over budget thins its densest cells further.
|
|
226
|
+
|
|
227
|
+
Thinning nests: a feature drawn at one zoom is drawn at every higher zoom, so features appear as you
|
|
228
|
+
zoom in and do not flicker. A tile can exceed the 500 KiB target to honour this. Only a tile that
|
|
229
|
+
would pass 6 MB drops features its parent drew.
|
|
230
|
+
|
|
231
|
+
`--rank-by` accepts any numeric attribute, whether or not it stays in the tiles. Features without a
|
|
232
|
+
number there thin first and are counted in `warnings`; a field with no numbers fails. Features are
|
|
233
|
+
written to tiles in rank order, which also gives the best ones first claim on label placement. Ranking
|
|
234
|
+
holds about 24 extra bytes per feature in memory.
|
|
235
|
+
|
|
236
|
+
`zooms[].thinnedFeatures` counts the features left out of tiles at each zoom. It is always zero at the
|
|
237
|
+
maximum zoom unless a geometry rounds away there. Zooming past the maximum reuses the last tiles.
|
|
238
|
+
|
|
239
|
+
A tile at the maximum zoom that would exceed 6 MB fails with `TF_TILESET_BUILD_TILE_TOO_LARGE`, because
|
|
240
|
+
keeping it would mean losing features silently. Raise `--maxzoom`, or remove attributes.
|
|
241
|
+
|
|
242
|
+
### Attributes
|
|
243
|
+
|
|
244
|
+
Strings, numbers and booleans map directly. Nested objects and arrays become JSON strings, and `null`
|
|
245
|
+
is omitted. Field names outside letters, digits, `.`, `_`, `:` and `-` are renamed, with accents
|
|
246
|
+
removed and other characters replaced by `_`; `layer.fields` maps each `sourceName` to its `name`, and
|
|
247
|
+
a warning lists the change. Two names that collide stop the build. A field with mixed types is
|
|
248
|
+
declared `Mixed`. Non-negative integer GeoJSON `id` values become vector tile feature IDs; other ids
|
|
249
|
+
are dropped with a warning. Each entry in `layer.fields` also reports `count`, `min` and `max` for numbers,
|
|
250
|
+
up to 64 distinct `values` of up to 64 characters, and `valuesComplete`. The archive metadata carries the same
|
|
251
|
+
values as `tilestats`. `--feature-id` reads the ID from an attribute instead, for any format, and fails if no
|
|
252
|
+
feature has a usable value. Add `--hash-feature-id` to turn text keys into stable integers. Vector tiles store
|
|
253
|
+
only integers, and two different values can collide once a dataset reaches tens of millions of features.
|
|
254
|
+
|
|
255
|
+
### JSON output
|
|
256
|
+
|
|
257
|
+
```json
|
|
258
|
+
{
|
|
259
|
+
"archive": {
|
|
260
|
+
"addressedTiles": 341,
|
|
261
|
+
"bounds": [-3.9, 40.2, -3.5, 40.6],
|
|
262
|
+
"byteCount": 48211,
|
|
263
|
+
"maxzoom": 10,
|
|
264
|
+
"maxzoomSource": "auto",
|
|
265
|
+
"minzoom": 0,
|
|
266
|
+
"path": "data/stores.pmtiles",
|
|
267
|
+
"tileContents": 341
|
|
268
|
+
},
|
|
269
|
+
"command": "tileset build",
|
|
270
|
+
"crs": {"authority": "format", "label": "GeoJSON (WGS84 by RFC 7946)", "reprojected": false},
|
|
271
|
+
"input": {
|
|
272
|
+
"features": 1200,
|
|
273
|
+
"format": "geojson",
|
|
274
|
+
"geometryTypes": ["Point"],
|
|
275
|
+
"skipped": {"emptyGeometry": 0, "outsideMercator": 0}
|
|
276
|
+
},
|
|
277
|
+
"layer": {
|
|
278
|
+
"fields": [{"name": "name", "sourceName": "name", "type": "String"}],
|
|
279
|
+
"id": "stores"
|
|
280
|
+
},
|
|
281
|
+
"recipe": {
|
|
282
|
+
"$schema": "...",
|
|
283
|
+
"layers": {"stores": {"maxzoom": 10, "minzoom": 0, "source": "stores"}},
|
|
284
|
+
"sources": {"stores": {}},
|
|
285
|
+
"version": 1
|
|
286
|
+
},
|
|
287
|
+
"schemaVersion": 1,
|
|
288
|
+
"source": {"sha256": "9f2a..."},
|
|
289
|
+
"warnings": [],
|
|
290
|
+
"zooms": [
|
|
291
|
+
{"features": 900, "largestTileBytes": 9120, "thinnedFeatures": 300, "tiles": 1, "zoom": 0}
|
|
292
|
+
]
|
|
293
|
+
}
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
On failure the exit code is 1, stdout is empty, and stderr carries
|
|
297
|
+
`{"error": {"code", "message", "path"}, "ok": false, "schemaVersion": 1}`.
|
|
298
|
+
|
|
299
|
+
| Code | Meaning |
|
|
300
|
+
| ------------------------------------------- | -------------------------------------------------------------- |
|
|
301
|
+
| `TF_TILESET_BUILD_INPUT_MISSING` | The input file does not exist |
|
|
302
|
+
| `TF_TILESET_BUILD_INPUT_INVALID` | Malformed data, XML, GeoPackage or zip |
|
|
303
|
+
| `TF_TILESET_BUILD_INPUT_UNSAFE` | The XML has a DOCTYPE or entity declaration |
|
|
304
|
+
| `TF_TILESET_BUILD_INPUT_LIMIT` | The input exceeds a safety limit listed below |
|
|
305
|
+
| `TF_TILESET_BUILD_FORMAT_UNSUPPORTED` | Unknown extension, or geometry with no tile equivalent |
|
|
306
|
+
| `TF_TILESET_BUILD_RUNTIME_UNSUPPORTED` | GeoPackage input needs Node.js 22.13 or later |
|
|
307
|
+
| `TF_TILESET_BUILD_ZIP_ENTRY_INVALID` | Archive has no data file, several, or not the one named |
|
|
308
|
+
| `TF_TILESET_BUILD_INPUT_LAYER_INVALID` | GeoPackage has no feature table, several, or not the one named |
|
|
309
|
+
| `TF_TILESET_BUILD_RANK_INVALID` | `--rank-by` has no numeric values, or is missing |
|
|
310
|
+
| `TF_TILESET_BUILD_FEATURE_ID_INVALID` | `--feature-id` has no usable value, or is missing |
|
|
311
|
+
| `TF_TILESET_BUILD_RECIPE_INVALID` | The recipe is not valid |
|
|
312
|
+
| `TF_TILESET_BUILD_RECIPE_UNREADABLE` | The recipe file cannot be read |
|
|
313
|
+
| `TF_TILESET_BUILD_RECIPE_CONFLICT` | `--recipe` was combined with a setting it already holds |
|
|
314
|
+
| `TF_TILESET_BUILD_PROGRESS_INVALID` | `--progress` is not `text` or `ndjson` |
|
|
315
|
+
| `TF_TILESET_BUILD_OPTION_INVALID` | A numeric option is not a positive integer |
|
|
316
|
+
| `TF_TILESET_BUILD_EMPTY` | No usable features |
|
|
317
|
+
| `TF_TILESET_BUILD_CRS_UNKNOWN` | The input does not state its CRS |
|
|
318
|
+
| `TF_TILESET_BUILD_CRS_NOT_WGS84` | Stated CRS needs `--reproject` |
|
|
319
|
+
| `TF_TILESET_BUILD_CRS_MIXED` | GeoPackage rows use a CRS other than their layer's |
|
|
320
|
+
| `TF_TILESET_BUILD_CRS_UNREADABLE` | `--source-crs` or the declared CRS could not be parsed |
|
|
321
|
+
| `TF_TILESET_BUILD_CRS_UNSUPPORTED` | Geographic CRS measured in something other than degrees |
|
|
322
|
+
| `TF_TILESET_BUILD_DATUM_UNSUPPORTED` | No known shift from the datum to WGS84 |
|
|
323
|
+
| `TF_TILESET_BUILD_COORDINATES_OUT_OF_RANGE` | Coordinates are not WGS84 after any conversion |
|
|
324
|
+
| `TF_TILESET_BUILD_CSV_COLUMNS` | Coordinate columns are missing or ambiguous |
|
|
325
|
+
| `TF_TILESET_BUILD_FIELD_CONFLICT` | Two field names collide after renaming |
|
|
326
|
+
| `TF_TILESET_BUILD_FIELDS_INVALID` | `--include` and `--exclude` were combined |
|
|
327
|
+
| `TF_TILESET_BUILD_TOO_MANY_FIELDS` | More than 512 fields; use `--include` |
|
|
328
|
+
| `TF_TILESET_BUILD_LAYER_INVALID` | The layer name is not valid |
|
|
329
|
+
| `TF_TILESET_BUILD_ZOOM_INVALID` | Zooms are out of range or reversed |
|
|
330
|
+
| `TF_TILESET_BUILD_OUTPUT_EXISTS` | The output exists; pass `--force` |
|
|
331
|
+
| `TF_TILESET_BUILD_OUTPUT_INVALID` | The output equals the input, or its folder is missing |
|
|
332
|
+
| `TF_TILESET_BUILD_TILE_TOO_LARGE` | A tile cannot fit without losing features |
|
|
333
|
+
| `TF_TILESET_BUILD_TOO_MANY_TILES` | More than 16 million tile entries; lower `--maxzoom` |
|
|
334
|
+
| `TF_TILESET_BUILD_ARCHIVE_TOO_LARGE` | The archive exceeds the publishable 8 GB |
|
|
335
|
+
| `TF_TILESET_BUILD_DISK_FULL` | Not enough free disk space |
|
|
336
|
+
|
|
337
|
+
### Limits
|
|
338
|
+
|
|
339
|
+
Features are spooled to disk rather than held in memory; a 5 million point, 777 MB GeoJSON file peaked
|
|
340
|
+
under 600 MB. A single very large feature must fit in memory on its own. Temporary files sit beside the
|
|
341
|
+
output and are removed on exit, including Ctrl-C. Allow free space of about twice the input, or three times the uncompressed Shapefile for a zip. Tiling
|
|
342
|
+
runs on one thread, with compression on the thread pool, so plan for up to a minute per million simple
|
|
343
|
+
features.
|
|
344
|
+
|
|
345
|
+
If a Shapefile has a DBF, each shape record needs a matching attribute row. A missing or truncated
|
|
346
|
+
row stops the build. Deleted rows are skipped, and extra rows produce a warning.
|
|
347
|
+
|
|
348
|
+
KML and GPX are streamed. A document with a DOCTYPE or entity declaration is refused, because neither
|
|
349
|
+
format needs one and it is how entity-expansion attacks work. Only the five predefined XML entities
|
|
350
|
+
and numeric character references are decoded. One tag can hold 1 MiB and 256 attributes, elements can
|
|
351
|
+
nest 128 levels, and one text value can hold 8 MiB in KML or 1 MiB in GPX. One KML feature can hold
|
|
352
|
+
64 MiB of attribute text.
|
|
353
|
+
|
|
354
|
+
A KMZ is streamed without being extracted. Its KML document can expand to 4 GiB, an archive can hold
|
|
355
|
+
65,536 entries, and an entry that inflates past its declared size stops the build.
|
|
356
|
+
|
|
357
|
+
A GeoPackage is opened read-only and runs none of its own SQL. Tables must be plain tables, so views
|
|
358
|
+
and virtual tables are not read. On early Node.js 22 releases, reading a GeoPackage in
|
|
359
|
+
write-ahead-log mode can leave `-wal` and `-shm` files beside it. A GeoPackage with a rollback journal
|
|
360
|
+
left by an interrupted writer is refused until a tool that can write to it recovers it.
|
|
361
|
+
|
|
362
|
+
One feature can hold 5 million positions and parts in every format, and one GeoJSON feature or Shapefile
|
|
363
|
+
record can take 256 MiB. The first bytes of a file must match its format, so a renamed file is refused
|
|
364
|
+
before parsing.
|
|
365
|
+
|
|
366
|
+
The build does not merge adjacent polygons at low zooms, cluster points, or join attributes. Polygons
|
|
367
|
+
are simplified independently, so neighbours that share a border can show hairline gaps at low zooms.
|
|
368
|
+
Thinning does not carry counts for heatmaps. Features crossing the antimeridian must be
|
|
369
|
+
split first; a warning flags segments wider than 180°. Web Mercator stops at latitude ±85.05: polygons
|
|
370
|
+
and lines are cut there, and points past it are skipped.
|
|
371
|
+
|
|
372
|
+
For control beyond this, use any tool that writes PMTiles. `tileflow tileset publish` accepts its
|
|
373
|
+
output after the same validation.
|
|
374
|
+
|
|
375
|
+
## tileset publish
|
|
376
|
+
|
|
377
|
+
Account-backed authorization renews automatically. Hashing reuses a 16 MiB buffer, and up to four parts stream concurrently with reusable buffers.
|
|
378
|
+
Administratively disabled uploads return `TF_TILESET_UPLOAD_DISABLED` without retrying. Temporary
|
|
379
|
+
service failures retain bounded retries.
|
|
380
|
+
|
|
381
|
+
## Restore
|
|
382
|
+
|
|
383
|
+
Deployments are immutable. Restoring one publishes its content again as a new version of the Map, so
|
|
384
|
+
the deployment it replaces stays in the history and can be restored in turn:
|
|
385
|
+
|
|
386
|
+
```bash
|
|
387
|
+
npx tileflow deployments list --map-id map_AbCdEfGhIjKlMnOp
|
|
388
|
+
npx tileflow deployments restore 12 --map-id map_AbCdEfGhIjKlMnOp
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
Allowed websites are not part of a deployment and do not change. A restore is refused when the data
|
|
392
|
+
the version needs has since changed, and the refusal names the source. Your repository still holds
|
|
393
|
+
what you replaced, so the next `tileflow deploy` publishes that again unless you revert it.
|
|
394
|
+
|
|
395
|
+
A tileset keeps its current and previous versions. Restoring swaps them for every Map that uses the
|
|
396
|
+
tileset, with no deployment, and restoring again undoes it:
|
|
397
|
+
|
|
398
|
+
```bash
|
|
399
|
+
npx tileflow tileset restore stores --team @acme
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
The restore is refused while an upload to that tileset is in progress, and while a live Map needs
|
|
403
|
+
data the previous version lacks. Restore or republish that Map first. An upload that nothing is
|
|
404
|
+
still sending can be cleared; it may belong to a teammate or a CI run, so check first:
|
|
405
|
+
|
|
406
|
+
```bash
|
|
407
|
+
npx tileflow tileset upload abort stores --team @acme
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
These commands are written for agents as well as people. With `--json`, a success has a `nextSteps`
|
|
411
|
+
array and a failure has `error.code`, `error.message` and `error.nextSteps`. Each step has a
|
|
412
|
+
`description` and, when one exists, the exact `command` to run. A failure blocked by an upload also
|
|
413
|
+
has `error.upload`, so you can see its state and expiry before aborting it. If a tileset restore
|
|
414
|
+
ends with `TF_TILESET_DELIVERY_PENDING` or its outcome is uncertain, follow the retry in `nextSteps`.
|
|
415
|
+
It passes `--expected-current-version <n>` from the original operation. The server can repair pending
|
|
416
|
+
delivery or report that the version has already changed. Check status before starting a new restore.
|
|
417
|
+
To cancel the upload named in a refusal, use its `--upload-id <id>` step. If that upload has ended,
|
|
418
|
+
the command leaves any replacement upload alone. While cancellation is pending, the command reports
|
|
419
|
+
`aborted: false`, the returned upload state and a status check. Recovery commands carry the selected
|
|
420
|
+
API origin and resource. Team-scoped commands keep the selected Team or the opaque tileset ID.
|
|
421
|
+
|
|
422
|
+
## Icon lock commands
|
|
423
|
+
|
|
424
|
+
The lock commands inspect the `iconSet()` references your config declares. They are the only
|
|
425
|
+
commands that resolve `latest`, and the only ones that write the lock. `install` fills in declared
|
|
426
|
+
references that have no pin, `update` moves selected references to their current latest revision,
|
|
427
|
+
and `pin` selects one exact revision. Every requested reference is resolved before a single
|
|
428
|
+
compare-and-swap write of the whole snapshot, so a concurrent writer fails the command instead of
|
|
429
|
+
losing its pins. No lock command rewrites `tileflow.config.ts`.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildTileset,
|
|
3
|
+
checkContainerFormat,
|
|
4
|
+
chooseCrsStatement,
|
|
5
|
+
chooseLayer,
|
|
6
|
+
detectFormat,
|
|
7
|
+
isKmz,
|
|
8
|
+
isZip
|
|
9
|
+
} from "./chunk-5U4J6E6Y.js";
|
|
10
|
+
import "./chunk-JVTGXPGV.js";
|
|
11
|
+
import "./chunk-43VQMAQO.js";
|
|
12
|
+
import "./chunk-GPPOECR5.js";
|
|
13
|
+
export {
|
|
14
|
+
buildTileset,
|
|
15
|
+
checkContainerFormat,
|
|
16
|
+
chooseCrsStatement,
|
|
17
|
+
chooseLayer,
|
|
18
|
+
detectFormat,
|
|
19
|
+
isKmz,
|
|
20
|
+
isZip
|
|
21
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// src/tileset-build/errors.ts
|
|
2
|
+
var TilesetBuildError = class extends Error {
|
|
3
|
+
constructor(code, path, message, choices) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.code = code;
|
|
6
|
+
this.path = path;
|
|
7
|
+
this.choices = choices;
|
|
8
|
+
this.name = "TilesetBuildError";
|
|
9
|
+
}
|
|
10
|
+
code;
|
|
11
|
+
path;
|
|
12
|
+
choices;
|
|
13
|
+
};
|
|
14
|
+
var SampleComplete = class extends TilesetBuildError {
|
|
15
|
+
constructor() {
|
|
16
|
+
super("TF_TILESET_BUILD_SAMPLE_COMPLETE", "input", "The sample is complete.");
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
function yieldToEventLoop() {
|
|
20
|
+
return new Promise((resolve) => setImmediate(resolve));
|
|
21
|
+
}
|
|
22
|
+
function listChoices(names) {
|
|
23
|
+
const shown = names.slice(0, 20).join(", ");
|
|
24
|
+
return names.length > 20 ? `${shown}, and ${names.length - 20} more` : shown;
|
|
25
|
+
}
|
|
26
|
+
var maximumFeaturePositions = 5e6;
|
|
27
|
+
var maximumFeatureBytes = 256 * 1024 * 1024;
|
|
28
|
+
function featureTooManyBytes(location) {
|
|
29
|
+
return new TilesetBuildError(
|
|
30
|
+
"TF_TILESET_BUILD_INPUT_LIMIT",
|
|
31
|
+
"input",
|
|
32
|
+
`${location}: the feature takes more than ${maximumFeatureBytes / 1024 ** 2} MiB. Split or simplify it first.`
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
function featureTooLarge(location) {
|
|
36
|
+
return new TilesetBuildError(
|
|
37
|
+
"TF_TILESET_BUILD_INPUT_LIMIT",
|
|
38
|
+
"input",
|
|
39
|
+
`${location}: the feature has more than ${maximumFeaturePositions} positions. Split or simplify it first.`
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export {
|
|
44
|
+
TilesetBuildError,
|
|
45
|
+
SampleComplete,
|
|
46
|
+
yieldToEventLoop,
|
|
47
|
+
listChoices,
|
|
48
|
+
maximumFeaturePositions,
|
|
49
|
+
maximumFeatureBytes,
|
|
50
|
+
featureTooManyBytes,
|
|
51
|
+
featureTooLarge
|
|
52
|
+
};
|