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/server.js
CHANGED
|
@@ -6,7 +6,7 @@ process.env.UV_THREADPOOL_SIZE = Math.ceil(Math.max(4, os.cpus().length * 1.5));
|
|
|
6
6
|
|
|
7
7
|
import fs from 'node:fs';
|
|
8
8
|
import path from 'path';
|
|
9
|
-
|
|
9
|
+
import fnv1a from '@sindresorhus/fnv1a';
|
|
10
10
|
import chokidar from 'chokidar';
|
|
11
11
|
import clone from 'clone';
|
|
12
12
|
import cors from 'cors';
|
|
@@ -19,7 +19,7 @@ import morgan from 'morgan';
|
|
|
19
19
|
import { serve_data } from './serve_data.js';
|
|
20
20
|
import { serve_style } from './serve_style.js';
|
|
21
21
|
import { serve_font } from './serve_font.js';
|
|
22
|
-
import { getTileUrls, getPublicUrl } from './utils.js';
|
|
22
|
+
import { getTileUrls, getPublicUrl, isValidHttpUrl } from './utils.js';
|
|
23
23
|
|
|
24
24
|
import { fileURLToPath } from 'url';
|
|
25
25
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -93,6 +93,7 @@ function start(opts) {
|
|
|
93
93
|
paths.fonts = path.resolve(paths.root, paths.fonts || '');
|
|
94
94
|
paths.sprites = path.resolve(paths.root, paths.sprites || '');
|
|
95
95
|
paths.mbtiles = path.resolve(paths.root, paths.mbtiles || '');
|
|
96
|
+
paths.pmtiles = path.resolve(paths.root, paths.pmtiles || '');
|
|
96
97
|
paths.icons = path.resolve(paths.root, paths.icons || '');
|
|
97
98
|
|
|
98
99
|
const startupPromises = [];
|
|
@@ -109,6 +110,7 @@ function start(opts) {
|
|
|
109
110
|
checkPath('fonts');
|
|
110
111
|
checkPath('sprites');
|
|
111
112
|
checkPath('mbtiles');
|
|
113
|
+
checkPath('pmtiles');
|
|
112
114
|
checkPath('icons');
|
|
113
115
|
|
|
114
116
|
/**
|
|
@@ -181,34 +183,43 @@ function start(opts) {
|
|
|
181
183
|
item,
|
|
182
184
|
id,
|
|
183
185
|
opts.publicUrl,
|
|
184
|
-
(
|
|
186
|
+
(StyleSourceId, protocol) => {
|
|
185
187
|
let dataItemId;
|
|
186
188
|
for (const id of Object.keys(data)) {
|
|
187
|
-
if (
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
}
|
|
189
|
+
if (id === StyleSourceId) {
|
|
190
|
+
// Style id was found in data ids, return that id
|
|
191
|
+
dataItemId = id;
|
|
191
192
|
} else {
|
|
192
|
-
|
|
193
|
+
const fileType = Object.keys(data[id])[0];
|
|
194
|
+
if (data[id][fileType] === StyleSourceId) {
|
|
195
|
+
// Style id was found in data filename, return the id that filename belong to
|
|
193
196
|
dataItemId = id;
|
|
194
197
|
}
|
|
195
198
|
}
|
|
196
199
|
}
|
|
197
200
|
if (dataItemId) {
|
|
198
|
-
//
|
|
201
|
+
// input files exists in the data config, return found id
|
|
199
202
|
return dataItemId;
|
|
200
203
|
} else {
|
|
201
|
-
if (
|
|
204
|
+
if (!allowMoreData) {
|
|
202
205
|
console.log(
|
|
203
|
-
`ERROR: style "${item.style}" using unknown
|
|
206
|
+
`ERROR: style "${item.style}" using unknown file "${StyleSourceId}"! Skipping...`,
|
|
204
207
|
);
|
|
205
208
|
return undefined;
|
|
206
209
|
} else {
|
|
207
|
-
let id =
|
|
208
|
-
|
|
210
|
+
let id =
|
|
211
|
+
StyleSourceId.substr(0, StyleSourceId.lastIndexOf('.')) ||
|
|
212
|
+
StyleSourceId;
|
|
213
|
+
if (isValidHttpUrl(StyleSourceId)) {
|
|
214
|
+
id =
|
|
215
|
+
fnv1a(StyleSourceId) + '_' + id.replace(/^.*\/(.*)$/, '$1');
|
|
216
|
+
}
|
|
217
|
+
while (data[id]) id += '_'; //if the data source id already exists, add a "_" untill it doesn't
|
|
218
|
+
//Add the new data source to the data array.
|
|
209
219
|
data[id] = {
|
|
210
|
-
|
|
220
|
+
[protocol]: StyleSourceId,
|
|
211
221
|
};
|
|
222
|
+
|
|
212
223
|
return id;
|
|
213
224
|
}
|
|
214
225
|
}
|
|
@@ -229,14 +240,24 @@ function start(opts) {
|
|
|
229
240
|
item,
|
|
230
241
|
id,
|
|
231
242
|
opts.publicUrl,
|
|
232
|
-
(
|
|
233
|
-
let
|
|
243
|
+
(StyleSourceId) => {
|
|
244
|
+
let fileType;
|
|
245
|
+
let inputFile;
|
|
234
246
|
for (const id of Object.keys(data)) {
|
|
235
|
-
|
|
236
|
-
|
|
247
|
+
fileType = Object.keys(data[id])[0];
|
|
248
|
+
if (StyleSourceId == id) {
|
|
249
|
+
inputFile = data[id][fileType];
|
|
250
|
+
break;
|
|
251
|
+
} else if (data[id][fileType] == StyleSourceId) {
|
|
252
|
+
inputFile = data[id][fileType];
|
|
253
|
+
break;
|
|
237
254
|
}
|
|
238
255
|
}
|
|
239
|
-
|
|
256
|
+
if (!isValidHttpUrl(inputFile)) {
|
|
257
|
+
inputFile = path.resolve(options.paths[fileType], inputFile);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return { inputfile: inputFile, filetype: fileType };
|
|
240
261
|
},
|
|
241
262
|
),
|
|
242
263
|
);
|
|
@@ -264,8 +285,11 @@ function start(opts) {
|
|
|
264
285
|
|
|
265
286
|
for (const id of Object.keys(data)) {
|
|
266
287
|
const item = data[id];
|
|
267
|
-
|
|
268
|
-
|
|
288
|
+
const fileType = Object.keys(data[id])[0];
|
|
289
|
+
if (!fileType || !(fileType === 'pmtiles' || fileType === 'mbtiles')) {
|
|
290
|
+
console.log(
|
|
291
|
+
`Missing "pmtiles" or "mbtiles" property for ${id} data source`,
|
|
292
|
+
);
|
|
269
293
|
continue;
|
|
270
294
|
}
|
|
271
295
|
|
|
@@ -424,14 +448,16 @@ function start(opts) {
|
|
|
424
448
|
};
|
|
425
449
|
|
|
426
450
|
serveTemplate('/$', 'index', (req) => {
|
|
427
|
-
|
|
428
|
-
for (const id of Object.keys(styles)) {
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
451
|
+
let styles = {};
|
|
452
|
+
for (const id of Object.keys(serving.styles || {})) {
|
|
453
|
+
let style = {
|
|
454
|
+
...serving.styles[id],
|
|
455
|
+
serving_data: serving.styles[id],
|
|
456
|
+
serving_rendered: serving.rendered[id],
|
|
457
|
+
};
|
|
458
|
+
|
|
433
459
|
if (style.serving_rendered) {
|
|
434
|
-
const center = style.serving_rendered.tileJSON
|
|
460
|
+
const { center } = style.serving_rendered.tileJSON;
|
|
435
461
|
if (center) {
|
|
436
462
|
style.viewer_hash = `#${center[2]}/${center[1].toFixed(
|
|
437
463
|
5,
|
|
@@ -451,40 +477,46 @@ function start(opts) {
|
|
|
451
477
|
opts.publicUrl,
|
|
452
478
|
)[0];
|
|
453
479
|
}
|
|
480
|
+
|
|
481
|
+
styles[id] = style;
|
|
454
482
|
}
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
483
|
+
|
|
484
|
+
let datas = {};
|
|
485
|
+
for (const id of Object.keys(serving.data || {})) {
|
|
486
|
+
let data = Object.assign({}, serving.data[id]);
|
|
487
|
+
|
|
488
|
+
const { tileJSON } = serving.data[id];
|
|
489
|
+
const { center } = tileJSON;
|
|
490
|
+
|
|
460
491
|
if (center) {
|
|
461
|
-
|
|
492
|
+
data.viewer_hash = `#${center[2]}/${center[1].toFixed(
|
|
462
493
|
5,
|
|
463
494
|
)}/${center[0].toFixed(5)}`;
|
|
464
495
|
}
|
|
465
|
-
|
|
466
|
-
|
|
496
|
+
|
|
497
|
+
data.is_vector = tileJSON.format === 'pbf';
|
|
498
|
+
if (!data.is_vector) {
|
|
467
499
|
if (center) {
|
|
468
500
|
const centerPx = mercator.px([center[0], center[1]], center[2]);
|
|
469
|
-
|
|
501
|
+
data.thumbnail = `${center[2]}/${Math.floor(
|
|
470
502
|
centerPx[0] / 256,
|
|
471
|
-
)}/${Math.floor(centerPx[1] / 256)}.${
|
|
503
|
+
)}/${Math.floor(centerPx[1] / 256)}.${tileJSON.format}`;
|
|
472
504
|
}
|
|
473
505
|
|
|
474
|
-
|
|
506
|
+
data.xyz_link = getTileUrls(
|
|
475
507
|
req,
|
|
476
|
-
|
|
508
|
+
tileJSON.tiles,
|
|
477
509
|
`data/${id}`,
|
|
478
|
-
|
|
510
|
+
tileJSON.format,
|
|
479
511
|
opts.publicUrl,
|
|
480
512
|
{
|
|
481
513
|
pbf: options.pbfAlias,
|
|
482
514
|
},
|
|
483
515
|
)[0];
|
|
484
516
|
}
|
|
485
|
-
if (
|
|
517
|
+
if (data.filesize) {
|
|
486
518
|
let suffix = 'kB';
|
|
487
|
-
let size = parseInt(
|
|
519
|
+
let size = parseInt(tileJSON.filesize, 10) / 1024;
|
|
488
520
|
if (size > 1024) {
|
|
489
521
|
suffix = 'MB';
|
|
490
522
|
size /= 1024;
|
|
@@ -493,26 +525,33 @@ function start(opts) {
|
|
|
493
525
|
suffix = 'GB';
|
|
494
526
|
size /= 1024;
|
|
495
527
|
}
|
|
496
|
-
|
|
528
|
+
data.formatted_filesize = `${size.toFixed(2)} ${suffix}`;
|
|
497
529
|
}
|
|
530
|
+
|
|
531
|
+
datas[id] = data;
|
|
498
532
|
}
|
|
533
|
+
|
|
499
534
|
return {
|
|
500
535
|
styles: Object.keys(styles).length ? styles : null,
|
|
501
|
-
data: Object.keys(
|
|
536
|
+
data: Object.keys(datas).length ? datas : null,
|
|
502
537
|
};
|
|
503
538
|
});
|
|
504
539
|
|
|
505
540
|
serveTemplate('/styles/:id/$', 'viewer', (req) => {
|
|
506
|
-
const id = req.params
|
|
541
|
+
const { id } = req.params;
|
|
507
542
|
const style = clone(((serving.styles || {})[id] || {}).styleJSON);
|
|
543
|
+
|
|
508
544
|
if (!style) {
|
|
509
545
|
return null;
|
|
510
546
|
}
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
547
|
+
|
|
548
|
+
return {
|
|
549
|
+
id,
|
|
550
|
+
name: (serving.styles[id] || serving.rendered[id]).name,
|
|
551
|
+
serving_data: serving.styles[id],
|
|
552
|
+
serving_rendered: serving.rendered[id],
|
|
553
|
+
...style,
|
|
554
|
+
};
|
|
516
555
|
});
|
|
517
556
|
|
|
518
557
|
/*
|
|
@@ -521,37 +560,49 @@ function start(opts) {
|
|
|
521
560
|
});
|
|
522
561
|
*/
|
|
523
562
|
serveTemplate('/styles/:id/wmts.xml', 'wmts', (req) => {
|
|
524
|
-
const id = req.params
|
|
563
|
+
const { id } = req.params;
|
|
525
564
|
const wmts = clone((serving.styles || {})[id]);
|
|
565
|
+
|
|
526
566
|
if (!wmts) {
|
|
527
567
|
return null;
|
|
528
568
|
}
|
|
569
|
+
|
|
529
570
|
if (wmts.hasOwnProperty('serve_rendered') && !wmts.serve_rendered) {
|
|
530
571
|
return null;
|
|
531
572
|
}
|
|
532
|
-
|
|
533
|
-
|
|
573
|
+
|
|
574
|
+
let baseUrl;
|
|
534
575
|
if (opts.publicUrl) {
|
|
535
|
-
|
|
576
|
+
baseUrl = opts.publicUrl;
|
|
536
577
|
} else {
|
|
537
|
-
|
|
578
|
+
baseUrl = `${
|
|
538
579
|
req.get('X-Forwarded-Protocol')
|
|
539
580
|
? req.get('X-Forwarded-Protocol')
|
|
540
581
|
: req.protocol
|
|
541
582
|
}://${req.get('host')}/`;
|
|
542
583
|
}
|
|
543
|
-
|
|
584
|
+
|
|
585
|
+
return {
|
|
586
|
+
id,
|
|
587
|
+
name: (serving.styles[id] || serving.rendered[id]).name,
|
|
588
|
+
baseUrl,
|
|
589
|
+
...wmts,
|
|
590
|
+
};
|
|
544
591
|
});
|
|
545
592
|
|
|
546
593
|
serveTemplate('/data/:id/$', 'data', (req) => {
|
|
547
|
-
const id = req.params
|
|
548
|
-
const data =
|
|
594
|
+
const { id } = req.params;
|
|
595
|
+
const data = serving.data[id];
|
|
596
|
+
|
|
549
597
|
if (!data) {
|
|
550
598
|
return null;
|
|
551
599
|
}
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
600
|
+
|
|
601
|
+
return {
|
|
602
|
+
id,
|
|
603
|
+
is_vector: data.tileJSON.format === 'pbf',
|
|
604
|
+
...data,
|
|
605
|
+
};
|
|
555
606
|
});
|
|
556
607
|
|
|
557
608
|
let startupComplete = false;
|
|
@@ -559,6 +610,7 @@ function start(opts) {
|
|
|
559
610
|
console.log('Startup complete');
|
|
560
611
|
startupComplete = true;
|
|
561
612
|
});
|
|
613
|
+
|
|
562
614
|
app.get('/health', (req, res, next) => {
|
|
563
615
|
if (startupComplete) {
|
|
564
616
|
return res.status(200).send('OK');
|
package/src/utils.js
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import fs from 'node:fs';
|
|
5
|
-
|
|
6
5
|
import clone from 'clone';
|
|
7
6
|
import glyphCompose from '@mapbox/glyph-pbf-composite';
|
|
8
7
|
|
|
@@ -163,3 +162,15 @@ export const getFontsPbf = (
|
|
|
163
162
|
|
|
164
163
|
return Promise.all(queue).then((values) => glyphCompose.combine(values));
|
|
165
164
|
};
|
|
165
|
+
|
|
166
|
+
export const isValidHttpUrl = (string) => {
|
|
167
|
+
let url;
|
|
168
|
+
|
|
169
|
+
try {
|
|
170
|
+
url = new URL(string);
|
|
171
|
+
} catch (_) {
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return url.protocol === 'http:' || url.protocol === 'https:';
|
|
176
|
+
};
|