tileserver-gl-light 5.0.0 → 5.1.0-pre.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/src/server.js CHANGED
@@ -1,9 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
3
 
4
- import os from 'os';
5
- process.env.UV_THREADPOOL_SIZE = Math.ceil(Math.max(4, os.cpus().length * 1.5));
6
-
7
4
  import fs from 'node:fs';
8
5
  import path from 'path';
9
6
  import fnv1a from '@sindresorhus/fnv1a';
@@ -19,7 +16,12 @@ import morgan from 'morgan';
19
16
  import { serve_data } from './serve_data.js';
20
17
  import { serve_style } from './serve_style.js';
21
18
  import { serve_font } from './serve_font.js';
22
- import { getTileUrls, getPublicUrl, isValidHttpUrl } from './utils.js';
19
+ import {
20
+ allowedTileSizes,
21
+ getTileUrls,
22
+ getPublicUrl,
23
+ isValidHttpUrl,
24
+ } from './utils.js';
23
25
 
24
26
  import { fileURLToPath } from 'url';
25
27
  const __filename = fileURLToPath(import.meta.url);
@@ -34,10 +36,11 @@ const serve_rendered = (
34
36
  ).serve_rendered;
35
37
 
36
38
  /**
37
- *
38
- * @param opts
39
+ * Starts the server.
40
+ * @param {object} opts - Configuration options for the server.
41
+ * @returns {Promise<object>} - A promise that resolves to the server object.
39
42
  */
