tileserver-gl-light 4.5.2 → 4.6.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/LICENSE.md +32 -0
- package/docs/config.rst +52 -10
- package/package.json +3 -1
- package/public/templates/index.tmpl +3 -2
- package/src/main.js +166 -89
- package/src/pmtiles_adapter.js +151 -0
- package/src/serve_data.js +188 -88
- package/src/serve_rendered.js +203 -98
- package/src/serve_style.js +17 -13
- package/src/server.js +114 -62
- package/src/utils.js +12 -1
package/src/serve_data.js
CHANGED
|
@@ -10,7 +10,12 @@ import MBTiles from '@mapbox/mbtiles';
|
|
|
10
10
|
import Pbf from 'pbf';
|
|
11
11
|
import { VectorTile } from '@mapbox/vector-tile';
|
|
12
12
|
|
|
13
|
-
import { getTileUrls, fixTileJSONCenter } from './utils.js';
|
|
13
|
+
import { getTileUrls, isValidHttpUrl, fixTileJSONCenter } from './utils.js';
|
|
14
|
+
import {
|
|
15
|
+
PMtilesOpen,
|
|
16
|
+
GetPMtilesInfo,
|
|
17
|
+
GetPMtilesTile,
|
|
18
|
+
} from './pmtiles_adapter.js';
|
|
14
19
|
|
|
15
20
|
export const serve_data = {
|
|
16
21
|
init: (options, repo) => {
|
|
@@ -18,7 +23,7 @@ export const serve_data = {
|
|
|
18
23
|
|
|
19
24
|
app.get(
|
|
20
25
|
'/:id/:z(\\d+)/:x(\\d+)/:y(\\d+).:format([\\w.]+)',
|
|
21
|
-
(req, res, next) => {
|
|
26
|
+
async (req, res, next) => {
|
|
22
27
|
const item = repo[req.params.id];
|
|
23
28
|
if (!item) {
|
|
24
29
|
return res.sendStatus(404);
|
|
@@ -48,71 +53,118 @@ export const serve_data = {
|
|
|
48
53
|
) {
|
|
49
54
|
return res.status(404).send('Out of bounds');
|
|
50
55
|
}
|
|
51
|
-
item.
|
|
52
|
-
let
|
|
53
|
-
if (
|
|
54
|
-
|
|
55
|
-
return res.status(204).send();
|
|
56
|
-
} else {
|
|
57
|
-
return res
|
|
58
|
-
.status(500)
|
|
59
|
-
.header('Content-Type', 'text/plain')
|
|
60
|
-
.send(err.message);
|
|
61
|
-
}
|
|
56
|
+
if (item.source_type === 'pmtiles') {
|
|
57
|
+
let tileinfo = await GetPMtilesTile(item.source, z, x, y);
|
|
58
|
+
if (tileinfo == undefined || tileinfo.data == undefined) {
|
|
59
|
+
return res.status(404).send('Not found');
|
|
62
60
|
} else {
|
|
63
|
-
|
|
64
|
-
|
|
61
|
+
let data = tileinfo.data;
|
|
62
|
+
let headers = tileinfo.header;
|
|
63
|
+
if (tileJSONFormat === 'pbf') {
|
|
64
|
+
if (options.dataDecoratorFunc) {
|
|
65
|
+
data = options.dataDecoratorFunc(id, 'data', data, z, x, y);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (format === 'pbf') {
|
|
69
|
+
headers['Content-Type'] = 'application/x-protobuf';
|
|
70
|
+
} else if (format === 'geojson') {
|
|
71
|
+
headers['Content-Type'] = 'application/json';
|
|
72
|
+
|
|
73
|
+
if (isGzipped) {
|
|
74
|
+
data = zlib.unzipSync(data);
|
|
75
|
+
isGzipped = false;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const tile = new VectorTile(new Pbf(data));
|
|
79
|
+
const geojson = {
|
|
80
|
+
type: 'FeatureCollection',
|
|
81
|
+
features: [],
|
|
82
|
+
};
|
|
83
|
+
for (const layerName in tile.layers) {
|
|
84
|
+
const layer = tile.layers[layerName];
|
|
85
|
+
for (let i = 0; i < layer.length; i++) {
|
|
86
|
+
const feature = layer.feature(i);
|
|
87
|
+
const featureGeoJSON = feature.toGeoJSON(x, y, z);
|
|
88
|
+
featureGeoJSON.properties.layer = layerName;
|
|
89
|
+
geojson.features.push(featureGeoJSON);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
data = JSON.stringify(geojson);
|
|
93
|
+
}
|
|
94
|
+
delete headers['ETag']; // do not trust the tile ETag -- regenerate
|
|
95
|
+
headers['Content-Encoding'] = 'gzip';
|
|
96
|
+
res.set(headers);
|
|
97
|
+
|
|
98
|
+
data = zlib.gzipSync(data);
|
|
99
|
+
|
|
100
|
+
return res.status(200).send(data);
|
|
101
|
+
}
|
|
102
|
+
} else if (item.source_type === 'mbtiles') {
|
|
103
|
+
item.source.getTile(z, x, y, (err, data, headers) => {
|
|
104
|
+
let isGzipped;
|
|
105
|
+
if (err) {
|
|
106
|
+
if (/does not exist/.test(err.message)) {
|
|
107
|
+
return res.status(204).send();
|
|
108
|
+
} else {
|
|
109
|
+
return res
|
|
110
|
+
.status(500)
|
|
111
|
+
.header('Content-Type', 'text/plain')
|
|
112
|
+
.send(err.message);
|
|
113
|
+
}
|
|
65
114
|
} else {
|
|
66
|
-
if (
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
if (
|
|
115
|
+
if (data == null) {
|
|
116
|
+
return res.status(404).send('Not found');
|
|
117
|
+
} else {
|
|
118
|
+
if (tileJSONFormat === 'pbf') {
|
|
119
|
+
isGzipped =
|
|
120
|
+
data.slice(0, 2).indexOf(Buffer.from([0x1f, 0x8b])) === 0;
|
|
121
|
+
if (options.dataDecoratorFunc) {
|
|
122
|
+
if (isGzipped) {
|
|
123
|
+
data = zlib.unzipSync(data);
|
|
124
|
+
isGzipped = false;
|
|
125
|
+
}
|
|
126
|
+
data = options.dataDecoratorFunc(id, 'data', data, z, x, y);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
if (format === 'pbf') {
|
|
130
|
+
headers['Content-Type'] = 'application/x-protobuf';
|
|
131
|
+
} else if (format === 'geojson') {
|
|
132
|
+
headers['Content-Type'] = 'application/json';
|
|
133
|
+
|
|
70
134
|
if (isGzipped) {
|
|
71
135
|
data = zlib.unzipSync(data);
|
|
72
136
|
isGzipped = false;
|
|
73
137
|
}
|
|
74
|
-
data = options.dataDecoratorFunc(id, 'data', data, z, x, y);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
if (format === 'pbf') {
|
|
78
|
-
headers['Content-Type'] = 'application/x-protobuf';
|
|
79
|
-
} else if (format === 'geojson') {
|
|
80
|
-
headers['Content-Type'] = 'application/json';
|
|
81
|
-
|
|
82
|
-
if (isGzipped) {
|
|
83
|
-
data = zlib.unzipSync(data);
|
|
84
|
-
isGzipped = false;
|
|
85
|
-
}
|
|
86
138
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
139
|
+
const tile = new VectorTile(new Pbf(data));
|
|
140
|
+
const geojson = {
|
|
141
|
+
type: 'FeatureCollection',
|
|
142
|
+
features: [],
|
|
143
|
+
};
|
|
144
|
+
for (const layerName in tile.layers) {
|
|
145
|
+
const layer = tile.layers[layerName];
|
|
146
|
+
for (let i = 0; i < layer.length; i++) {
|
|
147
|
+
const feature = layer.feature(i);
|
|
148
|
+
const featureGeoJSON = feature.toGeoJSON(x, y, z);
|
|
149
|
+
featureGeoJSON.properties.layer = layerName;
|
|
150
|
+
geojson.features.push(featureGeoJSON);
|
|
151
|
+
}
|
|
99
152
|
}
|
|
153
|
+
data = JSON.stringify(geojson);
|
|
100
154
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
headers['Content-Encoding'] = 'gzip';
|
|
105
|
-
res.set(headers);
|
|
155
|
+
delete headers['ETag']; // do not trust the tile ETag -- regenerate
|
|
156
|
+
headers['Content-Encoding'] = 'gzip';
|
|
157
|
+
res.set(headers);
|
|
106
158
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
}
|
|
159
|
+
if (!isGzipped) {
|
|
160
|
+
data = zlib.gzipSync(data);
|
|
161
|
+
}
|
|
111
162
|
|
|
112
|
-
|
|
163
|
+
return res.status(200).send(data);
|
|
164
|
+
}
|
|
113
165
|
}
|
|
114
|
-
}
|
|
115
|
-
}
|
|
166
|
+
});
|
|
167
|
+
}
|
|
116
168
|
},
|
|
117
169
|
);
|
|
118
170
|
|
|
@@ -137,55 +189,103 @@ export const serve_data = {
|
|
|
137
189
|
|
|
138
190
|
return app;
|
|
139
191
|
},
|
|
140
|
-
add: (options, repo, params, id, publicUrl) => {
|
|
141
|
-
|
|
192
|
+
add: async (options, repo, params, id, publicUrl) => {
|
|
193
|
+
let inputFile;
|
|
194
|
+
let inputType;
|
|
195
|
+
if (params.pmtiles) {
|
|
196
|
+
inputType = 'pmtiles';
|
|
197
|
+
if (isValidHttpUrl(params.pmtiles)) {
|
|
198
|
+
inputFile = params.pmtiles;
|
|
199
|
+
} else {
|
|
200
|
+
inputFile = path.resolve(options.paths.pmtiles, params.pmtiles);
|
|
201
|
+
}
|
|
202
|
+
} else if (params.mbtiles) {
|
|
203
|
+
inputType = 'mbtiles';
|
|
204
|
+
if (isValidHttpUrl(params.mbtiles)) {
|
|
205
|
+
console.log(
|
|
206
|
+
`ERROR: MBTiles does not support web based files. "${params.mbtiles}" is not a valid data file.`,
|
|
207
|
+
);
|
|
208
|
+
process.exit(1);
|
|
209
|
+
} else {
|
|
210
|
+
inputFile = path.resolve(options.paths.mbtiles, params.mbtiles);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
142
214
|
let tileJSON = {
|
|
143
215
|
tiles: params.domains || options.domains,
|
|
144
216
|
};
|
|
145
217
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
218
|
+
if (!isValidHttpUrl(inputFile)) {
|
|
219
|
+
const inputFileStats = fs.statSync(inputFile);
|
|
220
|
+
if (!inputFileStats.isFile() || inputFileStats.size === 0) {
|
|
221
|
+
throw Error(`Not valid input file: "${inputFile}"`);
|
|
222
|
+
}
|
|
149
223
|
}
|
|
224
|
+
|
|
150
225
|
let source;
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
226
|
+
let source_type;
|
|
227
|
+
if (inputType === 'pmtiles') {
|
|
228
|
+
source = PMtilesOpen(inputFile);
|
|
229
|
+
source_type = 'pmtiles';
|
|
230
|
+
const metadata = await GetPMtilesInfo(source);
|
|
231
|
+
|
|
232
|
+
tileJSON['name'] = id;
|
|
233
|
+
tileJSON['format'] = 'pbf';
|
|
234
|
+
Object.assign(tileJSON, metadata);
|
|
235
|
+
|
|
236
|
+
tileJSON['tilejson'] = '2.0.0';
|
|
237
|
+
delete tileJSON['filesize'];
|
|
238
|
+
delete tileJSON['mtime'];
|
|
239
|
+
delete tileJSON['scheme'];
|
|
240
|
+
|
|
241
|
+
Object.assign(tileJSON, params.tilejson || {});
|
|
242
|
+
fixTileJSONCenter(tileJSON);
|
|
243
|
+
|
|
244
|
+
if (options.dataDecoratorFunc) {
|
|
245
|
+
tileJSON = options.dataDecoratorFunc(id, 'tilejson', tileJSON);
|
|
246
|
+
}
|
|
247
|
+
} else if (inputType === 'mbtiles') {
|
|
248
|
+
source_type = 'mbtiles';
|
|
249
|
+
const sourceInfoPromise = new Promise((resolve, reject) => {
|
|
250
|
+
source = new MBTiles(inputFile + '?mode=ro', (err) => {
|
|
158
251
|
if (err) {
|
|
159
252
|
reject(err);
|
|
160
253
|
return;
|
|
161
254
|
}
|
|
162
|
-
|
|
163
|
-
|
|
255
|
+
source.getInfo((err, info) => {
|
|
256
|
+
if (err) {
|
|
257
|
+
reject(err);
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
tileJSON['name'] = id;
|
|
261
|
+
tileJSON['format'] = 'pbf';
|
|
164
262
|
|
|
165
|
-
|
|
263
|
+
Object.assign(tileJSON, info);
|
|
166
264
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
265
|
+
tileJSON['tilejson'] = '2.0.0';
|
|
266
|
+
delete tileJSON['filesize'];
|
|
267
|
+
delete tileJSON['mtime'];
|
|
268
|
+
delete tileJSON['scheme'];
|
|
171
269
|
|
|
172
|
-
|
|
173
|
-
|
|
270
|
+
Object.assign(tileJSON, params.tilejson || {});
|
|
271
|
+
fixTileJSONCenter(tileJSON);
|
|
174
272
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
273
|
+
if (options.dataDecoratorFunc) {
|
|
274
|
+
tileJSON = options.dataDecoratorFunc(id, 'tilejson', tileJSON);
|
|
275
|
+
}
|
|
276
|
+
resolve();
|
|
277
|
+
});
|
|
179
278
|
});
|
|
180
279
|
});
|
|
181
|
-
});
|
|
182
280
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
281
|
+
await sourceInfoPromise;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
repo[id] = {
|
|
285
|
+
tileJSON,
|
|
286
|
+
publicUrl,
|
|
287
|
+
source,
|
|
288
|
+
source_type,
|
|
289
|
+
};
|
|
190
290
|
},
|
|
191
291
|
};
|