tileserver-gl-light 4.1.2 → 4.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/src/server.js CHANGED
@@ -2,8 +2,7 @@
2
2
  'use strict';
3
3
 
4
4
  import os from 'os';
5
- process.env.UV_THREADPOOL_SIZE =
6
- Math.ceil(Math.max(4, os.cpus().length * 1.5));
5
+ process.env.UV_THREADPOOL_SIZE = Math.ceil(Math.max(4, os.cpus().length * 1.5));
7
6
 
8
7
  import fs from 'node:fs';
9
8
  import path from 'path';
@@ -17,19 +16,27 @@ import handlebars from 'handlebars';
17
16
  import SphericalMercator from '@mapbox/sphericalmercator';
18
17
  const mercator = new SphericalMercator();
19
18
  import morgan from 'morgan';
20
- import {serve_data} from './serve_data.js';
21
- import {serve_style} from './serve_style.js';
22
- import {serve_font} from './serve_font.js';
23
- import {getTileUrls, getPublicUrl} from './utils.js';
19
+ import { serve_data } from './serve_data.js';
20
+ import { serve_style } from './serve_style.js';
21
+ import { serve_font } from './serve_font.js';
22
+ import { getTileUrls, getPublicUrl } from './utils.js';
24
23
 
25
- import {fileURLToPath} from 'url';
24
+ import { fileURLToPath } from 'url';
26
25
  const __filename = fileURLToPath(import.meta.url);
27
26
  const __dirname = path.dirname(__filename);
28
- const packageJson = JSON.parse(fs.readFileSync(__dirname + '/../package.json', 'utf8'));
27
+ const packageJson = JSON.parse(
28
+ fs.readFileSync(__dirname + '/../package.json', 'utf8'),
29
+ );
29
30
 
30
31
  const isLight = packageJson.name.slice(-6) === '-light';
31
- const serve_rendered = (await import(`${!isLight ? `./serve_rendered.js` : `./serve_light.js`}`)).serve_rendered;
32
+ const serve_rendered = (
33
+ await import(`${!isLight ? `./serve_rendered.js` : `./serve_light.js`}`)
34
+ ).serve_rendered;
32
35
 