40
- function start(opts) {
43
+ async function start(opts) {
41
44
  console.log('Starting server');
42
45
 
43
46
  const app = express().disable('x-powered-by');
@@ -73,7 +76,7 @@ function start(opts) {
73
76
  config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
74
77
  } catch (e) {
75
78
  console.log('ERROR: Config file not found or invalid!');
76
- console.log(' See README.md for instructions and sample data.');
79
+ console.log(' See README.md for instructions and sample data.');
77
80
  process.exit(1);
78
81
  }
79
82
  }
@@ -116,8 +119,9 @@ function start(opts) {
116
119
  * Recursively get all files within a directory.
117
120
  * Inspired by https://stackoverflow.com/a/45130990/10133863
118
121
  * @param {string} directory Absolute path to a directory to get files from.
122
+ * @returns {Promise<string[]>} - A promise that resolves to an array of file paths relative to the icon directory.
119
123
  */
120
- const getFiles = async (directory) => {
124
+ async function getFiles(directory) {
121
125
  // Fetch all entries of the directory and attach type information
122
126
  const dirEntries = await fs.promises.readdir(directory, {
123
127
  withFileTypes: true,
@@ -136,7 +140,7 @@ function start(opts) {
136
140
 
137
141
  // Flatten the list of files to a single array
138
142
  return files.flat();
139
- };
143
+ }
140
144
 
141
145
  // Load all available icons into a settings object
142
146
  startupPromises.push(
@@ -159,18 +163,25 @@ function start(opts) {
159
163
  app.use(cors());
160
164
  }
161
165
 
162
- app.use('/data/', serve_data.init(options, serving.data));
166
+ app.use('/data/', serve_data.init(options, serving.data, opts));
163
167
  app.use('/files/', express.static(paths.files));
164
- app.use('/styles/', serve_style.init(options, serving.styles));
168
+ app.use('/styles/', serve_style.init(options, serving.styles, opts));
165
169
  if (!isLight) {
166
170
  startupPromises.push(
167
- serve_rendered.init(options, serving.rendered).then((sub) => {
171
+ serve_rendered.init(options, serving.rendered, opts).then((sub) => {
168
172
  app.use('/styles/', sub);
169
173
  }),
170
174
  );
171
175
  }
172
-
173
- const addStyle = (id, item, allowMoreData, reportFonts) => {
176
+ /**
177
+ * Adds a style to the server.
178
+ * @param {string} id - The ID of the style.
179
+ * @param {object} item - The style configuration object.
180
+ * @param {boolean} allowMoreData - Whether to allow adding more data sources.
181
+ * @param {boolean} reportFonts - Whether to report fonts.
182
+ * @returns {void}
183
+ */
184
+ function addStyle(id, item, allowMoreData, reportFonts) {
174
185
  let success = true;
175
186
  if (item.serve_data !== false) {
176
187
  success = serve_style.add(
@@ -178,7 +189,7 @@ function start(opts) {
178
189
  serving.styles,
179
190
  item,
180
191
  id,
181
- opts.publicUrl,
192
+ opts,
182
193
  (styleSourceId, protocol) => {
183
194
  let dataItemId;
184
195
  for (const id of Object.keys(data)) {
@@ -235,7 +246,7 @@ function start(opts) {
235
246
  serving.rendered,
236
247
  item,
237
248
  id,
238
- opts.publicUrl,
249
+ opts,
239
250
  function dataResolver(styleSourceId) {
240
251
  let fileType;
241
252
  let inputFile;
@@ -261,7 +272,7 @@ function start(opts) {
261
272
  item.serve_rendered = false;
262
273
  }
263
274
  }
264
- };
275
+ }
265
276
 
266
277
  for (const id of Object.keys(config.styles || {})) {
267
278
  const item = config.styles[id];
@@ -272,13 +283,11 @@ function start(opts) {
272
283
 
273
284
  addStyle(id, item, true, true);
274
285
  }
275
-
276
286
  startupPromises.push(
277
- serve_font(options, serving.fonts).then((sub) => {
287
+ serve_font(options, serving.fonts, opts).then((sub) => {
278
288
  app.use('/', sub);
279
289
  }),
280
290
  );
281
-
282
291
  for (const id of Object.keys(data)) {
283
292
  const item = data[id];
284
293
  const fileType = Object.keys(data[id])[0];
@@ -288,12 +297,8 @@ function start(opts) {
288
297
  );
289
298
  continue;
290
299
  }
291
-
292
- startupPromises.push(
293
- serve_data.add(options, serving.data, item, id, opts.publicUrl),
294
- );
300
+ startupPromises.push(serve_data.add(options, serving.data, item, id, opts));
295
301
  }
296
-
297
302
  if (options.serveAllStyles) {
298
303
  fs.readdir(options.paths.styles, { withFileTypes: true }, (err, files) => {
299
304
  if (err) {
@@ -333,7 +338,13 @@ function start(opts) {
333
338
  }
334
339
  });
335
340
  }
336
-
341
+ /**
342
+ * Handles requests for a list of available styles.
343
+ * @param {object} req - Express request object.
344
+ * @param {object} res - Express response object.
345
+ * @param {string} [req.query.key] - Optional API key.
346
+ * @returns {void}
347
+ */
337
348
  app.get('/styles.json', (req, res, next) => {
338
349
  const result = [];
339
350
  const query = req.query.key
@@ -354,7 +365,15 @@ function start(opts) {
354
365
  res.send(result);
355
366
  });
356
367
 
357
- const addTileJSONs = (arr, req, type, tileSize) => {
368
+ /**
369
+ * Adds TileJSON metadata to an array.
370
+ * @param {Array} arr - The array to add TileJSONs to
371
+ * @param {object} req - The express request object.
372
+ * @param {string} type - The type of resource
373
+ * @param {number} tileSize - The tile size.
374
+ * @returns {Array} - An array of TileJSON objects.
375
+ */
376
+ function addTileJSONs(arr, req, type, tileSize) {
358
377
  for (const id of Object.keys(serving[type])) {
359
378
  const info = clone(serving[type][id].tileJSON);
360
379
  let path = '';
@@ -377,20 +396,42 @@ function start(opts) {
377
396
  arr.push(info);
378
397
  }
379
398
  return arr;
380
- };
399
+ }
381
400
 
382
- app.get('/(:tileSize(256|512)/)?rendered.json', (req, res, next) => {
383
- const tileSize = parseInt(req.params.tileSize, 10) || undefined;
384
- res.send(addTileJSONs([], req, 'rendered', tileSize));
401
+ /**
402
+ * Handles requests for a rendered tilejson endpoint.
403
+ * @param {object} req - Express request object.
404
+ * @param {object} res - Express response object.
405
+ * @param {string} req.params.tileSize - Optional tile size parameter.
406
+ * @returns {void}
407
+ */
408
+ app.get('{/:tileSize}/rendered.json', (req, res, next) => {
409
+ const tileSize = allowedTileSizes(req.params['tileSize']);
410
+ res.send(addTileJSONs([], req, 'rendered', parseInt(tileSize, 10)));
385
411
  });
386
- app.get('/data.json', (req, res, next) => {
412
+
413
+ /**
414
+ * Handles requests for a data tilejson endpoint.
415
+ * @param {object} req - Express request object.
416
+ * @param {object} res - Express response object.
417
+ * @returns {void}
418
+ */
419
+ app.get('/data.json', (req, res) => {
387
420
  res.send(addTileJSONs([], req, 'data', undefined));
388
421
  });
389
- app.get('/(:tileSize(256|512)/)?index.json', (req, res, next) => {
390
- const tileSize = parseInt(req.params.tileSize, 10) || undefined;
422
+
423
+ /**
424
+ * Handles requests for a combined rendered and data tilejson endpoint.
425
+ * @param {object} req - Express request object.
426
+ * @param {object} res - Express response object.
427
+ * @param {string} req.params.tileSize - Optional tile size parameter.
428
+ * @returns {void}
429
+ */
430
+ app.get('{/:tileSize}/index.json', (req, res, next) => {
431
+ const tileSize = allowedTileSizes(req.params['tileSize']);
391
432
  res.send(
392
433
  addTileJSONs(
393
- addTileJSONs([], req, 'rendered', tileSize),
434
+ addTileJSONs([], req, 'rendered', parseInt(tileSize, 10)),
394
435
  req,
395
436
  'data',
396
437
  undefined,
@@ -403,7 +444,15 @@ function start(opts) {
403
444
  app.use('/', express.static(path.join(__dirname, '../public/resources')));
404
445
 
405
446
  const templates = path.join(__dirname, '../public/templates');
406
- const serveTemplate = (urlPath, template, dataGetter) => {
447
+
448
+ /**
449
+ * Serves a Handlebars template.
450
+ * @param {string} urlPath - The URL path to serve the template at
451
+ * @param {string} template - The name of the template file
452
+ * @param {Function} dataGetter - A function to get data to be passed to the template.
453
+ * @returns {void}
454
+ */
455
+ function serveTemplate(urlPath, template, dataGetter) {
407
456
  let templateFile = `${templates}/${template}.tmpl`;
408
457
  if (template === 'index') {
409
458
  if (options.frontPage === false) {
@@ -415,24 +464,17 @@ function start(opts) {
415
464
  templateFile = path.resolve(paths.root, options.frontPage);
416
465
  }
417
466
  }
418
- startupPromises.push(
419
- new Promise((resolve, reject) => {
420
- fs.readFile(templateFile, (err, content) => {
421
- if (err) {
422
- err = new Error(`Template not found: ${err.message}`);
423
- reject(err);
424
- return;
425
- }
426
- const compiled = handlebars.compile(content.toString());
427
-
428
- app.use(urlPath, (req, res, next) => {
429
- let data = {};
430
- if (dataGetter) {
431
- data = dataGetter(req);
432
- if (!data) {
433
- return res.status(404).send('Not found');
434
- }
435
- }
467
+ try {
468
+ const content = fs.readFileSync(templateFile, 'utf-8');
469
+ const compiled = handlebars.compile(content.toString());
470
+ app.get(urlPath, (req, res, next) => {
471
+ if (opts.verbose) {
472
+ console.log(`Serving template at path: ${urlPath}`);
473
+ }
474
+ let data = {};
475
+ if (dataGetter) {
476
+ data = dataGetter(req);
477
+ if (data) {
436
478
  data['server_version'] =
437
479
  `${packageJson.name} v${packageJson.version}`;
438
480
  data['public_url'] = opts.publicUrl || '/';
@@ -445,14 +487,27 @@ function start(opts) {
445
487
  : '';
446
488
  if (template === 'wmts') res.set('Content-Type', 'text/xml');
447
489
  return res.status(200).send(compiled(data));
448
- });
449
- resolve();
450
- });
451
- }),
452
- );
453
- };
490
+ } else {
491
+ if (opts.verbose) {
492
+ console.log(`Forwarding request for: ${urlPath} to next route`);
493
+ }
494
+ next('route');
495
+ }
496
+ }
497
+ });
498
+ } catch (err) {
499
+ console.error(`Error reading template file: ${templateFile}`, err);
500
+ throw new Error(`Template not found: ${err.message}`); //throw an error so that the server doesnt start
501
+ }
502
+ }
454
503
 
455
- serveTemplate('/$', 'index', (req) => {
504
+ /**
505
+ * Handles requests for the index page, providing a list of available styles and data.
506
+ * @param {object} req - Express request object.
507
+ * @param {object} res - Express response object.
508
+ * @returns {void}
509
+ */
510
+ serveTemplate('/', 'index', (req) => {
456
511
  let styles = {};
457
512
  for (const id of Object.keys(serving.styles || {})) {
458
513
  let style = {
@@ -464,11 +519,15 @@ function start(opts) {
464
519
  if (style.serving_rendered) {
465
520
  const { center } = style.serving_rendered.tileJSON;
466
521
  if (center) {
467
- style.viewer_hash = `#${center[2]}/${center[1].toFixed(5)}/${center[0].toFixed(5)}`;
522
+ style.viewer_hash = `#${center[2]}/${center[1].toFixed(
523
+ 5,
524
+ )}/${center[0].toFixed(5)}`;
468
525
 
469
526
  const centerPx = mercator.px([center[0], center[1]], center[2]);
470
527
  // Set thumbnail default size to be 256px x 256px
471
- style.thumbnail = `${center[2]}/${Math.floor(centerPx[0] / 256)}/${Math.floor(centerPx[1] / 256)}.png`;
528
+ style.thumbnail = `${Math.floor(center[2])}/${Math.floor(
529
+ centerPx[0] / 256,
530
+ )}/${Math.floor(centerPx[1] / 256)}.png`;
472
531
  }
473
532
 
474
533
  const tileSize = 512;
@@ -484,7 +543,6 @@ function start(opts) {
484
543
 
485
544
  styles[id] = style;
486
545
  }
487
-
488
546
  let datas = {};
489
547
  for (const id of Object.keys(serving.data || {})) {
490
548
  let data = Object.assign({}, serving.data[id]);
@@ -498,14 +556,6 @@ function start(opts) {
498
556
  )}/${center[0].toFixed(5)}`;
499
557
  }
500
558
 
501
- data.is_vector = tileJSON.format === 'pbf';
502
- if (!data.is_vector) {
503
- if (center) {
504
- const centerPx = mercator.px([center[0], center[1]], center[2]);
505
- data.thumbnail = `${center[2]}/${Math.floor(centerPx[0] / 256)}/${Math.floor(centerPx[1] / 256)}.${tileJSON.format}`;
506
- }
507
- }
508
-
509
559
  const tileSize = undefined;
510
560
  data.xyz_link = getTileUrls(
511
561
  req,
@@ -519,6 +569,26 @@ function start(opts) {
519
569
  },
520
570
  )[0];
521
571
 
572
+ data.is_vector = tileJSON.format === 'pbf';
573
+ if (!data.is_vector) {
574
+ if (
575
+ tileJSON.encoding === 'terrarium' ||
576
+ tileJSON.encoding === 'mapbox'
577
+ ) {
578
+ data.elevation_link = getTileUrls(
579
+ req,
580
+ tileJSON.tiles,
581
+ `data/${id}/elevation`,
582
+ )[0];
583
+ }
584
+ if (center) {
585
+ const centerPx = mercator.px([center[0], center[1]], center[2]);
586
+ data.thumbnail = `${Math.floor(center[2])}/${Math.floor(
587
+ centerPx[0] / 256,
588
+ )}/${Math.floor(centerPx[1] / 256)}.${tileJSON.format}`;
589
+ }
590
+ }
591
+
522
592
  if (data.filesize) {
523
593
  let suffix = 'kB';
524
594
  let size = parseInt(tileJSON.filesize, 10) / 1024;
@@ -532,24 +602,28 @@ function start(opts) {
532
602
  }
533
603
  data.formatted_filesize = `${size.toFixed(2)} ${suffix}`;
534
604
  }
535
-
536
605
  datas[id] = data;
537
606
  }
538
-
539
607
  return {
540
608
  styles: Object.keys(styles).length ? styles : null,
541
609
  data: Object.keys(datas).length ? datas : null,
542
610
  };
543
611
  });
544
612
 
545
- serveTemplate('/styles/:id/$', 'viewer', (req) => {
613
+ /**
614
+ * Handles requests for a map viewer template for a specific style.
615
+ * @param {object} req - Express request object.
616
+ * @param {object} res - Express response object.
617
+ * @param {string} req.params.id - ID of the style.
618
+ * @returns {void}
619
+ */
620
+ serveTemplate('/styles/:id/', 'viewer', (req) => {
546
621
  const { id } = req.params;
547
622
  const style = clone(((serving.styles || {})[id] || {}).styleJSON);
548
623
 
549
624
  if (!style) {
550
625
  return null;
551
626
  }
552
-
553
627
  return {
554
628
  ...style,
555
629
  id,
@@ -559,11 +633,13 @@ function start(opts) {
559
633
  };
560
634
  });
561
635
 
562
- /*
563
- app.use('/rendered/:id/$', function(req, res, next) {
564
- return res.redirect(301, '/styles/' + req.params.id + '/');
565
- });
566
- */
636
+ /**
637
+ * Handles requests for a Web Map Tile Service (WMTS) XML template.
638
+ * @param {object} req - Express request object.
639
+ * @param {object} res - Express response object.
640
+ * @param {string} req.params.id - ID of the style.
641
+ * @returns {void}
642
+ */
567
643
  serveTemplate('/styles/:id/wmts.xml', 'wmts', (req) => {
568
644
  const { id } = req.params;
569
645
  const wmts = clone((serving.styles || {})[id]);
@@ -595,18 +671,33 @@ function start(opts) {
595
671
  };
596
672
  });
597
673
 
598
- serveTemplate('/data/:id/$', 'data', (req) => {
599
- const { id } = req.params;
674
+ /**
675
+ * Handles requests for a data view template for a specific data source.
676
+ * @param {object} req - Express request object.
677
+ * @param {object} res - Express response object.
678
+ * @param {string} req.params.id - ID of the data source.
679
+ * @param {string} [req.params.view] - Optional view type.
680
+ * @returns {void}
681
+ */
682
+ serveTemplate('/data{/:view}/:id/', 'data', (req) => {
683
+ const { id, view } = req.params;
600
684
  const data = serving.data[id];
601
685
 
602
686
  if (!data) {
603
687
  return null;
604
688
  }
689
+ const is_terrain =
690
+ (data.tileJSON.encoding === 'terrarium' ||
691
+ data.tileJSON.encoding === 'mapbox') &&
692
+ view === 'preview';
605
693
 
606
694
  return {
607
695
  ...data,
608
696
  id,
609
- is_vector: data.tileJSON.format === 'pbf',
697
+ use_maplibre: data.tileJSON.format === 'pbf' || is_terrain,
698
+ is_terrain: is_terrain,
699
+ is_terrainrgb: data.tileJSON.encoding === 'mapbox',
700
+ terrain_encoding: data.tileJSON.encoding,
610
701
  };
611
702
  });
612
703
 
@@ -616,7 +707,13 @@ function start(opts) {
616
707
  startupComplete = true;
617
708
  });
618
709
 
619
- app.get('/health', (req, res, next) => {
710
+ /**
711
+ * Handles requests to see the health of the server.
712
+ * @param {object} req - Express request object.
713
+ * @param {object} res - Express response object.
714
+ * @returns {void}
715
+ */
716
+ app.get('/health', (req, res) => {
620
717
  if (startupComplete) {
621
718
  return res.status(200).send('OK');
622
719
  } else {
@@ -645,10 +742,10 @@ function start(opts) {
645
742
  startupPromise,
646
743
  };
647
744
  }
648
-
649
745
  /**
650
746
  * Stop the server gracefully
651
747
  * @param {string} signal Name of the received signal
748
+ * @returns {void}
652
749
  */
653
750
  function stopGracefully(signal) {
654
751
  console.log(`Caught signal ${signal}, stopping gracefully`);
@@ -656,11 +753,12 @@ function stopGracefully(signal) {
656
753
  }
657
754
 
658
755
  /**
659
- *
660
- * @param opts
756
+ * Starts and manages the server
757
+ * @param {object} opts - Configuration options for the server.
758
+ * @returns {Promise<object>} - A promise that resolves to the running server
661
759
  */
662
- export function server(opts) {
663
- const running = start(opts);
760
+ export async function server(opts) {
761
+ const running = await start(opts);
664
762
 
665
763
  running.startupPromise.catch((err) => {
666
764
  console.error(err.message);
@@ -680,6 +778,5 @@ export function server(opts) {
680
778
  running.app = restarted.app;
681
779
  });
682
780
  });
683
-
684
781
  return running;
685
782
  }