pmtiles-swarm 0.61.0 → 0.63.0
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/CHANGELOG.md +223 -0
- package/NOTICE.md +13 -0
- package/README.md +72 -54
- package/docs/configuration.md +21 -1
- package/docs/internals.md +8 -1
- package/docs/serving-tiles.md +29 -0
- package/docs/tile-stacks.md +811 -0
- package/package.json +7 -1
- package/src/api.js +730 -0
- package/src/auth.js +2 -1
- package/src/codec.js +164 -0
- package/src/config.js +47 -2
- package/src/elevation.js +597 -0
- package/src/index.js +15 -0
- package/src/library.js +47 -6
- package/src/rgba.js +312 -0
- package/src/savepath.js +71 -0
- package/src/stack-cache.js +223 -0
- package/src/stacks.js +592 -0
- package/src/web/index.html +2350 -170
- package/src/web/preview.html +133 -12
- package/stacks.json.sample +12 -0
- package/tools/resume-doctor.py +1628 -0
- package/tools/resume-experiment.py +401 -0
- package/tools/tile-bench.mjs +566 -0
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,229 @@
|
|
|
7
7
|
### 🐞 Bug fixes
|
|
8
8
|
- _...Add new stuff here..._
|
|
9
9
|
|
|
10
|
+
## 0.63.0
|
|
11
|
+
### ✨ Features and improvements
|
|
12
|
+
- **A terrain archive previews as terrain.** An archive whose `encoding` is `terrarium`,
|
|
13
|
+
`mapbox`, or `custom` with all four factors present now opens as hillshade with 3D relief
|
|
14
|
+
rather than as the raster it literally is — a terrain-RGB image drawn as colour says nothing
|
|
15
|
+
about the ground. The pitch ceiling goes to 85°, since MapLibre's default of 60 is not enough
|
|
16
|
+
to look across a landscape, and terrain itself is a control rather than a setting, because
|
|
17
|
+
flat hillshade is easier to compare against a map than a perspective is.
|
|
18
|
+
|
|
19
|
+
No server change was needed. The TileJSON has carried `encoding` and the four custom factors
|
|
20
|
+
for a while, and the preview already fetches it — so this is the page reading what was
|
|
21
|
+
already there. Stacks preview through the same file, which means a terrain stack gets the
|
|
22
|
+
view from its own declared output encoding.
|
|
23
|
+
|
|
24
|
+
`mlt` is not terrain. It travels in the same field and is a vector format, so the check names
|
|
25
|
+
the three encodings it can draw rather than testing that an encoding is set.
|
|
26
|
+
|
|
27
|
+
The raw tiles stay one click away and the map position survives the switch, because a missing
|
|
28
|
+
DEM tile hillshades as flat ground rather than as missing — the only way to see a hole is to
|
|
29
|
+
look at the pixels. A `custom` archive carrying none of its factors is drawn as an ordinary
|
|
30
|
+
raster with a note saying why.
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
### 🐞 Bug fixes
|
|
34
|
+
- _...Add new stuff here..._
|
|
35
|
+
|
|
36
|
+
## 0.62.0
|
|
37
|
+
### ✨ Features and improvements
|
|
38
|
+
- **Tile stacks: several archives served as one tile endpoint.** A stack is a
|
|
39
|
+
recipe rather than a file — an ordered list of sources, bottom first, with the
|
|
40
|
+
last painting over the ones before it. Sources are named by category, so a
|
|
41
|
+
stack follows a rebuild the way `/latest/<category>/` does, or by infohash
|
|
42
|
+
where it must not move.
|
|
43
|
+
|
|
44
|
+
What ships is the part that needs no image handling: `/stacks/<id>/tiles.json`
|
|
45
|
+
and `/stacks/<id>/{z}/{x}/{y}.<ext>`, answered by the topmost source holding
|
|
46
|
+
the tile. That is enough for the common shape — a regional archive over a
|
|
47
|
+
global one — and it costs nothing per tile beyond the read it would have done
|
|
48
|
+
anyway. `X-Stack-Sources` names which sources were asked and what each said,
|
|
49
|
+
because a stack missing a layer still renders, and flat ocean looks like a
|
|
50
|
+
plausible map rather than like a failure.
|
|
51
|
+
|
|
52
|
+
A recipe asking for masking, height shifts, opacity, blending or a different
|
|
53
|
+
output encoding answers 501 and names the field. Those need a pixel codec,
|
|
54
|
+
which this node does not have yet; approximating them would be worse than
|
|
55
|
+
refusing. See docs/tile-stacks.md.
|
|
56
|
+
|
|
57
|
+
- **A pixel codec, for the parts of a stack that are not passthrough.** `sharp`,
|
|
58
|
+
as an optional dependency probed at first use — the same library tileserver-gl
|
|
59
|
+
uses, so one image stack covers both ends of the pipeline. A node that only
|
|
60
|
+
distributes archives never needs it, and a node without it answers 501 naming
|
|
61
|
+
what to install rather than failing at the first tile.
|
|
62
|
+
|
|
63
|
+
Encoding is **lossless by default and has to be made lossy by name**. A
|
|
64
|
+
terrain-RGB pixel is not a colour: the three channels are the three bytes of
|
|
65
|
+
one height, so a lossy codec that shifts red by one moves the ground by 65
|
|
66
|
+
kilometres. Over an ordinary gradient, lossy WebP is wrong by about 125 km at
|
|
67
|
+
worst where lossless is byte-exact.
|
|
68
|
+
|
|
69
|
+
- **Elevation stacks merge for real.** A stack whose sources mask, shift or
|
|
70
|
+
re-encode is now served rather than refused: each source is decoded to metres,
|
|
71
|
+
masked, adjusted, resampled in float space and painted in the recipe's order,
|
|
72
|
+
then encoded once. A source with no tile at the requested zoom is taken from
|
|
73
|
+
its parent and cropped to the right sub-square, which is what lets a z8 global
|
|
74
|
+
source keep contributing at z14 — the passthrough path cannot do that, because
|
|
75
|
+
a parent's *bytes* are the wrong tile.
|
|
76
|
+
|
|
77
|
+
Two ways to say "no data here": `maskValues` names decoded heights, and
|
|
78
|
+
`maskColors` names pixel colours as `"#rrggbb"` or `[r, g, b]`. The colour form
|
|
79
|
+
is exact, comparing the bytes that were stored; the height form rounds, because
|
|
80
|
+
decoding produces `base + n * interval` in floating point and a mask of `-0.1`
|
|
81
|
+
meets a decoded `-0.09999999999763531`.
|
|
82
|
+
|
|
83
|
+
A tile no source covered answers 404 rather than a slab of nodata, so a client
|
|
84
|
+
overzooms a lower one. That decision is made on the coverage before nodata is
|
|
85
|
+
substituted in — afterwards every pixel holds a real value and there is nothing
|
|
86
|
+
left to test.
|
|
87
|
+
|
|
88
|
+
- **Merged tiles are cached on disk.** A merged tile costs a read of every
|
|
89
|
+
source, a decode each and an encode, and against a cache-mode source those
|
|
90
|
+
reads may go to the swarm — doing that again for a tile somebody already asked
|
|
91
|
+
for is the difference between a map that pans and one that does not. Bounded
|
|
92
|
+
by `stacks.cacheBytes` (2 GiB by default, zero to turn it off), evicted
|
|
93
|
+
least-recently-used, and indexed from disk at startup so a restart does not
|
|
94
|
+
throw the work away.
|
|
95
|
+
|
|
96
|
+
Keyed by the tile's ETag, which already covers the recipe's revision and what
|
|
97
|
+
its sources resolved to — so editing a stack or rebuilding a source produces a
|
|
98
|
+
different key rather than needing anything to remember to invalidate the old
|
|
99
|
+
one. Only the merging path is cached: passthrough already costs one read, and
|
|
100
|
+
keeping its answer would put a second copy of the archive's own bytes beside
|
|
101
|
+
the first.
|
|
102
|
+
|
|
103
|
+
Several requests for the same tile at once run one merge between them, which
|
|
104
|
+
matters because each duplicate would otherwise issue its own reads to every
|
|
105
|
+
source underneath it.
|
|
106
|
+
|
|
107
|
+
- **A stack's TileJSON declares `sparse`.** True by default, which for a stack
|
|
108
|
+
is not a guess: `maxzoom` is the deepest any source reaches, so most of the
|
|
109
|
+
pyramid below it is covered by only some of them. A tile no source covered
|
|
110
|
+
answers 404, which is what makes maplibre-gl-js and maplibre-native overzoom
|
|
111
|
+
the parent rather than draw nothing — the same flag, the same name and the
|
|
112
|
+
same rule tileserver-gl reads. A stack can set `sparse: false` to answer 204
|
|
113
|
+
instead.
|
|
114
|
+
|
|
115
|
+
- **Image stacks composite, with opacity and blend modes.** `space: "rgba"`
|
|
116
|
+
treats a tile as what it looks like rather than as packed numbers: each source
|
|
117
|
+
carries an `opacity` and a `blend` (`normal`, `multiply`, `screen`, `overlay`,
|
|
118
|
+
`darken`, `lighten`), and `maskColors` clears coverage so what is underneath
|
|
119
|
+
shows through. Hillshade over satellite is the case it exists for.
|
|
120
|
+
|
|
121
|
+
The W3C compositing model in full, not the source-over shortcut — the shortcut
|
|
122
|
+
is only correct when the backdrop is opaque, and a hillshade over a satellite
|
|
123
|
+
tile with transparent edges is exactly where that shows. Resampling from a
|
|
124
|
+
parent interpolates with alpha premultiplied, which is what stops a
|
|
125
|
+
transparent pixel dragging its colour into its neighbours and ringing
|
|
126
|
+
everything with a dark halo.
|
|
127
|
+
|
|
128
|
+
Terrain stays lossless; imagery may be compressed as a picture, which is the
|
|
129
|
+
only place the two spaces disagree about encoding.
|
|
130
|
+
|
|
131
|
+
- **A Stacks view in the console.** Every stack, what each source resolved to,
|
|
132
|
+
the zooms each covers, and — kept apart, because they call for different
|
|
133
|
+
things — what is invalid in a recipe, what cannot be served without a codec,
|
|
134
|
+
and whose sources are missing. Sources are listed in the file's order with
|
|
135
|
+
their indices, so the screen and `data/stacks.json` never disagree.
|
|
136
|
+
|
|
137
|
+
The tab is always present, including on a node with no stacks — that is where
|
|
138
|
+
a stack gets made, so hiding it until one exists would make the first one
|
|
139
|
+
unreachable. An empty state says what a stack is and how to add one.
|
|
140
|
+
|
|
141
|
+
- **`savePathLayout: "name"`**, giving each joined archive `<savePath>/<archive name>/`. The
|
|
142
|
+
infohash layout already separated two builds of the same map, but nothing in
|
|
143
|
+
`<savePath>/7fae2931a9269684a7d4ed6e5fdd7d0014e6bcd1/` tells you which map is in it. This is
|
|
144
|
+
the same separation in a directory you can find.
|
|
145
|
+
|
|
146
|
+
The name comes from the metainfo, or from a magnet's `dn=` — every magnet this node hands out
|
|
147
|
+
carries one — and there are three tiers so a placement can never fail to produce a directory.
|
|
148
|
+
A name already held by another archive takes the first eight characters of the infohash as a
|
|
149
|
+
suffix, which is the rebuild case: same name, new infohash. An archive with no usable name
|
|
150
|
+
takes its infohash, which is a bare magnet from somewhere else.
|
|
151
|
+
|
|
152
|
+
The name is written by whoever built the torrent, so it is sanitised down to one path segment
|
|
153
|
+
before it is joined to anything: separators, control characters and the characters Windows
|
|
154
|
+
refuses become `-`, leading and trailing dots go, the Windows device names get out of the way,
|
|
155
|
+
and it is cut to 120 characters. A name that survives none of that falls back to the infohash.
|
|
156
|
+
|
|
157
|
+
The directory is settled when the archive is added and never revised. A name learned later
|
|
158
|
+
over BEP 9 leaves the data where it is rather than moving hundreds of gigabytes to match a
|
|
159
|
+
tidier directory. Changing the setting places new arrivals only — everything already held
|
|
160
|
+
keeps the save path recorded for it, so nothing moves and nothing is re-checked.
|
|
161
|
+
|
|
162
|
+
- **The settings pane has tabs, and the settings in them have names and reasons.** Seven
|
|
163
|
+
groups — Serving tiles, Network, Publishing, Feeds, Engine, Transfers, Security — describing
|
|
164
|
+
67 settings as labelled fields with help text, beside one tab holding what is not described
|
|
165
|
+
yet. Nested keys are described one at a time, so `libtorrent.resumeDir` is a field with a
|
|
166
|
+
reason rather than a line inside a JSON blob.
|
|
167
|
+
|
|
168
|
+
The tables moved with them. Monitored folders, watched web locations, RSS feeds and remote
|
|
169
|
+
nodes are four ways of answering one question — where archives arrive from — and they now
|
|
170
|
+
sit together under **Feeds** rather than stacked underneath everything else. Tokens went to
|
|
171
|
+
Security, hooks and the speed and seeding limits to Transfers. Save locations went with the
|
|
172
|
+
feeds: a location is where an arriving archive lands, and the four tables above it are what
|
|
173
|
+
choose one. A
|
|
174
|
+
tab is therefore not only schema fields: `Feeds` is four tables and nothing else, which is
|
|
175
|
+
why the tabs are declared rather than derived from the schema.
|
|
176
|
+
|
|
177
|
+
What the node's own RSS says moved the other way, into Publishing. One tab called Feeds
|
|
178
|
+
holding both what this node emits and what it subscribes to was the muddle that made the
|
|
179
|
+
grouping worth doing.
|
|
180
|
+
|
|
181
|
+
Every setting the configuration declares is now described. The six that were left — the
|
|
182
|
+
WebTorrent client, the console login, DHT publishing, tile statistics, the traffic chart and
|
|
183
|
+
automatic rebuilds — were objects rendered as unlabelled JSON, so changing `traffic.keepHours`
|
|
184
|
+
meant editing JSON in a browser. The last tab now removes itself when it holds nothing, and
|
|
185
|
+
comes back on its own the moment a setting exists that the schema does not describe.
|
|
186
|
+
|
|
187
|
+
They come from a schema rather than from hand-written markup, which is what makes the rest
|
|
188
|
+
of it possible. Adding a setting used to be three edits in three places: the control, a
|
|
189
|
+
branch in the save handler, and a line in the skip-list of the generic renderer — where
|
|
190
|
+
forgetting the third showed the field twice and forgetting the second saved nothing. It is
|
|
191
|
+
now one row.
|
|
192
|
+
|
|
193
|
+
Two things fall out of that. A list-valued setting like `trackers` is edited one per line
|
|
194
|
+
instead of as JSON in a textarea. And the restart badge is per setting rather than per
|
|
195
|
+
top-level key, which is the thing the server cannot express: `tiles.maxOpenArchives` is read
|
|
196
|
+
live while `tiles.directoryCacheEntries` is not, and `RESTART_REQUIRED` has to mark the
|
|
197
|
+
whole object because the console used to edit it as one blob.
|
|
198
|
+
|
|
199
|
+
A save sends only what changed. Sending a whole group back would have reported that a
|
|
200
|
+
restart was needed on every press of Save — `JSON.stringify` keeps `null` and drops
|
|
201
|
+
`undefined`, so a group carrying either where the config has the other compares unequal
|
|
202
|
+
against a configuration nobody touched.
|
|
203
|
+
|
|
204
|
+
### 🐞 Bug fixes
|
|
205
|
+
- **A settings control that could only ever fail.** Describing every setting as a field gave
|
|
206
|
+
`allowHooksFromApi` a checkbox, and that one is refused by `saveConfig` whether it is on or
|
|
207
|
+
off — it decides whether an API token may choose what code the service runs, so it is the
|
|
208
|
+
config file's alone. Toggling it returned an error, and because everything in a save is
|
|
209
|
+
checked before anything is applied, it took the rest of that tab's settings down with it.
|
|
210
|
+
|
|
211
|
+
Fields the config file owns now render disabled with a `config file` badge, and the reader
|
|
212
|
+
skips disabled controls outright, so a stray enabled attribute cannot turn into a rejected
|
|
213
|
+
save. The hooks themselves were never affected: their editor has always disabled itself and
|
|
214
|
+
said why.
|
|
215
|
+
|
|
216
|
+
- **Two settings reported success and changed nothing.** `tiles` and `resumeSaveIntervalSeconds`
|
|
217
|
+
are read while the process starts — the tile reader's directory cache when the store is
|
|
218
|
+
built, the resume timer when it is created — and neither is consulted again. Both were
|
|
219
|
+
absent from `RESTART_REQUIRED`, so the console applied them, said so, and the node went on
|
|
220
|
+
behaving exactly as before. Being told to restart when you need not have is a small cost;
|
|
221
|
+
a setting that lies about taking effect is not.
|
|
222
|
+
|
|
223
|
+
`tiles` is blunter than it could be, deliberately: `maxOpenArchives` beside
|
|
224
|
+
`directoryCacheEntries` really is read live, but the console edits the object as a whole so
|
|
225
|
+
a badge on half of it is not expressible. That resolves when the settings pane grows real
|
|
226
|
+
fields for it.
|
|
227
|
+
|
|
228
|
+
- **`seedingCheckIntervalSeconds` is reloadable, and was neither.** The sweep reads its own
|
|
229
|
+
interval when it starts and there is already a reloader that restarts the sweep, so this
|
|
230
|
+
needed no restart and no new machinery — only to be listed beside the `seeding` object it
|
|
231
|
+
belongs to.
|
|
232
|
+
|
|
10
233
|
## 0.61.0
|
|
11
234
|
### ✨ Features and improvements
|
|
12
235
|
- **"Style URL" is now "source URL", because that is what it is.** It goes in a style's
|
package/NOTICE.md
CHANGED
|
@@ -44,6 +44,19 @@ library is used unmodified through its public API; `src/web/preview.html` is our
|
|
|
44
44
|
served the same way from its own `dist`. Used through its documented options; no implementation
|
|
45
45
|
code is copied.
|
|
46
46
|
|
|
47
|
+
## sharp — Apache-2.0
|
|
48
|
+
|
|
49
|
+
> Copyright 2013 Lovell Fuller and others
|
|
50
|
+
> https://github.com/lovell/sharp
|
|
51
|
+
|
|
52
|
+
The pixel codec behind tile stacks: decoding a tile to samples and encoding the result. An
|
|
53
|
+
optional dependency, probed at first use, so a node that only distributes archives never
|
|
54
|
+
installs it — see `src/codec.js`. Used unmodified through its public API.
|
|
55
|
+
|
|
56
|
+
The prebuilt `@img/sharp-*` packages it resolves carry libvips (LGPL-3.0-or-later) and its own
|
|
57
|
+
dependencies, each under their own terms; sharp links to libvips dynamically and ships it
|
|
58
|
+
unmodified.
|
|
59
|
+
|
|
47
60
|
## qBittorrent — GPL-2.0-or-later
|
|
48
61
|
|
|
49
62
|
> https://github.com/qbittorrent/qBittorrent
|
package/README.md
CHANGED
|
@@ -237,7 +237,7 @@ each monitored folder and each watched web location:
|
|
|
237
237
|
|
|
238
238
|
```json
|
|
239
239
|
"locations": [
|
|
240
|
-
{ "name": "bulk storage", "path": "M
|
|
240
|
+
{ "name": "bulk storage", "path": "M:\\archives\\finished-unsorted" },
|
|
241
241
|
{ "name": "fast", "path": "/mnt/nvme/tiles" }
|
|
242
242
|
]
|
|
243
243
|
```
|
|
@@ -262,7 +262,18 @@ one at the same path is refused with a 409 naming the first. Where that comes up
|
|
|
262
262
|
data/torrents-data/7fae2931a9269684a7d4ed6e5fdd7d0014e6bcd1/planet.pmtiles
|
|
263
263
|
```
|
|
264
264
|
|
|
265
|
-
It works from a bare magnet, since the infohash is the one thing a magnet always carries.
|
|
265
|
+
It works from a bare magnet, since the infohash is the one thing a magnet always carries.
|
|
266
|
+
`"savePathLayout": "name"` gives the same separation in a directory you can find without knowing
|
|
267
|
+
the infohash:
|
|
268
|
+
|
|
269
|
+
```
|
|
270
|
+
data/torrents-data/planet.pmtiles/planet.pmtiles
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
The name comes from the metainfo, or from a magnet's `dn=` — every magnet this node hands out
|
|
274
|
+
carries one — and is sanitised down to a single path segment before it is used. A second archive
|
|
275
|
+
of the same name takes `planet.pmtiles-7fae2931`, and one joined by a bare magnet, which carries
|
|
276
|
+
no name at all, takes its infohash. Flat
|
|
266
277
|
stays the default: it is what makes dropping a finished archive into the save path before adding
|
|
267
278
|
its torrent work, and it keeps a served filename readable. Archives _created_ from a local file
|
|
268
279
|
are unaffected either way — they keep the file they were made from — and web seed URLs are built
|
|
@@ -739,58 +750,65 @@ which the endpoint answers 501.
|
|
|
739
750
|
|
|
740
751
|
## API
|
|
741
752
|
|
|
742
|
-
| Method
|
|
743
|
-
|
|
|
744
|
-
| `GET`
|
|
745
|
-
| `GET`
|
|
746
|
-
| `POST`
|
|
747
|
-
| `DELETE`
|
|
748
|
-
| `GET`
|
|
749
|
-
| `GET`
|
|
750
|
-
| `GET`
|
|
751
|
-
| `GET`
|
|
752
|
-
| `PATCH`
|
|
753
|
-
| `PATCH`
|
|
754
|
-
| `PATCH`
|
|
755
|
-
| `PATCH` `GET`
|
|
756
|
-
| `POST`
|
|
757
|
-
| `POST`
|
|
758
|
-
| `POST`
|
|
759
|
-
| `DELETE`
|
|
760
|
-
| `POST`
|
|
761
|
-
| `POST`
|
|
762
|
-
| `DELETE`
|
|
763
|
-
| `POST`
|
|
764
|
-
| `POST`
|
|
765
|
-
| `POST`
|
|
766
|
-
| `GET` `DELETE`
|
|
767
|
-
| `GET` `POST`
|
|
768
|
-
| `GET`
|
|
769
|
-
| `POST`
|
|
770
|
-
| `POST`
|
|
771
|
-
| `POST`
|
|
772
|
-
| `POST`
|
|
773
|
-
| `POST`
|
|
774
|
-
| `POST`
|
|
775
|
-
| `GET` `POST` `DELETE`
|
|
776
|
-
| `GET` `POST`
|
|
777
|
-
| `GET` `DELETE`
|
|
778
|
-
| `GET`
|
|
779
|
-
| `GET` `PATCH`
|
|
780
|
-
| `POST`
|
|
781
|
-
| `GET`
|
|
782
|
-
| `GET`
|
|
783
|
-
| `GET`
|
|
784
|
-
| `GET`
|
|
785
|
-
| `GET`
|
|
786
|
-
| `GET`
|
|
787
|
-
| `GET`
|
|
788
|
-
| `GET`
|
|
789
|
-
| `GET`
|
|
790
|
-
| `GET`
|
|
791
|
-
| `GET`
|
|
792
|
-
| `GET`
|
|
793
|
-
| `GET`
|
|
753
|
+
| Method | Path | Purpose |
|
|
754
|
+
| ---------------------- | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
755
|
+
| `GET` | `/api/status` | Engine health, counts, watched folders, save locations, free space, and whether the swarm can reach this node |
|
|
756
|
+
| `GET` | `/api/torrents` | Catalog joined with live swarm state and what is left of each seeding limit |
|
|
757
|
+
| `POST` | `/api/torrents` | Add via `{path}`, `{url}`, `{magnet}`, `{torrentUrl}`, or a raw `.torrent` body |
|
|
758
|
+
| `DELETE` | `/api/torrents/:infoHash` | Remove (`?deleteData=true` to delete data too) |
|
|
759
|
+
| `GET` | `/api/torrents/:infoHash` | One archive, with disk usage and how it is being read |
|
|
760
|
+
| `GET` | `/api/torrents/:infoHash/file`, `/magnet` | Download the `.torrent`, or its magnet URI |
|
|
761
|
+
| `GET` | `/api/torrents/:infoHash/peers`, `/trackers`, `/content` | Per-peer, per-tracker and per-file detail |
|
|
762
|
+
| `GET` | `/api/torrents/:infoHash/pieces` | Which pieces are held, how rare each is, and what peers hold |
|
|
763
|
+
| `PATCH` | `/api/torrents/:infoHash/mode` | Switch between mirror and cache |
|
|
764
|
+
| `PATCH` | `/api/torrents/:infoHash/categories` | Set, add or remove categories |
|
|
765
|
+
| `PATCH` | `/api/torrents/:infoHash/seeding` | Per-archive seeding limit, or "use the global one" |
|
|
766
|
+
| `PATCH` `GET` | `/api/torrents/:infoHash/location` | Move the data; poll the move |
|
|
767
|
+
| `POST` | `/api/torrents/:infoHash/recheck` | Hash what is on disk again and believe the result — for an archive whose progress and whose files disagree |
|
|
768
|
+
| `POST` | `/api/torrents/:infoHash/pause`, `/resume` | Stop offering it, without forgetting it |
|
|
769
|
+
| `POST` | `/api/torrents/:infoHash/webseeds` | Add web seeds — does not change the infohash |
|
|
770
|
+
| `DELETE` | `/api/torrents/:infoHash/webseeds` | Drop web seeds — likewise |
|
|
771
|
+
| `POST` | `/api/torrents/:infoHash/publish` | What this node offers of the archive itself: `serveArchive`, `selfWebSeed`, `publicDownload` |
|
|
772
|
+
| `POST` | `/api/torrents/:infoHash/warm` | Pre-fetch a region (`GET` for progress, `DELETE` to cancel) |
|
|
773
|
+
| `DELETE` | `/api/torrents/:infoHash/cache` | Reclaim cached pieces, keep the archive |
|
|
774
|
+
| `POST` | `/api/torrents/:infoHash/check` | Has the source changed since the torrent was made? |
|
|
775
|
+
| `POST` | `/api/torrents/:infoHash/rebuild` | Rebuild from the current source (mints a new infohash) |
|
|
776
|
+
| `POST` | `/api/check-origins` | Check every archive with a watchable source |
|
|
777
|
+
| `GET` `DELETE` | `/api/adds` | Adds still in flight — downloads, and local files being hashed — and cancelling a download by URL. A hash cannot be stopped part-way, so it is listed but not cancellable |
|
|
778
|
+
| `GET` `POST` | `/api/speed` | Which speed limits are in force, and the manual switch between the two sets |
|
|
779
|
+
| `GET` | `/api/categories` | Every category, with the endpoints resolving to its newest build |
|
|
780
|
+
| `POST` | `/api/adopt`, `/api/adopt/candidates` | Take over what an engine or another node holds |
|
|
781
|
+
| `POST` | `/api/sources/preview` | What a watched web location would take, without taking it |
|
|
782
|
+
| `POST` | `/api/subscriptions/preview` | Whether a peer is reachable and what it offers |
|
|
783
|
+
| `POST` | `/api/subscriptions/refresh` | Poll subscribed feeds now |
|
|
784
|
+
| `POST` | `/api/sources/check` | Check scheduled sources now, rather than at the next due time |
|
|
785
|
+
| `POST` | `/api/torrents/:infoHash/hooks/complete` | Run the completion hook again for one archive |
|
|
786
|
+
| `GET` `POST` `DELETE` | `/api/tokens`, `/api/tokens/:id` | Mint, list and revoke access tokens |
|
|
787
|
+
| `GET` `POST` | `/api/restart` | What a restart would do, and doing it |
|
|
788
|
+
| `GET` `DELETE` | `/api/stats` | What this node has served — per-archive counters and the last N requests; `DELETE` clears them |
|
|
789
|
+
| `GET` | `/api/traffic` | Upload and download speed per archive over time, from `stats.db`; `infoHash`, `hours` and `buckets` narrow it, and `totals` ranks archives by bytes moved |
|
|
790
|
+
| `GET` `PATCH` | `/api/config` | Read and change settings |
|
|
791
|
+
| `POST` | `/api/login`, `/api/logout` | Console sign-in |
|
|
792
|
+
| `GET` | `/api/session` | Who this request is, and whether a credential is needed at all |
|
|
793
|
+
| `GET` | `/api/catalog` | The whole catalogue, for a peer keeping itself in step |
|
|
794
|
+
| `GET` | `/archives/:infoHash/tiles.json` | TileJSON — **public** |
|
|
795
|
+
| `GET` | `/archives/:infoHash/:z/:x/:y.:ext` | One tile — **public** |
|
|
796
|
+
| `GET` | `/health` | 200 when this node can serve, 503 when its engine cannot — **public**, no credential, for a load balancer |
|
|
797
|
+
| `GET` | `/archives/:infoHash/ready` | Whether this node can serve _this_ archive yet: 200 ready, 503 not yet, 415 never — **public** |
|
|
798
|
+
| `GET` | `/archives/:infoHash/archive.torrent` | The `.torrent` a torrent-aware client joins with — **public** |
|
|
799
|
+
| `GET` | `/archives/:infoHash/archive.pmtiles` | The archive itself, by byte range — what any PMTiles reader wants, and a valid web seed. Only where [`serveArchive`](docs/configuration.md#servearchive) is on — **public**. Complete archives, unless [`serveArchiveFromSwarm`](docs/configuration.md#servearchivefromswarm) is set, which answers a bounded range from the swarm instead |
|
|
800
|
+
| `GET` | `/archives/:infoHash/preview` | Map preview for one archive — admin, not public |
|
|
801
|
+
| `GET` | `/latest/` | Every category and the endpoints that resolve to its newest build — **public**, the index of everything below |
|
|
802
|
+
| `GET` | `/latest/:category/tiles.json`, `/:name.torrent`, `/magnet` | The newest build in a category — **public**. The torrent name is yours to choose, so a link can read `planetiler-openmaptiles-latest.torrent`; it redirects to the immutable URL, which names the download after the build it actually is |
|
|
803
|
+
| `GET` | `/latest/:category/archive.pmtiles` | The newest build in a category as a file, by byte range — **public**, and gated by [`serveArchive`](docs/configuration.md#servearchive). What to point a PMTiles reader at when you want "whichever is current" rather than one build |
|
|
804
|
+
| `GET` | `/api/stacks` | Every stack, with what each source resolved to and why one cannot be served yet |
|
|
805
|
+
| `GET` | `/api/torrents/:infoHash/stacks` | Which stacks would break if this archive were removed, and how |
|
|
806
|
+
| `GET`, `PUT`, `DELETE` | `/api/stacks/:id/raw`, `/api/stacks/:id` | Read a recipe as written, save one, or remove it. `/raw` is the recipe; `/api/stacks` is what it resolved to |
|
|
807
|
+
| `GET` | `/stacks/:id/preview` | A map of a stack, for looking at it — **public** |
|
|
808
|
+
| `GET` | `/stacks/:id/tiles.json` | TileJSON for a stack — **public**. `maxzoom` is the maximum over its sources, not the minimum |
|
|
809
|
+
| `GET` | `/stacks/:id/:size/:z/:x/:y.:ext` | The same tile at 256 or 512 px. A tile's coordinates are an extent rather than a pixel count, so this is the same ground on a finer or coarser grid |
|
|
810
|
+
| `GET` | `/stacks/:id/:z/:x/:y.:ext` | One tile of a stack — **public**. Answered by the topmost source holding it; `X-Stack-Sources` says which were asked and what each said |
|
|
811
|
+
| `GET` | `/feed.xml`, `/feed/:category.xml`, `/latest/:category.xml` | RSS — **public** |
|
|
794
812
|
|
|
795
813
|
Everything under `/api/` is guarded once a credential is configured; tiles, TileJSON and the feeds
|
|
796
814
|
never are. A `peer` token may read but not change, and may be narrowed to some categories. See
|
package/docs/configuration.md
CHANGED
|
@@ -95,7 +95,7 @@ the question entirely. See [haproxy.md](haproxy.md).
|
|
|
95
95
|
| `dataDir` | `'./data'` | catalog, generated `.torrent` files and keys |
|
|
96
96
|
| `savePath` | unset | where archive data lives |
|
|
97
97
|
| `cacheSavePath` | unset | separate path for cache-mode pieces |
|
|
98
|
-
| `savePathLayout` | `'flat'` | `'flat'` or `'
|
|
98
|
+
| `savePathLayout` | `'flat'` | `'flat'`, `'infohash'` or `'name'` |
|
|
99
99
|
| `locations` | `[]` | named places for data to land: `[{ name, path }]` |
|
|
100
100
|
| `incompleteSuffix` | `'.incomplete'` | marker on an archive that is not whole yet |
|
|
101
101
|
| `completionCheckIntervalSeconds` | `15` | how often to look for finished downloads |
|
|
@@ -133,6 +133,8 @@ measurable and clearable as a directory.
|
|
|
133
133
|
- `'infohash'` — each archive under `<savePath>/<infohash>/`. Two builds of the
|
|
134
134
|
same map are both `planet.pmtiles`, and this is the only arrangement in which
|
|
135
135
|
that can never matter.
|
|
136
|
+
- `'name'` — each archive under `<savePath>/<archive name>/`. The same
|
|
137
|
+
separation, in a directory you can find without knowing the infohash.
|
|
136
138
|
|
|
137
139
|
Flat by default, because the collision it avoids is now refused outright when the
|
|
138
140
|
second archive is added — so the cost of flat is an error message at the moment
|
|
@@ -143,6 +145,24 @@ Only joined archives are placed. One created here keeps the file it was made
|
|
|
143
145
|
from, and web seed URLs are built from the published location rather than from
|
|
144
146
|
the save path.
|
|
145
147
|
|
|
148
|
+
Changing this places new arrivals only. Everything already held keeps the save
|
|
149
|
+
path recorded for it, so nothing moves on disk and nothing has to be re-checked.
|
|
150
|
+
|
|
151
|
+
Under `'name'` the directory is settled when the archive is added, from whatever
|
|
152
|
+
name is known then, and never revised afterwards:
|
|
153
|
+
|
|
154
|
+
- The name is taken from the metainfo, or from a magnet's `dn=`. Every magnet
|
|
155
|
+
this node hands out carries one.
|
|
156
|
+
- A **bare magnet** carries no name, so that archive gets its infohash instead.
|
|
157
|
+
The name learned later over BEP 9 does not move it — the data is already
|
|
158
|
+
there, and a rename to tidy the directory is not worth a re-check of it.
|
|
159
|
+
- A name already held by another archive gets the first eight characters of the
|
|
160
|
+
infohash appended, which is the rebuild case: same name, new infohash.
|
|
161
|
+
- The name is written by whoever built the torrent, so it is sanitised down to
|
|
162
|
+
one path segment before it is used. Separators, control characters and the
|
|
163
|
+
ones Windows refuses become `-`; a name that survives none of that falls back
|
|
164
|
+
to the infohash.
|
|
165
|
+
|
|
146
166
|
### `incompleteSuffix`
|
|
147
167
|
|
|
148
168
|
Set to an empty string to switch it off, in which case a partial archive is
|
package/docs/internals.md
CHANGED
|
@@ -103,6 +103,13 @@ writing pieces into one file, and neither ends up with the archive it thinks it
|
|
|
103
103
|
has. This is caught when the second archive is added, where it can still be
|
|
104
104
|
answered by choosing somewhere else to put it.
|
|
105
105
|
|
|
106
|
+
`savePathLayout` is the standing answer rather than the per-archive one. Under
|
|
107
|
+
`'infohash'` the collision cannot arise. Under `'name'` it still can — the names
|
|
108
|
+
are what collide — so the second archive takes `<name>-<first eight of the
|
|
109
|
+
infohash>`, and an archive with no usable name at all takes its infohash. Both
|
|
110
|
+
are decided once, when the archive is added; a name learned later over BEP 9
|
|
111
|
+
leaves the directory alone rather than moving the data to match it.
|
|
112
|
+
|
|
106
113
|
### Staging, for an archive fetched from a URL
|
|
107
114
|
|
|
108
115
|
An archive fetched over HTTP has no infohash while it is being fetched — the
|
|
@@ -868,7 +875,7 @@ carry several categories on purpose — a planet build is both `basemaps` and
|
|
|
868
875
|
answer.
|
|
869
876
|
|
|
870
877
|
So the location is chosen rather than derived, and naming them is what makes that
|
|
871
|
-
bearable: `M:\
|
|
878
|
+
bearable: `M:\archives\finished-unsorted` is not something anyone should retype, and a
|
|
872
879
|
name survives the path changing underneath it.
|
|
873
880
|
|
|
874
881
|
Only new data is placed. An archive records where it was put and keeps it, so
|
package/docs/serving-tiles.md
CHANGED
|
@@ -748,6 +748,35 @@ first tile can be seconds away.
|
|
|
748
748
|
**Raster archives get the raster.** There is nothing to inspect in an image, so
|
|
749
749
|
the panel says so and the map is for checking coverage.
|
|
750
750
|
|
|
751
|
+
**Terrain archives get hillshade and 3D relief**, and open in it. An archive
|
|
752
|
+
whose [`encoding`](tilejson.md#encoding) is `terrarium`, `mapbox`, or `custom`
|
|
753
|
+
with all four factors present is drawn as a `raster-dem` source: a hillshade
|
|
754
|
+
layer over sea-coloured background, terrain switchable from the control beside
|
|
755
|
+
the compass, and the pitch ceiling raised to 85° — MapLibre's default of 60 is
|
|
756
|
+
not enough to see relief, and looking across a landscape rather than down at it
|
|
757
|
+
is the point of the view.
|
|
758
|
+
|
|
759
|
+
`mlt` is not terrain. It travels in the same `encoding` field and is a vector
|
|
760
|
+
format, which is why the check names the three it can draw rather than testing
|
|
761
|
+
that an encoding is set at all.
|
|
762
|
+
|
|
763
|
+
Terrain and hillshade get a source each over the same URL, which is what
|
|
764
|
+
MapLibre's own terrain example does and what tileserver-gl ships. The two ask a
|
|
765
|
+
DEM source for different things — one is sampled for height across the whole
|
|
766
|
+
viewport, the other is shaded per tile — and sharing one between them has a
|
|
767
|
+
history of rendering artefacts. The tiles are requested once either way, since
|
|
768
|
+
the HTTP cache answers the second source.
|
|
769
|
+
|
|
770
|
+
**The raw tiles stay one click away**, from the link in the header, and the map
|
|
771
|
+
position survives the switch. That view is not decorative: a missing DEM tile
|
|
772
|
+
hillshades as flat ground rather than as missing, so the only way to see a hole
|
|
773
|
+
is to look at the pixels. A `custom` archive carrying none of its four factors
|
|
774
|
+
is drawn as an ordinary raster with a note saying why — pixels whose meaning is
|
|
775
|
+
not stated cannot honestly be rendered as heights.
|
|
776
|
+
|
|
777
|
+
Stacks preview through the same page, so a terrain stack gets the terrain view
|
|
778
|
+
from its own declared output encoding. See [tile stacks](tile-stacks.md).
|
|
779
|
+
|
|
751
780
|
**What it has actually served** is on the archive's detail, as `served`, and
|
|
752
781
|
across the node at `GET /api/stats` — requests, bytes, a breakdown by zoom and
|
|
753
782
|
status, and which client addresses asked. Worth reading beside `reading`: an
|