pmtiles-swarm 0.2.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 +53 -0
- package/LICENSE +28 -0
- package/NOTICE.md +47 -0
- package/README.md +172 -0
- package/docs/architecture-diagram.md +287 -0
- package/docs/engines.md +140 -0
- package/docs/publishing.md +387 -0
- package/docs/serving-tiles.md +218 -0
- package/docs/subscribing.md +187 -0
- package/package.json +56 -0
- package/src/api.js +472 -0
- package/src/catalog.js +159 -0
- package/src/config.js +231 -0
- package/src/engines/libtorrent.js +384 -0
- package/src/engines/qbittorrent.js +320 -0
- package/src/engines/types.js +59 -0
- package/src/engines/webtorrent.js +264 -0
- package/src/feed.js +226 -0
- package/src/file-source.js +67 -0
- package/src/index.js +177 -0
- package/src/library.js +567 -0
- package/src/mutable.js +197 -0
- package/src/origin.js +205 -0
- package/src/pmtiles-probe.js +94 -0
- package/src/read-engine.js +175 -0
- package/src/sources.js +245 -0
- package/src/subscriptions.js +158 -0
- package/src/tilejson.js +112 -0
- package/src/tiles.js +284 -0
- package/src/torrent-create.js +250 -0
- package/src/warm.js +251 -0
- package/src/watch.js +98 -0
- package/src/web/index.html +254 -0
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
# Serving tiles
|
|
2
|
+
|
|
3
|
+
Every archive in the catalog is also a tile endpoint. A map can point straight at
|
|
4
|
+
this server — no separate tile server, no unpacking the archive.
|
|
5
|
+
|
|
6
|
+
```
|
|
7
|
+
GET /archives/{infohash}/tiles.json TileJSON 3.0.0
|
|
8
|
+
GET /archives/{infohash}/{z}/{x}/{y}.{ext} one tile
|
|
9
|
+
GET /archives/{infohash}/archive.torrent the .torrent
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
`{ext}` must match what the archive holds: `pbf` or `mvt` for vector, `png`,
|
|
13
|
+
`jpg`, `webp`, `avif` for raster. Asking for the wrong one is a 400 rather than
|
|
14
|
+
a silently wrong content type.
|
|
15
|
+
|
|
16
|
+
Drop the TileJSON URL into any client that speaks TileJSON:
|
|
17
|
+
|
|
18
|
+
```js
|
|
19
|
+
map.addSource('basemap', {
|
|
20
|
+
type: 'vector',
|
|
21
|
+
url: 'https://swarm.example.org/archives/913d671f…/tiles.json',
|
|
22
|
+
});
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Where the bytes come from
|
|
26
|
+
|
|
27
|
+
This is the part that makes serving tiles from a torrent client worth doing at
|
|
28
|
+
all. The endpoint behaves the same either way, but underneath there are two very
|
|
29
|
+
different paths:
|
|
30
|
+
|
|
31
|
+
| This node | Reads from | Cost |
|
|
32
|
+
| --- | --- | --- |
|
|
33
|
+
| Holds a complete copy | The local file, directly | Disk, no swarm involvement |
|
|
34
|
+
| In cache mode | The swarm, one piece at a time | Only the pieces tiles are actually in |
|
|
35
|
+
|
|
36
|
+
A cache-mode node holds almost none of a 700 GiB planet archive but can still
|
|
37
|
+
answer for any tile in it. The first request for a cold area pulls the pieces
|
|
38
|
+
that tile lives in, and those pieces stay — both in the piece cache and in the
|
|
39
|
+
engine's store, where they are seeded back to the swarm. Reading an archive makes
|
|
40
|
+
you a better peer for it.
|
|
41
|
+
|
|
42
|
+
Which path is used is decided per archive, from the engine's own progress rather
|
|
43
|
+
than the file size on disk: both engines preallocate the full file, so a torrent
|
|
44
|
+
one piece in already looks complete by size.
|
|
45
|
+
|
|
46
|
+
`GET /api/torrents/{infohash}` reports which path an open archive is using.
|
|
47
|
+
|
|
48
|
+
### Engine support
|
|
49
|
+
|
|
50
|
+
Cache-mode reads need piece-level control, which not every engine has:
|
|
51
|
+
|
|
52
|
+
| Engine | Complete copy | Cache mode |
|
|
53
|
+
| --- | --- | --- |
|
|
54
|
+
| libtorrent | yes | yes — piece deadlines and per-piece priorities |
|
|
55
|
+
| webtorrent | yes | yes — shares the seeding client |
|
|
56
|
+
| qBittorrent | yes | **no** |
|
|
57
|
+
|
|
58
|
+
qBittorrent's WebUI has per-file priorities but nothing per piece and no way to
|
|
59
|
+
read one back, so there is no honest way to serve a tile from an archive it holds
|
|
60
|
+
only part of. It returns 501 with an explanation rather than a generic failure.
|
|
61
|
+
That is rarely a problem in practice, since qBittorrent is normally the bulk
|
|
62
|
+
seeder and a bulk seeder holds complete copies.
|
|
63
|
+
|
|
64
|
+
Both peer-to-peer engines reuse the client already seeding the archive rather
|
|
65
|
+
than starting a second one. One peer pool, one port, one DHT node — and for
|
|
66
|
+
libtorrent, one sidecar process.
|
|
67
|
+
|
|
68
|
+
## The `torrent` block
|
|
69
|
+
|
|
70
|
+
TileJSON documents from this server carry a non-standard `torrent` member:
|
|
71
|
+
|
|
72
|
+
```json
|
|
73
|
+
{
|
|
74
|
+
"tilejson": "3.0.0",
|
|
75
|
+
"tiles": ["https://swarm.example.org/archives/913d…/{z}/{x}/{y}.pbf"],
|
|
76
|
+
"minzoom": 0,
|
|
77
|
+
"maxzoom": 14,
|
|
78
|
+
"vector_layers": [{ "id": "water" }],
|
|
79
|
+
"torrent": {
|
|
80
|
+
"infohash": "913d671f3a28c5b8d605e28cf6bf01e293d36e86",
|
|
81
|
+
"magnet": "magnet:?xt=urn:btih:913d671f…",
|
|
82
|
+
"torrent": "https://swarm.example.org/archives/913d…/archive.torrent",
|
|
83
|
+
"name": "planet.pmtiles",
|
|
84
|
+
"size": 77242531840,
|
|
85
|
+
"webseeds": ["https://maps.example.org/planet.pmtiles"]
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
This is progressive enhancement, and the reason it is shaped this way:
|
|
91
|
+
|
|
92
|
+
- **A plain client ignores it.** TileJSON permits unknown members and MapLibre's
|
|
93
|
+
style-spec permits arbitrary source properties, so maplibre-gl-js, Leaflet and
|
|
94
|
+
anything else fetch tiles over HTTP exactly as they would from any tile server.
|
|
95
|
+
- **A torrent-aware client uses it.** It joins the swarm and reads tiles from
|
|
96
|
+
pieces, falling back to the HTTP URLs whenever the swarm cannot answer.
|
|
97
|
+
|
|
98
|
+
One URL serves both, so a style does not have to know which kind of client will
|
|
99
|
+
load it. That also means a torrent-aware client gets a working map immediately
|
|
100
|
+
over HTTP while the swarm is still finding peers, rather than staring at an empty
|
|
101
|
+
canvas for the 90 to 240 seconds a cold magnet can take to resolve metadata.
|
|
102
|
+
|
|
103
|
+
For an archive published as a mutable torrent, the block also carries
|
|
104
|
+
`mutable.publicKey`, so a client that understands BEP 46 can follow updates
|
|
105
|
+
rather than pinning to the version the document was generated from. See
|
|
106
|
+
[publishing](publishing.md).
|
|
107
|
+
|
|
108
|
+
## Caching
|
|
109
|
+
|
|
110
|
+
Tiles are served `Cache-Control: public, max-age=31536000, immutable`.
|
|
111
|
+
|
|
112
|
+
That is safe rather than optimistic. An infohash is a content hash: the bytes
|
|
113
|
+
under `/archives/{infohash}/…` cannot change, because different bytes would be a
|
|
114
|
+
different infohash. When a mutable archive is updated the infohash changes, so
|
|
115
|
+
the URLs change with it and caches never need invalidating — the old ones simply
|
|
116
|
+
stop being referenced.
|
|
117
|
+
|
|
118
|
+
The TileJSON itself is not cached that way, since it is the document that points
|
|
119
|
+
at the current infohash.
|
|
120
|
+
|
|
121
|
+
## Configuration
|
|
122
|
+
|
|
123
|
+
```json
|
|
124
|
+
{
|
|
125
|
+
"tiles": {
|
|
126
|
+
"maxOpenArchives": 16,
|
|
127
|
+
"directoryCacheEntries": 200,
|
|
128
|
+
"pieceCacheBytes": null,
|
|
129
|
+
"hydrateIdleMs": null,
|
|
130
|
+
"pieceTimeoutMs": 120000,
|
|
131
|
+
"readyTimeoutMs": 60000
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Warming a region
|
|
137
|
+
|
|
138
|
+
A cache-mode node is slow exactly once per region: the first request pulls the
|
|
139
|
+
pieces those tiles live in, and everything after is served from what it now
|
|
140
|
+
holds. Warming moves that cost off the request path, which matters most just
|
|
141
|
+
before adding a node to a load-balanced pool.
|
|
142
|
+
|
|
143
|
+
```sh
|
|
144
|
+
curl -X POST http://node:8090/api/torrents/$INFOHASH/warm \
|
|
145
|
+
-H 'content-type: application/json' \
|
|
146
|
+
-d '{"bounds": [5.9, 45.8, 10.5, 47.8], "minZoom": 0, "maxZoom": 12}'
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Everything is optional. Without `bounds` it uses the archive's own; without a
|
|
150
|
+
zoom range it warms from the archive's minimum up a handful of levels, because
|
|
151
|
+
tile counts quadruple per level and warming to z14 globally is never what was
|
|
152
|
+
meant. The zoom range is clamped to what the archive actually holds, so asking
|
|
153
|
+
for more than exists costs nothing.
|
|
154
|
+
|
|
155
|
+
```
|
|
156
|
+
GET /api/torrents/{infohash}/warm progress
|
|
157
|
+
DELETE /api/torrents/{infohash}/warm cancel
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Progress reports `total`, `done`, `hits`, `misses` and `errors`. Misses are
|
|
161
|
+
normal — a bounding box over a sparse archive covers tiles that were never
|
|
162
|
+
generated. A job that fails every tile without a single success gives up early
|
|
163
|
+
rather than grinding through the region to prove the archive is unreadable.
|
|
164
|
+
|
|
165
|
+
`maxTiles` caps a job (5000 by default) and `concurrency` sets how many tiles are
|
|
166
|
+
in flight (4 by default). Raising concurrency helps when the bottleneck is swarm
|
|
167
|
+
latency rather than bandwidth.
|
|
168
|
+
|
|
169
|
+
**Warming a mirror node does nothing useful** — it already holds everything and
|
|
170
|
+
reads its local file. The endpoint still works; it just finishes almost
|
|
171
|
+
immediately.
|
|
172
|
+
|
|
173
|
+
**Warming is cheaper on the second node.** Every node in the serving tier is a
|
|
174
|
+
peer in the same swarm, so a node warming a region a sibling already holds
|
|
175
|
+
fetches it from that sibling rather than from the original seed. Local service
|
|
176
|
+
discovery is on by default, so nodes on the same subnet find each other with no
|
|
177
|
+
configuration.
|
|
178
|
+
|
|
179
|
+
### Absolute URLs behind a proxy
|
|
180
|
+
|
|
181
|
+
TileJSON contains absolute tile URLs, so the server has to know how it is reached:
|
|
182
|
+
|
|
183
|
+
```json
|
|
184
|
+
{
|
|
185
|
+
"publicUrl": "https://maps.example.org",
|
|
186
|
+
"trustProxy": "loopback, 10.0.0.0/8"
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
`publicUrl` pins one canonical URL. Leave it unset and set `trustProxy` instead
|
|
191
|
+
to derive the URL per request from `X-Forwarded-Proto` and `X-Forwarded-Host`,
|
|
192
|
+
which lets one node serve `https://maps.example.org` and `http://maps.internal`
|
|
193
|
+
correctly at the same time.
|
|
194
|
+
|
|
195
|
+
Set at least one of them behind a TLS-terminating proxy. Otherwise the TileJSON
|
|
196
|
+
advertises `http://` tile URLs, and a browser that loaded the map over `https`
|
|
197
|
+
blocks them all as mixed content.
|
|
198
|
+
|
|
199
|
+
### Tuning
|
|
200
|
+
|
|
201
|
+
`maxOpenArchives` bounds how many archives are held open at once; the least
|
|
202
|
+
recently used is closed when the limit is passed, and reopened transparently if
|
|
203
|
+
asked for again. Each open archive costs a file descriptor or a torrent reader
|
|
204
|
+
plus its piece cache.
|
|
205
|
+
|
|
206
|
+
Leave `pieceCacheBytes` unset unless you have a reason. It is then sized from the
|
|
207
|
+
torrent's piece length, which is the safer default — a fixed byte budget is a
|
|
208
|
+
trap with 16 MiB pieces, since 64 MiB holds only four of them.
|
|
209
|
+
|
|
210
|
+
## What this is not
|
|
211
|
+
|
|
212
|
+
This is a tile endpoint for archives this node distributes, not a general tile
|
|
213
|
+
server. It does not render raster tiles from vector data, compose styles, serve
|
|
214
|
+
fonts or sprites, or reproject. For any of that, point
|
|
215
|
+
[tileserver-gl](https://github.com/maptiler/tileserver-gl) at the archive — it
|
|
216
|
+
can read PMTiles from a torrent directly, using the same
|
|
217
|
+
[pmtiles-torrent](https://github.com/TechIdiots-LLC/pmtiles-torrent) package this
|
|
218
|
+
does.
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# Subscribing and distribution
|
|
2
|
+
|
|
3
|
+
How a second node follows a first, and what it costs.
|
|
4
|
+
|
|
5
|
+
## A two-node setup
|
|
6
|
+
|
|
7
|
+
### The publisher
|
|
8
|
+
|
|
9
|
+
Watches a build directory, creates torrents, publishes a feed.
|
|
10
|
+
|
|
11
|
+
```json
|
|
12
|
+
{
|
|
13
|
+
"port": 8090,
|
|
14
|
+
"engine": "libtorrent",
|
|
15
|
+
"publicUrl": "https://maps.example.org",
|
|
16
|
+
"libtorrent": { "savePath": "/mnt/maps/generated" },
|
|
17
|
+
"watch": [
|
|
18
|
+
{
|
|
19
|
+
"path": "/mnt/maps/generated",
|
|
20
|
+
"category": "basemaps",
|
|
21
|
+
"webSeedBase": "https://maps.example.org/files"
|
|
22
|
+
}
|
|
23
|
+
]
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
`publicUrl` matters: feed items carry absolute links, and a subscriber that cannot
|
|
28
|
+
resolve them cannot fetch the `.torrent`. `webSeedBase` matters just as much — it makes
|
|
29
|
+
each archive usable before it has any peers at all.
|
|
30
|
+
|
|
31
|
+
Its feed is then at `https://maps.example.org/feed.xml`, with per-category feeds at
|
|
32
|
+
`/feed/basemaps.xml`.
|
|
33
|
+
|
|
34
|
+
### The subscriber
|
|
35
|
+
|
|
36
|
+
```json
|
|
37
|
+
{
|
|
38
|
+
"port": 8090,
|
|
39
|
+
"engine": "libtorrent",
|
|
40
|
+
"libtorrent": { "savePath": "/var/lib/maps" },
|
|
41
|
+
"subscriptions": [
|
|
42
|
+
{ "url": "https://maps.example.org/feed.xml", "mode": "cache" }
|
|
43
|
+
],
|
|
44
|
+
"subscriptionIntervalSeconds": 900
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Poll immediately rather than waiting for the interval:
|
|
49
|
+
|
|
50
|
+
```sh
|
|
51
|
+
curl -X POST localhost:8090/api/subscriptions/refresh
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## mirror or cache
|
|
55
|
+
|
|
56
|
+
The choice that decides what a subscriber costs.
|
|
57
|
+
|
|
58
|
+
| Mode | Disk | The node becomes |
|
|
59
|
+
| --- | --- | --- |
|
|
60
|
+
| `mirror` | the whole archive | A full seeder — redundancy for the swarm |
|
|
61
|
+
| `cache` | only what is read | A tile server that pays for what people look at |
|
|
62
|
+
|
|
63
|
+
**Cache mode is the interesting one.** The node joins the swarm and downloads nothing.
|
|
64
|
+
A tile server reads byte ranges from it on demand through `pmtiles-torrent`, so disk use
|
|
65
|
+
tracks what users actually view rather than what exists — which is what makes a 700 GiB
|
|
66
|
+
planet archive serveable from a small VPS. The node still seeds whatever pieces it picked
|
|
67
|
+
up along the way, so a cache-mode subscriber is a contributing swarm member, not a
|
|
68
|
+
freeloader.
|
|
69
|
+
|
|
70
|
+
Mirror mode is what you want on at least one or two nodes per archive, so the swarm does
|
|
71
|
+
not depend on a single origin.
|
|
72
|
+
|
|
73
|
+
### Filtering
|
|
74
|
+
|
|
75
|
+
One feed can serve subscribers with different appetites:
|
|
76
|
+
|
|
77
|
+
```json
|
|
78
|
+
{
|
|
79
|
+
"subscriptions": [
|
|
80
|
+
{ "url": "https://maps.example.org/feed.xml", "mode": "mirror", "filter": "europe" },
|
|
81
|
+
{ "url": "https://maps.example.org/feed.xml", "mode": "cache", "category": "planet" }
|
|
82
|
+
]
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
`filter` is a case-insensitive regular expression matched against the item title.
|
|
87
|
+
|
|
88
|
+
## Subscribing with plain qBittorrent
|
|
89
|
+
|
|
90
|
+
The feed is ordinary RSS 2.0 with `application/x-bittorrent` enclosures, which is exactly
|
|
91
|
+
what qBittorrent's built-in RSS auto-downloader consumes. **An operator can follow a
|
|
92
|
+
pmtiles-swarm feed today with no new software** — RSS tab, add the URL, set an
|
|
93
|
+
auto-download rule.
|
|
94
|
+
|
|
95
|
+
They lose cache mode, since that needs piece-level control, but for a node that wants a
|
|
96
|
+
full mirror it works out of the box.
|
|
97
|
+
|
|
98
|
+
## How many items a feed carries
|
|
99
|
+
|
|
100
|
+
```json
|
|
101
|
+
{ "feedMaxItems": 50 }
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Newest first; `0` means no limit. A consumer can also ask for a different number with
|
|
105
|
+
`?limit=`, so one publisher can serve subscribers with different poll intervals from the
|
|
106
|
+
same catalog:
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
/feed.xml the configured default
|
|
110
|
+
/feed.xml?limit=1 newest build only
|
|
111
|
+
/feed.xml?limit=0 everything
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
**Choose it against how often subscribers poll, not how tidy the feed looks.** A feed
|
|
115
|
+
holding a single item is only safe if everyone polls more often than you publish. If a
|
|
116
|
+
consumer is down overnight and you publish daily, that build drops off the feed before it
|
|
117
|
+
is seen — and nothing indicates it was missed. With a daily build and subscribers polling
|
|
118
|
+
every 15 minutes, 50 items is roughly seven weeks of slack.
|
|
119
|
+
|
|
120
|
+
The same applies to `lookbackDays` on scheduled sources, for the same reason and in the
|
|
121
|
+
other direction.
|
|
122
|
+
|
|
123
|
+
## What the feed carries
|
|
124
|
+
|
|
125
|
+
Beyond the standard fields, items carry a namespaced description of the map:
|
|
126
|
+
|
|
127
|
+
```xml
|
|
128
|
+
<item>
|
|
129
|
+
<title>planetiler-openmaptiles-latest.pmtiles</title>
|
|
130
|
+
<enclosure url="https://maps.example.org/api/torrents/5e1c…/file"
|
|
131
|
+
length="77230486744" type="application/x-bittorrent"/>
|
|
132
|
+
<pmtiles:infohash>5e1c143c400d15aaacfb1c748d4ab6d1b46c5df5</pmtiles:infohash>
|
|
133
|
+
<pmtiles:magnet>magnet:?xt=urn:btih:5e1c…</pmtiles:magnet>
|
|
134
|
+
<pmtiles:format>pbf</pmtiles:format>
|
|
135
|
+
<pmtiles:minzoom>0</pmtiles:minzoom>
|
|
136
|
+
<pmtiles:maxzoom>14</pmtiles:maxzoom>
|
|
137
|
+
<pmtiles:bounds>-180,-85.05,180,85.05</pmtiles:bounds>
|
|
138
|
+
</item>
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
This is the part a generic torrent feed cannot offer. A subscriber can decide whether it
|
|
142
|
+
wants a 72 GiB download from the feed alone, rather than fetching metadata to find out.
|
|
143
|
+
Generic clients ignore the namespace and still work.
|
|
144
|
+
|
|
145
|
+
## Updatable torrents (BEP 46)
|
|
146
|
+
|
|
147
|
+
A rebuilt archive is a different torrent, because the infohash is a hash of its content.
|
|
148
|
+
BEP 46 adds a level of indirection: an ed25519-signed record in the DHT whose value names
|
|
149
|
+
the *current* infohash, addressed by public key. Subscribers follow the key and are
|
|
150
|
+
carried across rebuilds.
|
|
151
|
+
|
|
152
|
+
```js
|
|
153
|
+
import { generatePublisherKey, mutableMagnet, publishInfoHash } from 'pmtiles-swarm/mutable';
|
|
154
|
+
|
|
155
|
+
const key = generatePublisherKey(); // back this up — it is the archive's identity
|
|
156
|
+
const magnet = mutableMagnet(key.publicKey, { name: 'planet.pmtiles' });
|
|
157
|
+
// magnet:?xs=urn:btpk:200d26e8… — note: no infohash
|
|
158
|
+
|
|
159
|
+
await publishInfoHash(dht, key, currentInfoHash);
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
The public key is the permanent address; losing the private key means subscribers can
|
|
163
|
+
never be moved forward again.
|
|
164
|
+
|
|
165
|
+
RSS and BEP 46 fail in opposite ways, which is the argument for publishing both:
|
|
166
|
+
|
|
167
|
+
| | Needs | Fails when |
|
|
168
|
+
| --- | --- | --- |
|
|
169
|
+
| RSS | a server that stays up | the server goes away |
|
|
170
|
+
| BEP 46 | periodic republishing | the record expires (hours, not days) |
|
|
171
|
+
|
|
172
|
+
**Status:** the crypto and magnet handling are tested — 32-byte keys, 64-byte signatures
|
|
173
|
+
verifying, roundtrip stable. Publishing and resolving against a live DHT, and interop
|
|
174
|
+
with libtorrent's exact value encoding, are **not yet verified**.
|
|
175
|
+
|
|
176
|
+
## Making the archives serveable
|
|
177
|
+
|
|
178
|
+
A subscriber holding archives is only useful if something serves tiles from them. Point
|
|
179
|
+
tileserver-gl at the same torrents:
|
|
180
|
+
|
|
181
|
+
```sh
|
|
182
|
+
export PMTILES_TORRENT_PATH=/var/lib/maps
|
|
183
|
+
tileserver-gl --file /var/lib/maps/torrents/5e1c….torrent
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Both processes then share the data directory: pmtiles-swarm manages membership and the
|
|
187
|
+
catalog, tileserver-gl reads ranges on demand and seeds what it fetches.
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pmtiles-swarm",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "BitTorrent distribution for PMTiles map archives: create torrents, watch folders, publish and subscribe to RSS feeds, and seed through qBittorrent or an embedded client",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "src/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"pmtiles-swarm": "src/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"src",
|
|
12
|
+
"docs",
|
|
13
|
+
"NOTICE.md",
|
|
14
|
+
"CHANGELOG.md"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"start": "node src/index.js",
|
|
18
|
+
"test": "node --test test/*.test.js"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"pmtiles",
|
|
22
|
+
"bittorrent",
|
|
23
|
+
"torrent",
|
|
24
|
+
"map",
|
|
25
|
+
"tiles",
|
|
26
|
+
"rss",
|
|
27
|
+
"p2p",
|
|
28
|
+
"distribution"
|
|
29
|
+
],
|
|
30
|
+
"license": "BSD-3-Clause",
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"chokidar": "^5.0.0",
|
|
33
|
+
"create-torrent": "^6.1.0",
|
|
34
|
+
"express": "^5.2.1",
|
|
35
|
+
"parse-torrent": "^11.0.24",
|
|
36
|
+
"pmtiles": "^4.4.1",
|
|
37
|
+
"pmtiles-torrent": "^0.1.1"
|
|
38
|
+
},
|
|
39
|
+
"optionalDependencies": {
|
|
40
|
+
"webtorrent": "^3.0.21"
|
|
41
|
+
},
|
|
42
|
+
"engines": {
|
|
43
|
+
"node": "^22.13.0 || 24"
|
|
44
|
+
},
|
|
45
|
+
"repository": {
|
|
46
|
+
"type": "git",
|
|
47
|
+
"url": "git+https://github.com/TechIdiots-LLC/pmtiles-swarm.git"
|
|
48
|
+
},
|
|
49
|
+
"bugs": {
|
|
50
|
+
"url": "https://github.com/TechIdiots-LLC/pmtiles-swarm/issues"
|
|
51
|
+
},
|
|
52
|
+
"homepage": "https://github.com/TechIdiots-LLC/pmtiles-swarm#readme",
|
|
53
|
+
"publishConfig": {
|
|
54
|
+
"access": "public"
|
|
55
|
+
}
|
|
56
|
+
}
|