pmtiles-swarm 0.62.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 +26 -0
- package/docs/serving-tiles.md +29 -0
- package/package.json +1 -1
- package/src/web/preview.html +133 -12
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,32 @@
|
|
|
4
4
|
### β¨ Features and improvements
|
|
5
5
|
- _...Add new stuff here..._
|
|
6
6
|
|
|
7
|
+
### π Bug fixes
|
|
8
|
+
- _...Add new stuff here..._
|
|
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
|
+
|
|
7
33
|
### π Bug fixes
|
|
8
34
|
- _...Add new stuff here..._
|
|
9
35
|
|
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
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pmtiles-swarm",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.63.0",
|
|
4
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
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
package/src/web/preview.html
CHANGED
|
@@ -36,7 +36,8 @@
|
|
|
36
36
|
header h1 { font-size: 1rem; margin: 0; }
|
|
37
37
|
header .sub { color: var(--muted); font-size: 0.8rem; }
|
|
38
38
|
header code { font-size: 0.75rem; color: var(--muted); }
|
|
39
|
-
header
|
|
39
|
+
header .links { margin-left: auto; display: flex; gap: 0.9rem; }
|
|
40
|
+
header a { color: var(--accent); font-size: 0.8rem; }
|
|
40
41
|
#map { flex: 1; min-height: 0; }
|
|
41
42
|
.banner {
|
|
42
43
|
margin: auto;
|
|
@@ -66,7 +67,10 @@
|
|
|
66
67
|
<h1 id="name">Loadingβ¦</h1>
|
|
67
68
|
<span class="sub" id="summary"></span>
|
|
68
69
|
<code id="endpoint"></code>
|
|
69
|
-
<
|
|
70
|
+
<span class="links">
|
|
71
|
+
<a id="mode" href="#" hidden></a>
|
|
72
|
+
<a href="/">β console</a>
|
|
73
|
+
</span>
|
|
70
74
|
</header>
|
|
71
75
|
<div id="note" hidden></div>
|
|
72
76
|
<div id="map"></div>
|
|
@@ -113,6 +117,26 @@
|
|
|
113
117
|
const vector =
|
|
114
118
|
vectorLayers.length > 0 || /\.(pbf|mvt)(\?|$)/.test(tilejson.tiles?.[0] ?? '');
|
|
115
119
|
|
|
120
|
+
// The encodings MapLibre can read as a raster-dem source. Named rather
|
|
121
|
+
// than tested as "encoding is set", because `mlt` travels in the same
|
|
122
|
+
// field and is a vector format -- treating it as terrain would render a
|
|
123
|
+
// hillshade of nothing.
|
|
124
|
+
const TERRAIN = ['terrarium', 'mapbox', 'custom'];
|
|
125
|
+
const FACTORS = ['redFactor', 'greenFactor', 'blueFactor', 'baseShift'];
|
|
126
|
+
// `custom` is unreadable without all four, so an archive that declares it
|
|
127
|
+
// and carries none is not terrain this page can draw.
|
|
128
|
+
const terrainReady =
|
|
129
|
+
!vector &&
|
|
130
|
+
TERRAIN.includes(tilejson.encoding) &&
|
|
131
|
+
(tilejson.encoding !== 'custom' ||
|
|
132
|
+
FACTORS.every((name) => Number.isFinite(tilejson[name])));
|
|
133
|
+
// Terrain by default where there is terrain: a terrain-RGB archive drawn
|
|
134
|
+
// as an ordinary raster is a psychedelic mess that says nothing about the
|
|
135
|
+
// ground. The raw tiles stay one click away, because they are what shows
|
|
136
|
+
// a hole in the data -- a missing tile reads as flat, not as missing.
|
|
137
|
+
const raw = new URLSearchParams(location.search).has('raw');
|
|
138
|
+
const terrain = terrainReady && !raw;
|
|
139
|
+
|
|
116
140
|
$('name').textContent = tilejson.name ?? tileJsonUrl;
|
|
117
141
|
$('summary').textContent =
|
|
118
142
|
`${vector ? 'vector' : 'raster'} Β· z${tilejson.minzoom ?? 0}β${tilejson.maxzoom ?? 14}` +
|
|
@@ -120,24 +144,104 @@
|
|
|
120
144
|
? vectorLayers.length > 0
|
|
121
145
|
? ` Β· ${vectorLayers.length} layers`
|
|
122
146
|
: ' Β· no layer list yet'
|
|
123
|
-
:
|
|
147
|
+
: terrainReady
|
|
148
|
+
? ` Β· ${tilejson.encoding} terrain`
|
|
149
|
+
: '');
|
|
124
150
|
$('endpoint').textContent = tilejson.tiles?.[0] ?? '';
|
|
125
151
|
|
|
126
152
|
// One source, described entirely by the archive's own TileJSON. That
|
|
127
153
|
// endpoint is already a complete, valid source description, so there is
|
|
128
154
|
// nothing to reconstruct here β which is most of the reason it is worth
|
|
129
155
|
// having.
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
156
|
+
/**
|
|
157
|
+
* The archive as a DEM source, described by its own TileJSON.
|
|
158
|
+
* @returns {object} - A raster-dem source.
|
|
159
|
+
*/
|
|
160
|
+
const demSource = () => ({
|
|
161
|
+
type: 'raster-dem',
|
|
162
|
+
url: tileJsonUrl,
|
|
163
|
+
encoding: tilejson.encoding,
|
|
164
|
+
...(tilejson.encoding === 'custom'
|
|
165
|
+
? Object.fromEntries(FACTORS.map((name) => [name, tilejson[name]]))
|
|
166
|
+
: {}),
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
const style = terrain
|
|
170
|
+
? {
|
|
171
|
+
version: 8,
|
|
172
|
+
// Two sources over one URL, which is what MapLibre's own terrain
|
|
173
|
+
// example does and what tileserver-gl ships. Terrain and hillshade
|
|
174
|
+
// ask a DEM source for different things -- one is sampled for
|
|
175
|
+
// height across the whole viewport, the other is shaded per tile --
|
|
176
|
+
// and sharing one source between them has a history of rendering
|
|
177
|
+
// artefacts. The tiles are requested once either way: the HTTP
|
|
178
|
+
// cache answers the second source.
|
|
179
|
+
sources: { terrain: demSource(), hillshade: demSource() },
|
|
180
|
+
terrain: { source: 'terrain' },
|
|
181
|
+
layers: [
|
|
182
|
+
{
|
|
183
|
+
id: 'bg',
|
|
184
|
+
type: 'background',
|
|
185
|
+
// Sea, not page furniture. Where a terrain archive has no data
|
|
186
|
+
// it is usually because there is water there.
|
|
187
|
+
paint: { 'background-color': '#16323f' },
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
id: 'hillshade',
|
|
191
|
+
type: 'hillshade',
|
|
192
|
+
source: 'hillshade',
|
|
193
|
+
paint: {
|
|
194
|
+
'hillshade-shadow-color': 'hsl(39, 21%, 33%)',
|
|
195
|
+
'hillshade-illumination-direction': 315,
|
|
196
|
+
'hillshade-exaggeration': 0.8,
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
],
|
|
200
|
+
}
|
|
201
|
+
: {
|
|
202
|
+
version: 8,
|
|
203
|
+
sources: {
|
|
204
|
+
archive: { type: vector ? 'vector' : 'raster', url: tileJsonUrl },
|
|
205
|
+
},
|
|
206
|
+
layers: [
|
|
207
|
+
{
|
|
208
|
+
id: 'bg',
|
|
209
|
+
type: 'background',
|
|
210
|
+
paint: { 'background-color': '#12141a' },
|
|
211
|
+
},
|
|
212
|
+
],
|
|
213
|
+
};
|
|
214
|
+
if (!vector && !terrain) {
|
|
138
215
|
style.layers.push({ id: 'archive', type: 'raster', source: 'archive' });
|
|
139
216
|
}
|
|
140
217
|
|
|
218
|
+
// Offered rather than assumed, both ways round. Reading heights off the
|
|
219
|
+
// raw pixels is the only way to see that a tile is present but wrong.
|
|
220
|
+
if (terrainReady) {
|
|
221
|
+
const toggle = $('mode');
|
|
222
|
+
toggle.hidden = false;
|
|
223
|
+
toggle.textContent = terrain ? 'raw tiles β' : 'terrain β';
|
|
224
|
+
toggle.addEventListener('click', (event) => {
|
|
225
|
+
event.preventDefault();
|
|
226
|
+
// Assembled at click time so the hash, which is where the map keeps
|
|
227
|
+
// the position it is currently showing, survives the reload.
|
|
228
|
+
location.href =
|
|
229
|
+
location.pathname + (terrain ? '?raw=1' : '') + location.hash;
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// An archive that says `custom` and carries none of the four factors
|
|
234
|
+
// cannot be read as heights by anything, this page included.
|
|
235
|
+
if (!vector && tilejson.encoding === 'custom' && !terrainReady) {
|
|
236
|
+
$('note').hidden = false;
|
|
237
|
+
$('note').textContent =
|
|
238
|
+
'This archive declares the custom terrain encoding but not the four ' +
|
|
239
|
+
'factors it is unreadable without β redFactor, greenFactor, ' +
|
|
240
|
+
'blueFactor and baseShift. It is drawn as an ordinary raster, which ' +
|
|
241
|
+
'is the only honest thing to do with pixels whose meaning is not ' +
|
|
242
|
+
'stated.';
|
|
243
|
+
}
|
|
244
|
+
|
|
141
245
|
// An archive whose metadata has not been read yet cannot be drawn: a
|
|
142
246
|
// vector style needs `source-layer` names, and nothing in a tile request
|
|
143
247
|
// supplies them. Say so, rather than presenting a black rectangle.
|
|
@@ -157,11 +261,28 @@
|
|
|
157
261
|
center: tilejson.center?.slice(0, 2) ?? [0, 0],
|
|
158
262
|
zoom: tilejson.center?.[2] ?? 2,
|
|
159
263
|
maxZoom: (tilejson.maxzoom ?? 14) + 2,
|
|
264
|
+
// 60 is the default and it is not enough to see relief: the whole
|
|
265
|
+
// point of the terrain view is looking across a landscape rather than
|
|
266
|
+
// down at it.
|
|
267
|
+
...(terrain ? { maxPitch: 85 } : {}),
|
|
160
268
|
hash: true,
|
|
161
269
|
});
|
|
162
|
-
map.addControl(
|
|
270
|
+
map.addControl(
|
|
271
|
+
new maplibregl.NavigationControl({ visualizePitch: terrain }),
|
|
272
|
+
'top-left',
|
|
273
|
+
);
|
|
163
274
|
map.addControl(new maplibregl.ScaleControl(), 'bottom-left');
|
|
164
275
|
|
|
276
|
+
if (terrain) {
|
|
277
|
+
// Terrain off is a useful view of a DEM in its own right -- hillshade
|
|
278
|
+
// flat on the screen is easier to compare against a map than a
|
|
279
|
+
// perspective is -- so this stays a switch rather than being set once.
|
|
280
|
+
map.addControl(
|
|
281
|
+
new maplibregl.TerrainControl({ source: 'terrain' }),
|
|
282
|
+
'top-left',
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
|
|
165
286
|
if (vector) {
|
|
166
287
|
// MapLibre's own inspect control, rather than something equivalent
|
|
167
288
|
// written here: it is maintained alongside the renderer, so it keeps
|