36
+ /**
37
+ *
38
+ * @param opts
39
+ */
33
40
  function start(opts) {
34
41
  console.log('Starting server');
35
42
 
@@ -38,18 +45,24 @@ function start(opts) {
38
45
  styles: {},
39
46
  rendered: {},
40
47
  data: {},
41
- fonts: {}
48
+ fonts: {},
42
49
  };
43
50
 
44
51
  app.enable('trust proxy');
45
52
 
46
53
  if (process.env.NODE_ENV !== 'test') {
47
- const defaultLogFormat = process.env.NODE_ENV === 'production' ? 'tiny' : 'dev';
54
+ const defaultLogFormat =
55
+ process.env.NODE_ENV === 'production' ? 'tiny' : 'dev';
48
56
  const logFormat = opts.logFormat || defaultLogFormat;
49
- app.use(morgan(logFormat, {
50
- stream: opts.logFile ? fs.createWriteStream(opts.logFile, {flags: 'a'}) : process.stdout,
51
- skip: (req, res) => opts.silent && (res.statusCode === 200 || res.statusCode === 304)
52
- }));
57
+ app.use(
58
+ morgan(logFormat, {
59
+ stream: opts.logFile
60
+ ? fs.createWriteStream(opts.logFile, { flags: 'a' })
61
+ : process.stdout,
62
+ skip: (req, res) =>
63
+ opts.silent && (res.statusCode === 200 || res.statusCode === 304),
64
+ }),
65
+ );
53
66
  }
54
67
 
55
68
  let config = opts.config || null;
@@ -74,7 +87,8 @@ function start(opts) {
74
87
  options.paths = paths;
75
88
  paths.root = path.resolve(
76
89
  configPath ? path.dirname(configPath) : process.cwd(),
77
- paths.root || '');
90
+ paths.root || '',
91
+ );
78
92
  paths.styles = path.resolve(paths.root, paths.styles || '');
79
93
  paths.fonts = path.resolve(paths.root, paths.fonts || '');
80
94
  paths.sprites = path.resolve(paths.root, paths.sprites || '');
@@ -85,7 +99,9 @@ function start(opts) {
85
99
 
86
100
  const checkPath = (type) => {
87
101
  if (!fs.existsSync(paths[type])) {
88
- console.error(`The specified path for "${type}" does not exist (${paths[type]}).`);
102
+ console.error(
103
+ `The specified path for "${type}" does not exist (${paths[type]}).`,
104
+ );
89
105
  process.exit(1);
90
106
  }
91
107
  };
@@ -96,37 +112,48 @@ function start(opts) {
96
112
  checkPath('icons');
97
113
 
98
114
  /**
99
- * Recursively get all files within a directory.
100
- * Inspired by https://stackoverflow.com/a/45130990/10133863
101
- * @param {String} directory Absolute path to a directory to get files from.
102
- */
115
+ * Recursively get all files within a directory.
116
+ * Inspired by https://stackoverflow.com/a/45130990/10133863
117
+ *
118
+ * @param {string} directory Absolute path to a directory to get files from.
119
+ */
103
120
  const getFiles = async (directory) => {
104
121
  // Fetch all entries of the directory and attach type information
105
- const dirEntries = await fs.promises.readdir(directory, { withFileTypes: true });
122
+ const dirEntries = await fs.promises.readdir(directory, {
123
+ withFileTypes: true,
124
+ });
106
125
 
107
126
  // Iterate through entries and return the relative file-path to the icon directory if it is not a directory
108
127
  // otherwise initiate a recursive call
109
- const files = await Promise.all(dirEntries.map((dirEntry) => {
110
- const entryPath = path.resolve(directory, dirEntry.name);
111
- return dirEntry.isDirectory() ?
112
- getFiles(entryPath) : entryPath.replace(paths.icons + path.sep, "");
113
- }));
128
+ const files = await Promise.all(
129
+ dirEntries.map((dirEntry) => {
130
+ const entryPath = path.resolve(directory, dirEntry.name);
131
+ return dirEntry.isDirectory()
132
+ ? getFiles(entryPath)
133
+ : entryPath.replace(paths.icons + path.sep, '');
134
+ }),
135
+ );
114
136
 
115
137
  // Flatten the list of files to a single array
116
138
  return files.flat();
117
- }
139
+ };
118
140
 
119
141
  // Load all available icons into a settings object
120
- startupPromises.push(new Promise(resolve => {
121
- getFiles(paths.icons).then((files) => {
122
- paths.availableIcons = files;
123
- resolve();
124
- });
125
- }));
142
+ startupPromises.push(
143
+ new Promise((resolve) => {
144
+ getFiles(paths.icons).then((files) => {
145
+ paths.availableIcons = files;
146
+ resolve();
147
+ });
148
+ }),
149
+ );
126
150
 
127
151
  if (options.dataDecorator) {
128
152
  try {
129
- options.dataDecoratorFunc = require(path.resolve(paths.root, options.dataDecorator));
153
+ options.dataDecoratorFunc = require(path.resolve(
154
+ paths.root,
155
+ options.dataDecorator,
156
+ ));
130
157
  } catch (e) {}
131
158
  }
132
159
 
@@ -140,54 +167,69 @@ function start(opts) {
140
167
  app.use('/styles/', serve_style.init(options, serving.styles));
141
168
  if (!isLight) {
142
169
  startupPromises.push(
143
- serve_rendered.init(options, serving.rendered)
144
- .then((sub) => {
145
- app.use('/styles/', sub);
146
- })
170
+ serve_rendered.init(options, serving.rendered).then((sub) => {
171
+ app.use('/styles/', sub);
172
+ }),
147
173
  );
148
174
  }
149
175
 
150
176
  const addStyle = (id, item, allowMoreData, reportFonts) => {
151
177
  let success = true;
152
178
  if (item.serve_data !== false) {
153
- success = serve_style.add(options, serving.styles, item, id, opts.publicUrl,
154
- (mbtiles, fromData) => {
155
- let dataItemId;
156
- for (const id of Object.keys(data)) {
157
- if (fromData) {
158
- if (id === mbtiles) {
159
- dataItemId = id;
160
- }
161
- } else {
162
- if (data[id].mbtiles === mbtiles) {
163
- dataItemId = id;
164
- }
179
+ success = serve_style.add(
180
+ options,
181
+ serving.styles,
182
+ item,
183
+ id,
184
+ opts.publicUrl,
185
+ (mbtiles, fromData) => {
186
+ let dataItemId;
187
+ for (const id of Object.keys(data)) {
188
+ if (fromData) {
189
+ if (id === mbtiles) {
190
+ dataItemId = id;
165
191
  }
166
- }
167
- if (dataItemId) { // mbtiles exist in the data config
168
- return dataItemId;
169
192
  } else {
170
- if (fromData || !allowMoreData) {
171
- console.log(`ERROR: style "${item.style}" using unknown mbtiles "${mbtiles}"! Skipping...`);
172
- return undefined;
173
- } else {
174
- let id = mbtiles.substr(0, mbtiles.lastIndexOf('.')) || mbtiles;
175
- while (data[id]) id += '_';
176
- data[id] = {
177
- 'mbtiles': mbtiles
178
- };
179
- return id;
193
+ if (data[id].mbtiles === mbtiles) {
194
+ dataItemId = id;
180
195
  }
181
196
  }
182
- }, (font) => {
183
- if (reportFonts) {
184
- serving.fonts[font] = true;
197
+ }
198
+ if (dataItemId) {
199
+ // mbtiles exist in the data config
200
+ return dataItemId;
201
+ } else {
202
+ if (fromData || !allowMoreData) {
203
+ console.log(
204
+ `ERROR: style "${item.style}" using unknown mbtiles "${mbtiles}"! Skipping...`,
205
+ );
206
+ return undefined;
207
+ } else {
208
+ let id = mbtiles.substr(0, mbtiles.lastIndexOf('.')) || mbtiles;
209
+ while (data[id]) id += '_';
210
+ data[id] = {
211
+ mbtiles: mbtiles,
212
+ };
213
+ return id;
185
214
  }
186
- });
215
+ }
216
+ },
217
+ (font) => {
218
+ if (reportFonts) {
219
+ serving.fonts[font] = true;
220
+ }
221
+ },
222
+ );
187
223
  }
188
224
  if (success && item.serve_rendered !== false) {
189
225
  if (!isLight) {
190
- startupPromises.push(serve_rendered.add(options, serving.rendered, item, id, opts.publicUrl,
226
+ startupPromises.push(
227
+ serve_rendered.add(
228
+ options,
229
+ serving.rendered,
230
+ item,
231
+ id,
232
+ opts.publicUrl,
191
233
  (mbtiles) => {
192
234
  let mbtilesFile;
193
235
  for (const id of Object.keys(data)) {
@@ -196,8 +238,9 @@ function start(opts) {
196
238
  }
197
239
  }
198
240
  return mbtilesFile;
199
- }
200
- ));
241
+ },
242
+ ),
243
+ );
201
244
  } else {
202
245
  item.serve_rendered = false;
203
246
  }
@@ -215,9 +258,9 @@ function start(opts) {
215
258
  }
216
259
 
217
260
  startupPromises.push(
218
- serve_font(options, serving.fonts).then((sub) => {
219
- app.use('/', sub);
220
- })
261
+ serve_font(options, serving.fonts).then((sub) => {
262
+ app.use('/', sub);
263
+ }),
221
264
  );
222
265
 
223
266
  for (const id of Object.keys(data)) {
@@ -228,61 +271,65 @@ function start(opts) {
228
271
  }
229
272
 
230
273
  startupPromises.push(
231
- serve_data.add(options, serving.data, item, id, opts.publicUrl)
274
+ serve_data.add(options, serving.data, item, id, opts.publicUrl),
232
275
  );
233
276
  }
234
277
 
235
278
  if (options.serveAllStyles) {
236
- fs.readdir(options.paths.styles, {withFileTypes: true}, (err, files) => {
279
+ fs.readdir(options.paths.styles, { withFileTypes: true }, (err, files) => {
237
280
  if (err) {
238
281
  return;
239
282
  }
240
283
  for (const file of files) {
241
- if (file.isFile() &&
242
- path.extname(file.name).toLowerCase() == '.json') {
284
+ if (file.isFile() && path.extname(file.name).toLowerCase() == '.json') {
243
285
  const id = path.basename(file.name, '.json');
244
286
  const item = {
245
- style: file.name
287
+ style: file.name,
246
288
  };
247
289
  addStyle(id, item, false, false);
248
290
  }
249
291
  }
250
292
  });
251
293
 
252
- const watcher = chokidar.watch(path.join(options.paths.styles, '*.json'),
253
- {
254
- });
255
- watcher.on('all',
256
- (eventType, filename) => {
257
- if (filename) {
258
- const id = path.basename(filename, '.json');
259
- console.log(`Style "${id}" changed, updating...`);
260
-
261
- serve_style.remove(serving.styles, id);
262
- if (!isLight) {
263
- serve_rendered.remove(serving.rendered, id);
264
- }
294
+ const watcher = chokidar.watch(
295
+ path.join(options.paths.styles, '*.json'),
296
+ {},
297
+ );
298
+ watcher.on('all', (eventType, filename) => {
299
+ if (filename) {
300
+ const id = path.basename(filename, '.json');
301
+ console.log(`Style "${id}" changed, updating...`);
302
+
303
+ serve_style.remove(serving.styles, id);
304
+ if (!isLight) {
305
+ serve_rendered.remove(serving.rendered, id);
306
+ }
265
307
 
266
- if (eventType == 'add' || eventType == 'change') {
267
- const item = {
268
- style: filename
269
- };
270
- addStyle(id, item, false, false);
271
- }
272
- }
273
- });
308
+ if (eventType == 'add' || eventType == 'change') {
309
+ const item = {
310
+ style: filename,
311
+ };
312
+ addStyle(id, item, false, false);
313
+ }
314
+ }
315
+ });
274
316
  }
275
317
 
276
318
  app.get('/styles.json', (req, res, next) => {
277
319
  const result = [];
278
- const query = req.query.key ? (`?key=${encodeURIComponent(req.query.key)}`) : '';
320
+ const query = req.query.key
321
+ ? `?key=${encodeURIComponent(req.query.key)}`
322
+ : '';
279
323
  for (const id of Object.keys(serving.styles)) {
280
324
  const styleJSON = serving.styles[id].styleJSON;
281
325
  result.push({
282
326
  version: styleJSON.version,
283
327
  name: styleJSON.name,
284
328
  id: id,
285
- url: `${getPublicUrl(opts.publicUrl, req)}styles/${id}/style.json${query}`
329
+ url: `${getPublicUrl(
330
+ opts.publicUrl,
331
+ req,
332
+ )}styles/${id}/style.json${query}`,
286
333
  });
287
334
  }
288
335
  res.send(result);
@@ -297,9 +344,16 @@ function start(opts) {
297
344
  } else {
298
345
  path = `${type}/${id}`;
299
346
  }
300
- info.tiles = getTileUrls(req, info.tiles, path, info.format, opts.publicUrl, {
301
- 'pbf': options.pbfAlias
302
- });
347
+ info.tiles = getTileUrls(
348
+ req,
349
+ info.tiles,
350
+ path,
351
+ info.format,
352
+ opts.publicUrl,
353
+ {
354
+ pbf: options.pbfAlias,
355
+ },
356
+ );
303
357
  arr.push(info);
304
358
  }
305
359
  return arr;
@@ -325,40 +379,49 @@ function start(opts) {
325
379
  if (template === 'index') {
326
380
  if (options.frontPage === false) {
327
381
  return;
328
- } else if (options.frontPage &&
329
- options.frontPage.constructor === String) {
382
+ } else if (
383
+ options.frontPage &&
384
+ options.frontPage.constructor === String
385
+ ) {
330
386
  templateFile = path.resolve(paths.root, options.frontPage);
331
387
  }
332
388
  }
333
- startupPromises.push(new Promise((resolve, reject) => {
334
- fs.readFile(templateFile, (err, content) => {
335
- if (err) {
336
- err = new Error(`Template not found: ${err.message}`);
337
- reject(err);
338
- return;
339
- }
340
- const compiled = handlebars.compile(content.toString());
341
-
342
- app.use(urlPath, (req, res, next) => {
343
- let data = {};
344
- if (dataGetter) {
345
- data = dataGetter(req);
346
- if (!data) {
347
- return res.status(404).send('Not found');
348
- }
389
+ startupPromises.push(
390
+ new Promise((resolve, reject) => {
391
+ fs.readFile(templateFile, (err, content) => {
392
+ if (err) {
393
+ err = new Error(`Template not found: ${err.message}`);
394
+ reject(err);
395
+ return;
349
396
  }
350
- data['server_version'] = `${packageJson.name} v${packageJson.version}`;
351
- data['public_url'] = opts.publicUrl || '/';
352
- data['is_light'] = isLight;
353
- data['key_query_part'] =
354
- req.query.key ? `key=${encodeURIComponent(req.query.key)}&` : '';
355
- data['key_query'] = req.query.key ? `?key=${encodeURIComponent(req.query.key)}` : '';
356
- if (template === 'wmts') res.set('Content-Type', 'text/xml');
357
- return res.status(200).send(compiled(data));
397
+ const compiled = handlebars.compile(content.toString());
398
+
399
+ app.use(urlPath, (req, res, next) => {
400
+ let data = {};
401
+ if (dataGetter) {
402
+ data = dataGetter(req);
403
+ if (!data) {
404
+ return res.status(404).send('Not found');
405
+ }
406
+ }
407
+ data[
408
+ 'server_version'
409
+ ] = `${packageJson.name} v${packageJson.version}`;
410
+ data['public_url'] = opts.publicUrl || '/';
411
+ data['is_light'] = isLight;
412
+ data['key_query_part'] = req.query.key
413
+ ? `key=${encodeURIComponent(req.query.key)}&`
414
+ : '';
415
+ data['key_query'] = req.query.key
416
+ ? `?key=${encodeURIComponent(req.query.key)}`
417
+ : '';
418
+ if (template === 'wmts') res.set('Content-Type', 'text/xml');
419
+ return res.status(200).send(compiled(data));
420
+ });
421
+ resolve();
358
422
  });
359
- resolve();
360
- });
361
- }));
423
+ }),
424
+ );
362
425
  };
363
426
 
364
427
  serveTemplate('/$', 'index', (req) => {
@@ -371,15 +434,23 @@ function start(opts) {
371
434
  if (style.serving_rendered) {
372
435
  const center = style.serving_rendered.tileJSON.center;
373
436
  if (center) {
374
- style.viewer_hash = `#${center[2]}/${center[1].toFixed(5)}/${center[0].toFixed(5)}`;
437
+ style.viewer_hash = `#${center[2]}/${center[1].toFixed(
438
+ 5,
439
+ )}/${center[0].toFixed(5)}`;
375
440
 
376
441
  const centerPx = mercator.px([center[0], center[1]], center[2]);
377
- style.thumbnail = `${center[2]}/${Math.floor(centerPx[0] / 256)}/${Math.floor(centerPx[1] / 256)}.png`;
442
+ style.thumbnail = `${center[2]}/${Math.floor(
443
+ centerPx[0] / 256,
444
+ )}/${Math.floor(centerPx[1] / 256)}.png`;
378
445
  }
379
446
 
380
447
  style.xyz_link = getTileUrls(
381
- req, style.serving_rendered.tileJSON.tiles,
382
- `styles/${id}`, style.serving_rendered.tileJSON.format, opts.publicUrl)[0];
448
+ req,
449
+ style.serving_rendered.tileJSON.tiles,
450
+ `styles/${id}`,
451
+ style.serving_rendered.tileJSON.format,
452
+ opts.publicUrl,
453
+ )[0];
383
454
  }
384
455
  }
385
456
  const data = clone(serving.data || {});
@@ -388,19 +459,29 @@ function start(opts) {
388
459
  const tilejson = data[id].tileJSON;
389
460
  const center = tilejson.center;
390
461
  if (center) {
391
- data_.viewer_hash = `#${center[2]}/${center[1].toFixed(5)}/${center[0].toFixed(5)}`;
462
+ data_.viewer_hash = `#${center[2]}/${center[1].toFixed(
463
+ 5,
464
+ )}/${center[0].toFixed(5)}`;
392
465
  }
393
466
  data_.is_vector = tilejson.format === 'pbf';
394
467
  if (!data_.is_vector) {
395
468
  if (center) {
396
469
  const centerPx = mercator.px([center[0], center[1]], center[2]);
397
- data_.thumbnail = `${center[2]}/${Math.floor(centerPx[0] / 256)}/${Math.floor(centerPx[1] / 256)}.${data_.tileJSON.format}`;
470
+ data_.thumbnail = `${center[2]}/${Math.floor(
471
+ centerPx[0] / 256,
472
+ )}/${Math.floor(centerPx[1] / 256)}.${data_.tileJSON.format}`;
398
473
  }
399
474
 
400
475
  data_.xyz_link = getTileUrls(
401
- req, tilejson.tiles, `data/${id}`, tilejson.format, opts.publicUrl, {
402
- 'pbf': options.pbfAlias
403
- })[0];
476
+ req,
477
+ tilejson.tiles,
478
+ `data/${id}`,
479
+ tilejson.format,
480
+ opts.publicUrl,
481
+ {
482
+ pbf: options.pbfAlias,
483
+ },
484
+ )[0];
404
485
  }
405
486
  if (data_.filesize) {
406
487
  let suffix = 'kB';
@@ -418,7 +499,7 @@ function start(opts) {
418
499
  }
419
500
  return {
420
501
  styles: Object.keys(styles).length ? styles : null,
421
- data: Object.keys(data).length ? data : null
502
+ data: Object.keys(data).length ? data : null,
422
503
  };
423
504
  });
424
505
 
@@ -453,9 +534,12 @@ function start(opts) {
453
534
  wmts.name = (serving.styles[id] || serving.rendered[id]).name;
454
535
  if (opts.publicUrl) {
455
536
  wmts.baseUrl = opts.publicUrl;
456
- }
457
- else {
458
- wmts.baseUrl = `${req.get('X-Forwarded-Protocol') ? req.get('X-Forwarded-Protocol') : req.protocol}://${req.get('host')}/`;
537
+ } else {
538
+ wmts.baseUrl = `${
539
+ req.get('X-Forwarded-Protocol')
540
+ ? req.get('X-Forwarded-Protocol')
541
+ : req.protocol
542
+ }://${req.get('host')}/`;
459
543
  }
460
544
  return wmts;
461
545
  });
@@ -484,13 +568,17 @@ function start(opts) {
484
568
  }
485
569
  });
486
570
 
487
- const server = app.listen(process.env.PORT || opts.port, process.env.BIND || opts.bind, function() {
488
- let address = this.address().address;
489
- if (address.indexOf('::') === 0) {
490
- address = `[${address}]`; // literal IPv6 address
491
- }
492
- console.log(`Listening at http://${address}:${this.address().port}/`);
493
- });
571
+ const server = app.listen(
572
+ process.env.PORT || opts.port,
573
+ process.env.BIND || opts.bind,
574
+ function () {
575
+ let address = this.address().address;
576
+ if (address.indexOf('::') === 0) {
577
+ address = `[${address}]`; // literal IPv6 address
578
+ }
579
+ console.log(`Listening at http://${address}:${this.address().port}/`);
580
+ },
581
+ );
494
582
 
495
583
  // add server.shutdown() to gracefully stop serving
496
584
  enableShutdown(server);
@@ -498,10 +586,14 @@ function start(opts) {
498
586
  return {
499
587
  app: app,
500
588
  server: server,
501
- startupPromise: startupPromise
589
+ startupPromise: startupPromise,
502
590
  };
503
591
  }
504
592
 
593
+ /**
594
+ *
595
+ * @param opts
596
+ */
505
597
  export function server(opts) {
506
598
  const running = start(opts);
507
599
 
@@ -525,4 +617,4 @@ export function server(opts) {
525
617
  });
526
618
 
527
619
  return running;
528
- };
620
+ }