tileserver-gl-light 4.1.2 → 4.2.1

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.
@@ -7,19 +7,19 @@ import url from 'url';
7
7
  import util from 'util';
8
8
  import zlib from 'zlib';
9
9
  import sharp from 'sharp'; // sharp has to be required before node-canvas. see https://github.com/lovell/sharp/issues/371
10
- import {createCanvas, Image} from 'canvas';
10
+ import { createCanvas, Image } from 'canvas';
11
11
  import clone from 'clone';
12
12
  import Color from 'color';
13
13
  import express from 'express';
14
- import sanitize from "sanitize-filename";
14
+ import sanitize from 'sanitize-filename';
15
15
  import SphericalMercator from '@mapbox/sphericalmercator';
16
16
  import mlgl from '@maplibre/maplibre-gl-native';
17
17
  import MBTiles from '@mapbox/mbtiles';
18
18
  import proj4 from 'proj4';
19
19
  import request from 'request';
20
- import {getFontsPbf, getTileUrls, fixTileJSONCenter} from './utils.js';
20
+ import { getFontsPbf, getTileUrls, fixTileJSONCenter } from './utils.js';
21
21
 
22
- const FLOAT_PATTERN = '[+-]?(?:\\d+|\\d+\.?\\d+)';
22
+ const FLOAT_PATTERN = '[+-]?(?:\\d+|\\d+.?\\d+)';
23
23
  const httpTester = /^(http(s)?:)?\/\//;
24
24
 
25
25
  const mercator = new SphericalMercator();
@@ -38,7 +38,7 @@ const extensionToFormat = {
38
38
  '.jpg': 'jpeg',
39
39
  '.jpeg': 'jpeg',
40
40
  '.png': 'png',
41
- '.webp': 'webp'
41
+ '.webp': 'webp',
42
42
  };
43
43
 
44
44
  /**
@@ -46,18 +46,19 @@ const extensionToFormat = {
46
46
  * string is for unknown or unsupported formats.
47
47
  */
48
48
  const cachedEmptyResponses = {
49
- '': Buffer.alloc(0)
49
+ '': Buffer.alloc(0),
50
50
  };
51
51
 
52
52
  /**
53
53
  * Create an appropriate mlgl response for http errors.
54
+ *
54
55
  * @param {string} format The format (a sharp format or 'pbf').
55
56
  * @param {string} color The background color (or empty string for transparent).
56
57
  * @param {Function} callback The mlgl callback.
57
58
  */
58
59
  function createEmptyResponse(format, color, callback) {
59
60
  if (!format || format === 'pbf') {
60
- callback(null, {data: cachedEmptyResponses['']});
61
+ callback(null, { data: cachedEmptyResponses[''] });
61
62
  return;
62
63
  }
63
64
 
@@ -71,7 +72,7 @@ function createEmptyResponse(format, color, callback) {
71
72
  const cacheKey = `${format},${color}`;
72
73
  const data = cachedEmptyResponses[cacheKey];
73
74
  if (data) {
74
- callback(null, {data: data});
75
+ callback(null, { data: data });
75
76
  return;
76
77
  }
77
78
 
@@ -83,21 +84,25 @@ function createEmptyResponse(format, color, callback) {
83
84
  raw: {
84
85
  width: 1,
85
86
  height: 1,
86
- channels: channels
87
- }
88
- }).toFormat(format).toBuffer((err, buffer, info) => {
89
- if (!err) {
90
- cachedEmptyResponses[cacheKey] = buffer;
91
- }
92
- callback(null, {data: buffer});
93
- });
87
+ channels: channels,
88
+ },
89
+ })
90
+ .toFormat(format)
91
+ .toBuffer((err, buffer, info) => {
92
+ if (!err) {
93
+ cachedEmptyResponses[cacheKey] = buffer;
94
+ }
95
+ callback(null, { data: buffer });
96
+ });
94
97
  }
95
98
 
96
99
  /**
97
100
  * Parses coordinate pair provided to pair of floats and ensures the resulting
98
101
  * pair is a longitude/latitude combination depending on lnglat query parameter.
102
+ *
99
103
  * @param {List} coordinatePair Coordinate pair.
100
- * @param {Object} query Request query parameters.
104
+ * @param coordinates
105
+ * @param {object} query Request query parameters.
101
106
  */
102
107
  const parseCoordinatePair = (coordinates, query) => {
103
108
  const firstCoordinate = parseFloat(coordinates[0]);
@@ -119,8 +124,9 @@ const parseCoordinatePair = (coordinates, query) => {
119
124
 
120
125
  /**
121
126
  * Parses a coordinate pair from query arguments and optionally transforms it.
127
+ *
122
128
  * @param {List} coordinatePair Coordinate pair.
123
- * @param {Object} query Request query parameters.
129
+ * @param {object} query Request query parameters.
124
130
  * @param {Function} transformer Optional transform function.
125
131
  */
126
132
  const parseCoordinates = (coordinatePair, query, transformer) => {
@@ -134,10 +140,10 @@ const parseCoordinates = (coordinatePair, query, transformer) => {
134
140
  return parsedCoordinates;
135
141
  };
136
142
 
137
-
138
143
  /**
139
144
  * Parses paths provided via query into a list of path objects.
140
- * @param {Object} query Request query parameters.
145
+ *
146
+ * @param {object} query Request query parameters.
141
147
  * @param {Function} transformer Optional transform function.
142
148
  */
143
149
  const extractPathsFromQuery = (query, transformer) => {
@@ -180,9 +186,8 @@ const extractPathsFromQuery = (query, transformer) => {
180
186
 
181
187
  // Extend list of paths with current path if it contains coordinates
182
188
  if (currentPath.length) {
183
- paths.push(currentPath)
189
+ paths.push(currentPath);
184
190
  }
185
-
186
191
  }
187
192
  return paths;
188
193
  };
@@ -192,8 +197,9 @@ const extractPathsFromQuery = (query, transformer) => {
192
197
  * on marker object.
193
198
  * Options adhere to the following format
194
199
  * [optionName]:[optionValue]
200
+ *
195
201
  * @param {List[String]} optionsList List of option strings.
196
- * @param {Object} marker Marker object to configure.
202
+ * @param {object} marker Marker object to configure.
197
203
  */
198
204
  const parseMarkerOptions = (optionsList, marker) => {
199
205
  for (const options of optionsList) {
@@ -207,7 +213,7 @@ const parseMarkerOptions = (optionsList, marker) => {
207
213
  // Scale factor to up- or downscale icon
208
214
  case 'scale':
209
215
  // Scale factors must not be negative
210
- marker.scale = Math.abs(parseFloat(optionParts[1]))
216
+ marker.scale = Math.abs(parseFloat(optionParts[1]));
211
217
  break;
212
218
  // Icon offset as positive or negative pixel value in the following
213
219
  // format [offsetX],[offsetY] where [offsetY] is optional
@@ -226,8 +232,9 @@ const parseMarkerOptions = (optionsList, marker) => {
226
232
 
227
233
  /**
228
234
  * Parses markers provided via query into a list of marker objects.
229
- * @param {Object} query Request query parameters.
230
- * @param {Object} options Configuration options.
235
+ *
236
+ * @param {object} query Request query parameters.
237
+ * @param {object} options Configuration options.
231
238
  * @param {Function} transformer Optional transform function.
232
239
  */
233
240
  const extractMarkersFromQuery = (query, options, transformer) => {
@@ -240,8 +247,9 @@ const extractMarkersFromQuery = (query, options, transformer) => {
240
247
 
241
248
  // Check if multiple markers have been provided and mimic a list if it's a
242
249
  // single maker.
243
- const providedMarkers = Array.isArray(query.marker) ?
244
- query.marker : [query.marker];
250
+ const providedMarkers = Array.isArray(query.marker)
251
+ ? query.marker
252
+ : [query.marker];
245
253
 
246
254
  // Iterate through provided markers which can have one of the following
247
255
  // formats
@@ -266,7 +274,7 @@ const extractMarkersFromQuery = (query, options, transformer) => {
266
274
  if (!(iconURI.startsWith('http://') || iconURI.startsWith('https://'))) {
267
275
  // Sanitize URI with sanitize-filename
268
276
  // https://www.npmjs.com/package/sanitize-filename#details
269
- iconURI = sanitize(iconURI)
277
+ iconURI = sanitize(iconURI);
270
278
 
271
279
  // If the selected icon is not part of available icons skip it
272
280
  if (!options.paths.availableIcons.includes(iconURI)) {
@@ -275,7 +283,7 @@ const extractMarkersFromQuery = (query, options, transformer) => {
275
283
 
276
284
  iconURI = path.resolve(options.paths.icons, iconURI);
277
285
 
278
- // When we encounter a remote icon check if the configuration explicitly allows them.
286
+ // When we encounter a remote icon check if the configuration explicitly allows them.
279
287
  } else if (options.allowRemoteMarkerIcons !== true) {
280
288
  continue;
281
289
  }
@@ -298,15 +306,15 @@ const extractMarkersFromQuery = (query, options, transformer) => {
298
306
 
299
307
  // Add marker to list
300
308
  markers.push(marker);
301
-
302
309
  }
303
310
  return markers;
304
311
  };
305
312
 
306
313
  /**
307
314
  * Transforms coordinates to pixels.
315
+ *
308
316
  * @param {List[Number]} ll Longitude/Latitude coordinate pair.
309
- * @param {Number} zoom Map zoom level.
317
+ * @param {number} zoom Map zoom level.
310
318
  */
311
319
  const precisePx = (ll, zoom) => {
312
320
  const px = mercator.px(ll, 20);
@@ -316,12 +324,13 @@ const precisePx = (ll, zoom) => {
316
324
 
317
325
  /**
318
326
  * Draws a marker in cavans context.
319
- * @param {Object} ctx Canvas context object.
320
- * @param {Object} marker Marker object parsed by extractMarkersFromQuery.
321
- * @param {Number} z Map zoom level.
327
+ *
328
+ * @param {object} ctx Canvas context object.
329
+ * @param {object} marker Marker object parsed by extractMarkersFromQuery.
330
+ * @param {number} z Map zoom level.
322
331
  */
323
332
  const drawMarker = (ctx, marker, z) => {
324
- return new Promise(resolve => {
333
+ return new Promise((resolve) => {
325
334
  const img = new Image();
326
335
  const pixelCoords = precisePx(marker.location, z);
327
336
 
@@ -340,15 +349,15 @@ const drawMarker = (ctx, marker, z) => {
340
349
  // scaled as well. Additionally offsets are provided as either positive or
341
350
  // negative values so we always add them
342
351
  if (marker.offsetX) {
343
- xCoordinate = xCoordinate + (marker.offsetX * scale);
352
+ xCoordinate = xCoordinate + marker.offsetX * scale;
344
353
  }
345
354
  if (marker.offsetY) {
346
- yCoordinate = yCoordinate + (marker.offsetY * scale);
355
+ yCoordinate = yCoordinate + marker.offsetY * scale;
347
356
  }
348
357
 
349
358
  return {
350
- 'x': xCoordinate,
351
- 'y': yCoordinate
359
+ x: xCoordinate,
360
+ y: yCoordinate,
352
361
  };
353
362
  };
354
363
 
@@ -375,19 +384,22 @@ const drawMarker = (ctx, marker, z) => {
375
384
  };
376
385
 
377
386
  img.onload = drawOnCanvas;
378
- img.onerror = err => { throw err };
387
+ img.onerror = (err) => {
388
+ throw err;
389
+ };
379
390
  img.src = marker.icon;
380
391
  });
381
- }
392
+ };
382
393
 
383
394
  /**
384
395
  * Draws a list of markers onto a canvas.
385
396
  * Wraps drawing of markers into list of promises and awaits them.
386
397
  * It's required because images are expected to load asynchronous in canvas js
387
398
  * even when provided from a local disk.
388
- * @param {Object} ctx Canvas context object.
399
+ *
400
+ * @param {object} ctx Canvas context object.
389
401
  * @param {List[Object]} markers Marker objects parsed by extractMarkersFromQuery.
390
- * @param {Number} z Map zoom level.
402
+ * @param {number} z Map zoom level.
391
403
  */
392
404
  const drawMarkers = async (ctx, markers, z) => {
393
405
  const markerPromises = [];
@@ -399,14 +411,15 @@ const drawMarkers = async (ctx, markers, z) => {
399
411
 
400
412
  // Await marker drawings before continuing
401
413
  await Promise.all(markerPromises);
402
- }
414
+ };
403
415
 
404
416
  /**
405
417
  * Draws a list of coordinates onto a canvas and styles the resulting path.
406
- * @param {Object} ctx Canvas context object.
418
+ *
419
+ * @param {object} ctx Canvas context object.
407
420
  * @param {List[Number]} path List of coordinates.
408
- * @param {Object} query Request query parameters.
409
- * @param {Number} z Map zoom level.
421
+ * @param {object} query Request query parameters.
422
+ * @param {number} z Map zoom level.
410
423
  */
411
424
  const drawPath = (ctx, path, query, z) => {
412
425
  if (!path || path.length < 2) {
@@ -422,8 +435,10 @@ const drawPath = (ctx, path, query, z) => {
422
435
  }
423
436
 
424
437
  // Check if first coordinate matches last coordinate
425
- if (path[0][0] === path[path.length - 1][0] &&
426
- path[0][1] === path[path.length - 1][1]) {
438
+ if (
439
+ path[0][0] === path[path.length - 1][0] &&
440
+ path[0][1] === path[path.length - 1][1]
441
+ ) {
427
442
  ctx.closePath();
428
443
  }
429
444
 
@@ -434,14 +449,15 @@ const drawPath = (ctx, path, query, z) => {
434
449
  }
435
450
 
436
451
  // Get line width from query and fall back to 1 if not provided
437
- const lineWidth = query.width !== undefined ?
438
- parseFloat(query.width) : 1;
452
+ const lineWidth = query.width !== undefined ? parseFloat(query.width) : 1;
439
453
 
440
454
  // Ensure line width is valid
441
455
  if (lineWidth > 0) {
442
456
  // Get border width from query and fall back to 10% of line width
443
- const borderWidth = query.borderwidth !== undefined ?
444
- parseFloat(query.borderwidth) : lineWidth * 0.1;
457
+ const borderWidth =
458
+ query.borderwidth !== undefined
459
+ ? parseFloat(query.borderwidth)
460
+ : lineWidth * 0.1;
445
461
 
446
462
  // Set rendering style for the start and end points of the path
447
463
  // https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineCap
@@ -456,7 +472,7 @@ const drawPath = (ctx, path, query, z) => {
456
472
  if (query.border !== undefined && borderWidth > 0) {
457
473
  // We need to double the desired border width and add it to the line width
458
474
  // in order to get the desired border on each side of the line.
459
- ctx.lineWidth = lineWidth + (borderWidth * 2);
475
+ ctx.lineWidth = lineWidth + borderWidth * 2;
460
476
  // Set border style as rgba
461
477
  ctx.strokeStyle = query.border;
462
478
  ctx.stroke();
@@ -466,9 +482,21 @@ const drawPath = (ctx, path, query, z) => {
466
482
  ctx.strokeStyle = query.stroke || 'rgba(0,64,255,0.7)';
467
483
  ctx.stroke();
468
484
  }
469
- }
485
+ };
470
486
 
471
- const renderOverlay = async (z, x, y, bearing, pitch, w, h, scale, paths, markers, query) => {
487
+ const renderOverlay = async (
488
+ z,
489
+ x,
490
+ y,
491
+ bearing,
492
+ pitch,
493
+ w,
494
+ h,
495
+ scale,
496
+ paths,
497
+ markers,
498
+ query,
499
+ ) => {
472
500
  if ((!paths || paths.length === 0) && (!markers || markers.length === 0)) {
473
501
  return null;
474
502
  }
@@ -479,7 +507,7 @@ const renderOverlay = async (z, x, y, bearing, pitch, w, h, scale, paths, marker
479
507
  const maxEdge = center[1] + h / 2;
480
508
  const minEdge = center[1] - h / 2;
481
509
  if (maxEdge > mapHeight) {
482
- center[1] -= (maxEdge - mapHeight);
510
+ center[1] -= maxEdge - mapHeight;
483
511
  } else if (minEdge < 0) {
484
512
  center[1] -= minEdge;
485
513
  }
@@ -489,7 +517,7 @@ const renderOverlay = async (z, x, y, bearing, pitch, w, h, scale, paths, marker
489
517
  ctx.scale(scale, scale);
490
518
  if (bearing) {
491
519
  ctx.translate(w / 2, h / 2);
492
- ctx.rotate(-bearing / 180 * Math.PI);
520
+ ctx.rotate((-bearing / 180) * Math.PI);
493
521
  ctx.translate(-center[0], -center[1]);
494
522
  } else {
495
523
  // optimized path
@@ -510,18 +538,18 @@ const renderOverlay = async (z, x, y, bearing, pitch, w, h, scale, paths, marker
510
538
  const calcZForBBox = (bbox, w, h, query) => {
511
539
  let z = 25;
512
540
 
513
- const padding = query.padding !== undefined ?
514
- parseFloat(query.padding) : 0.1;
541
+ const padding = query.padding !== undefined ? parseFloat(query.padding) : 0.1;
515
542
 
516
543
  const minCorner = mercator.px([bbox[0], bbox[3]], z);
517
544
  const maxCorner = mercator.px([bbox[2], bbox[1]], z);
518
545
  const w_ = w / (1 + 2 * padding);
519
546
  const h_ = h / (1 + 2 * padding);
520
547
 
521
- z -= Math.max(
548
+ z -=
549
+ Math.max(
522
550
  Math.log((maxCorner[0] - minCorner[0]) / w_),
523
- Math.log((maxCorner[1] - minCorner[1]) / h_)
524
- ) / Math.LN2;
551
+ Math.log((maxCorner[1] - minCorner[1]) / h_),
552
+ ) / Math.LN2;
525
553
 
526
554
  z = Math.max(Math.log(Math.max(w, h) / 256) / Math.LN2, Math.min(25, z));
527
555
 
@@ -563,14 +591,36 @@ export const serve_rendered = {
563
591
 
564
592
  const app = express().disable('x-powered-by');
565
593
 
566
- const respondImage = (item, z, lon, lat, bearing, pitch, width, height, scale, format, res, next, opt_overlay, opt_mode='tile') => {
567
- if (Math.abs(lon) > 180 || Math.abs(lat) > 85.06 ||
568
- lon !== lon || lat !== lat) {
594
+ const respondImage = (
595
+ item,
596
+ z,
597
+ lon,
598
+ lat,
599
+ bearing,
600
+ pitch,
601
+ width,
602
+ height,
603
+ scale,
604
+ format,
605
+ res,
606
+ next,
607
+ opt_overlay,
608
+ opt_mode = 'tile',
609
+ ) => {
610
+ if (
611
+ Math.abs(lon) > 180 ||
612
+ Math.abs(lat) > 85.06 ||
613
+ lon !== lon ||
614
+ lat !== lat
615
+ ) {
569
616
  return res.status(400).send('Invalid center');
570
617
  }
571
- if (Math.min(width, height) <= 0 ||
618
+ if (
619
+ Math.min(width, height) <= 0 ||
572
620
  Math.max(width, height) * scale > (options.maxSize || 2048) ||
573
- width !== width || height !== height) {
621
+ width !== width ||
622
+ height !== height
623
+ ) {
574
624
  return res.status(400).send('Invalid size');
575
625
  }
576
626
  if (format === 'png' || format === 'webp') {
@@ -594,7 +644,7 @@ export const serve_rendered = {
594
644
  bearing: bearing,
595
645
  pitch: pitch,
596
646
  width: width,
597
- height: height
647
+ height: height,
598
648
  };
599
649
  if (z === 0) {
600
650
  params.width *= 2;
@@ -611,7 +661,10 @@ export const serve_rendered = {
611
661
  pool.release(renderer);
612
662
  if (err) {
613
663
  console.error(err);
614
- return res.status(500).send(err);
664
+ return res
665
+ .status(500)
666
+ .header('Content-Type', 'text/plain')
667
+ .send(err);
615
668
  }
616
669
 
617
670
  // Fix semi-transparent outlines on raw, premultiplied input
@@ -634,18 +687,21 @@ export const serve_rendered = {
634
687
  raw: {
635
688
  width: params.width * scale,
636
689
  height: params.height * scale,
637
- channels: 4
638
- }
690
+ channels: 4,
691
+ },
639
692
  });
640
693
 
641
694
  if (z > 2 && tileMargin > 0) {
642
695
  const [_, y] = mercator.px(params.center, z);
643
- let yoffset = Math.max(Math.min(0, y - 128 - tileMargin), y + 128 + tileMargin - Math.pow(2, z + 8));
696
+ let yoffset = Math.max(
697
+ Math.min(0, y - 128 - tileMargin),
698
+ y + 128 + tileMargin - Math.pow(2, z + 8),
699
+ );
644
700
  image.extract({
645
701
  left: tileMargin * scale,
646
702
  top: (tileMargin + yoffset) * scale,
647
703
  width: width * scale,
648
- height: height * scale
704
+ height: height * scale,
649
705
  });
650
706
  }
651
707
 
@@ -655,7 +711,7 @@ export const serve_rendered = {
655
711
  }
656
712
 
657
713
  if (opt_overlay) {
658
- image.composite([{input: opt_overlay}]);
714
+ image.composite([{ input: opt_overlay }]);
659
715
  }
660
716
  if (item.watermark) {
661
717
  const canvas = createCanvas(scale * width, scale * height);
@@ -668,17 +724,17 @@ export const serve_rendered = {
668
724
  ctx.fillStyle = 'rgba(0,0,0,.4)';
669
725
  ctx.fillText(item.watermark, 5, height - 5);
670
726
 
671
- image.composite([{input: canvas.toBuffer()}]);
727
+ image.composite([{ input: canvas.toBuffer() }]);
672
728
  }
673
729
 
674
730
  const formatQuality = (options.formatQuality || {})[format];
675
731
 
676
732
  if (format === 'png') {
677
- image.png({adaptiveFiltering: false});
733
+ image.png({ adaptiveFiltering: false });
678
734
  } else if (format === 'jpeg') {
679
- image.jpeg({quality: formatQuality || 80});
735
+ image.jpeg({ quality: formatQuality || 80 });
680
736
  } else if (format === 'webp') {
681
- image.webp({quality: formatQuality || 90});
737
+ image.webp({ quality: formatQuality || 90 });
682
738
  }
683
739
  image.toBuffer((err, buffer, info) => {
684
740
  if (!buffer) {
@@ -687,7 +743,7 @@ export const serve_rendered = {
687
743
 
688
744
  res.set({
689
745
  'Last-Modified': item.lastModified,
690
- 'Content-Type': `image/${format}`
746
+ 'Content-Type': `image/${format}`,
691
747
  });
692
748
  return res.status(200).send(buffer);
693
749
  });
@@ -695,80 +751,144 @@ export const serve_rendered = {
695
751
  });
696
752
  };
697
753
 
698
- app.get(`/:id/:z(\\d+)/:x(\\d+)/:y(\\d+):scale(${scalePattern})?.:format([\\w]+)`, (req, res, next) => {
699
- const item = repo[req.params.id];
700
- if (!item) {
701
- return res.sendStatus(404);
702
- }
703
-
704
- const modifiedSince = req.get('if-modified-since'); const cc = req.get('cache-control');
705
- if (modifiedSince && (!cc || cc.indexOf('no-cache') === -1)) {
706
- if (new Date(item.lastModified) <= new Date(modifiedSince)) {
707
- return res.sendStatus(304);
708
- }
709
- }
710
-
711
- const z = req.params.z | 0;
712
- const x = req.params.x | 0;
713
- const y = req.params.y | 0;
714
- const scale = getScale(req.params.scale);
715
- const format = req.params.format;
716
- if (z < 0 || x < 0 || y < 0 ||
717
- z > 22 || x >= Math.pow(2, z) || y >= Math.pow(2, z)) {
718
- return res.status(404).send('Out of bounds');
719
- }
720
- const tileSize = 256;
721
- const tileCenter = mercator.ll([
722
- ((x + 0.5) / (1 << z)) * (256 << z),
723
- ((y + 0.5) / (1 << z)) * (256 << z)
724
- ], z);
725
- return respondImage(item, z, tileCenter[0], tileCenter[1], 0, 0, tileSize, tileSize, scale, format, res, next);
726
- });
727
-
728
- if (options.serveStaticMaps !== false) {
729
- const staticPattern =
730
- `/:id/static/:raw(raw)?/%s/:width(\\d+)x:height(\\d+):scale(${scalePattern})?.:format([\\w]+)`;
731
-
732
- const centerPattern =
733
- util.format(':x(%s),:y(%s),:z(%s)(@:bearing(%s)(,:pitch(%s))?)?',
734
- FLOAT_PATTERN, FLOAT_PATTERN, FLOAT_PATTERN,
735
- FLOAT_PATTERN, FLOAT_PATTERN);
736
-
737
- app.get(util.format(staticPattern, centerPattern), async (req, res, next) => {
754
+ app.get(
755
+ `/:id/:z(\\d+)/:x(\\d+)/:y(\\d+):scale(${scalePattern})?.:format([\\w]+)`,
756
+ (req, res, next) => {
738
757
  const item = repo[req.params.id];
739
758
  if (!item) {
740
759
  return res.sendStatus(404);
741
760
  }
742
- const raw = req.params.raw;
743
- const z = +req.params.z;
744
- let x = +req.params.x;
745
- let y = +req.params.y;
746
- const bearing = +(req.params.bearing || '0');
747
- const pitch = +(req.params.pitch || '0');
748
- const w = req.params.width | 0;
749
- const h = req.params.height | 0;
761
+
762
+ const modifiedSince = req.get('if-modified-since');
763
+ const cc = req.get('cache-control');
764
+ if (modifiedSince && (!cc || cc.indexOf('no-cache') === -1)) {
765
+ if (new Date(item.lastModified) <= new Date(modifiedSince)) {
766
+ return res.sendStatus(304);
767
+ }
768
+ }
769
+
770
+ const z = req.params.z | 0;
771
+ const x = req.params.x | 0;
772
+ const y = req.params.y | 0;
750
773
  const scale = getScale(req.params.scale);
751
774
  const format = req.params.format;
752
-
753
- if (z < 0) {
754
- return res.status(404).send('Invalid zoom');
775
+ if (
776
+ z < 0 ||
777
+ x < 0 ||
778
+ y < 0 ||
779
+ z > 22 ||
780
+ x >= Math.pow(2, z) ||
781
+ y >= Math.pow(2, z)
782
+ ) {
783
+ return res.status(404).send('Out of bounds');
755
784
  }
785
+ const tileSize = 256;
786
+ const tileCenter = mercator.ll(
787
+ [
788
+ ((x + 0.5) / (1 << z)) * (256 << z),
789
+ ((y + 0.5) / (1 << z)) * (256 << z),
790
+ ],
791
+ z,
792
+ );
793
+ return respondImage(
794
+ item,
795
+ z,
796
+ tileCenter[0],
797
+ tileCenter[1],
798
+ 0,
799
+ 0,
800
+ tileSize,
801
+ tileSize,
802
+ scale,
803
+ format,
804
+ res,
805
+ next,
806
+ );
807
+ },
808
+ );
756
809
 
757
- const transformer = raw ?
758
- mercator.inverse.bind(mercator) : item.dataProjWGStoInternalWGS;
810
+ if (options.serveStaticMaps !== false) {
811
+ const staticPattern = `/:id/static/:raw(raw)?/%s/:width(\\d+)x:height(\\d+):scale(${scalePattern})?.:format([\\w]+)`;
812
+
813
+ const centerPattern = util.format(
814
+ ':x(%s),:y(%s),:z(%s)(@:bearing(%s)(,:pitch(%s))?)?',
815
+ FLOAT_PATTERN,
816
+ FLOAT_PATTERN,
817
+ FLOAT_PATTERN,
818
+ FLOAT_PATTERN,
819
+ FLOAT_PATTERN,
820
+ );
821
+
822
+ app.get(
823
+ util.format(staticPattern, centerPattern),
824
+ async (req, res, next) => {
825
+ const item = repo[req.params.id];
826
+ if (!item) {
827
+ return res.sendStatus(404);
828
+ }
829
+ const raw = req.params.raw;
830
+ const z = +req.params.z;
831
+ let x = +req.params.x;
832
+ let y = +req.params.y;
833
+ const bearing = +(req.params.bearing || '0');
834
+ const pitch = +(req.params.pitch || '0');
835
+ const w = req.params.width | 0;
836
+ const h = req.params.height | 0;
837
+ const scale = getScale(req.params.scale);
838
+ const format = req.params.format;
839
+
840
+ if (z < 0) {
841
+ return res.status(404).send('Invalid zoom');
842
+ }
759
843
 
760
- if (transformer) {
761
- const ll = transformer([x, y]);
762
- x = ll[0];
763
- y = ll[1];
764
- }
844
+ const transformer = raw
845
+ ? mercator.inverse.bind(mercator)
846
+ : item.dataProjWGStoInternalWGS;
765
847
 
766
- const paths = extractPathsFromQuery(req.query, transformer);
767
- const markers = extractMarkersFromQuery(req.query, options, transformer);
768
- const overlay = await renderOverlay(z, x, y, bearing, pitch, w, h, scale, paths, markers, req.query);
848
+ if (transformer) {
849
+ const ll = transformer([x, y]);
850
+ x = ll[0];
851
+ y = ll[1];
852
+ }
769
853
 
770
- return respondImage(item, z, x, y, bearing, pitch, w, h, scale, format, res, next, overlay, 'static');
771
- });
854
+ const paths = extractPathsFromQuery(req.query, transformer);
855
+ const markers = extractMarkersFromQuery(
856
+ req.query,
857
+ options,
858
+ transformer,
859
+ );
860
+ const overlay = await renderOverlay(
861
+ z,
862
+ x,
863
+ y,
864
+ bearing,
865
+ pitch,
866
+ w,
867
+ h,
868
+ scale,
869
+ paths,
870
+ markers,
871
+ req.query,
872
+ );
873
+
874
+ return respondImage(
875
+ item,
876
+ z,
877
+ x,
878
+ y,
879
+ bearing,
880
+ pitch,
881
+ w,
882
+ h,
883
+ scale,
884
+ format,
885
+ res,
886
+ next,
887
+ overlay,
888
+ 'static',
889
+ );
890
+ },
891
+ );
772
892
 
773
893
  const serveBounds = async (req, res, next) => {
774
894
  const item = repo[req.params.id];
@@ -776,11 +896,17 @@ export const serve_rendered = {
776
896
  return res.sendStatus(404);
777
897
  }
778
898
  const raw = req.params.raw;
779
- const bbox = [+req.params.minx, +req.params.miny, +req.params.maxx, +req.params.maxy];
899
+ const bbox = [
900
+ +req.params.minx,
901
+ +req.params.miny,
902
+ +req.params.maxx,
903
+ +req.params.maxy,
904
+ ];
780
905
  let center = [(bbox[0] + bbox[2]) / 2, (bbox[1] + bbox[3]) / 2];
781
906
 
782
- const transformer = raw ?
783
- mercator.inverse.bind(mercator) : item.dataProjWGStoInternalWGS;
907
+ const transformer = raw
908
+ ? mercator.inverse.bind(mercator)
909
+ : item.dataProjWGStoInternalWGS;
784
910
 
785
911
  if (transformer) {
786
912
  const minCorner = transformer(bbox.slice(0, 2));
@@ -804,14 +930,49 @@ export const serve_rendered = {
804
930
  const pitch = 0;
805
931
 
806
932
  const paths = extractPathsFromQuery(req.query, transformer);
807
- const markers = extractMarkersFromQuery(req.query, options, transformer);
808
- const overlay = await renderOverlay(z, x, y, bearing, pitch, w, h, scale, paths, markers, req.query);
809
- return respondImage(item, z, x, y, bearing, pitch, w, h, scale, format, res, next, overlay, 'static');
933
+ const markers = extractMarkersFromQuery(
934
+ req.query,
935
+ options,
936
+ transformer,
937
+ );
938
+ const overlay = await renderOverlay(
939
+ z,
940
+ x,
941
+ y,
942
+ bearing,
943
+ pitch,
944
+ w,
945
+ h,
946
+ scale,
947
+ paths,
948
+ markers,
949
+ req.query,
950
+ );
951
+ return respondImage(
952
+ item,
953
+ z,
954
+ x,
955
+ y,
956
+ bearing,
957
+ pitch,
958
+ w,
959
+ h,
960
+ scale,
961
+ format,
962
+ res,
963
+ next,
964
+ overlay,
965
+ 'static',
966
+ );
810
967
  };
811
968
 
812
- const boundsPattern =
813
- util.format(':minx(%s),:miny(%s),:maxx(%s),:maxy(%s)',
814
- FLOAT_PATTERN, FLOAT_PATTERN, FLOAT_PATTERN, FLOAT_PATTERN);
969
+ const boundsPattern = util.format(
970
+ ':minx(%s),:miny(%s),:maxx(%s),:maxy(%s)',
971
+ FLOAT_PATTERN,
972
+ FLOAT_PATTERN,
973
+ FLOAT_PATTERN,
974
+ FLOAT_PATTERN,
975
+ );
815
976
 
816
977
  app.get(util.format(staticPattern, boundsPattern), serveBounds);
817
978
 
@@ -839,66 +1000,102 @@ export const serve_rendered = {
839
1000
 
840
1001
  const autoPattern = 'auto';
841
1002
 
842
- app.get(util.format(staticPattern, autoPattern), async (req, res, next) => {
843
- const item = repo[req.params.id];
844
- if (!item) {
845
- return res.sendStatus(404);
846
- }
847
- const raw = req.params.raw;
848
- const w = req.params.width | 0;
849
- const h = req.params.height | 0;
850
- const bearing = 0;
851
- const pitch = 0;
852
- const scale = getScale(req.params.scale);
853
- const format = req.params.format;
854
-
855
- const transformer = raw ?
856
- mercator.inverse.bind(mercator) : item.dataProjWGStoInternalWGS;
857
-
858
- const paths = extractPathsFromQuery(req.query, transformer);
859
- const markers = extractMarkersFromQuery(req.query, options, transformer);
860
-
861
- // Extract coordinates from markers
862
- const markerCoordinates = [];
863
- for (const marker of markers) {
864
- markerCoordinates.push(marker.location);
865
- }
866
-
867
- // Create array with coordinates from markers and path
868
- const coords = new Array().concat(paths.flat()).concat(markerCoordinates);
869
-
870
- // Check if we have at least one coordinate to calculate a bounding box
871
- if (coords.length < 1) {
872
- return res.status(400).send('No coordinates provided');
873
- }
874
-
875
- const bbox = [Infinity, Infinity, -Infinity, -Infinity];
876
- for (const pair of coords) {
877
- bbox[0] = Math.min(bbox[0], pair[0]);
878
- bbox[1] = Math.min(bbox[1], pair[1]);
879
- bbox[2] = Math.max(bbox[2], pair[0]);
880
- bbox[3] = Math.max(bbox[3], pair[1]);
881
- }
1003
+ app.get(
1004
+ util.format(staticPattern, autoPattern),
1005
+ async (req, res, next) => {
1006
+ const item = repo[req.params.id];
1007
+ if (!item) {
1008
+ return res.sendStatus(404);
1009
+ }
1010
+ const raw = req.params.raw;
1011
+ const w = req.params.width | 0;
1012
+ const h = req.params.height | 0;
1013
+ const bearing = 0;
1014
+ const pitch = 0;
1015
+ const scale = getScale(req.params.scale);
1016
+ const format = req.params.format;
1017
+
1018
+ const transformer = raw
1019
+ ? mercator.inverse.bind(mercator)
1020
+ : item.dataProjWGStoInternalWGS;
1021
+
1022
+ const paths = extractPathsFromQuery(req.query, transformer);
1023
+ const markers = extractMarkersFromQuery(
1024
+ req.query,
1025
+ options,
1026
+ transformer,
1027
+ );
1028
+
1029
+ // Extract coordinates from markers
1030
+ const markerCoordinates = [];
1031
+ for (const marker of markers) {
1032
+ markerCoordinates.push(marker.location);
1033
+ }
882
1034
 
883
- const bbox_ = mercator.convert(bbox, '900913');
884
- const center = mercator.inverse(
885
- [(bbox_[0] + bbox_[2]) / 2, (bbox_[1] + bbox_[3]) / 2]
886
- );
1035
+ // Create array with coordinates from markers and path
1036
+ const coords = [].concat(paths.flat()).concat(markerCoordinates);
887
1037
 
888
- // Calculate zoom level
889
- const maxZoom = parseFloat(req.query.maxzoom);
890
- let z = calcZForBBox(bbox, w, h, req.query);
891
- if (maxZoom > 0) {
892
- z = Math.min(z, maxZoom);
893
- }
1038
+ // Check if we have at least one coordinate to calculate a bounding box
1039
+ if (coords.length < 1) {
1040
+ return res.status(400).send('No coordinates provided');
1041
+ }
894
1042
 
895
- const x = center[0];
896
- const y = center[1];
1043
+ const bbox = [Infinity, Infinity, -Infinity, -Infinity];
1044
+ for (const pair of coords) {
1045
+ bbox[0] = Math.min(bbox[0], pair[0]);
1046
+ bbox[1] = Math.min(bbox[1], pair[1]);
1047
+ bbox[2] = Math.max(bbox[2], pair[0]);
1048
+ bbox[3] = Math.max(bbox[3], pair[1]);
1049
+ }
897
1050
 
898
- const overlay = await renderOverlay(z, x, y, bearing, pitch, w, h, scale, paths, markers, req.query);
1051
+ const bbox_ = mercator.convert(bbox, '900913');
1052
+ const center = mercator.inverse([
1053
+ (bbox_[0] + bbox_[2]) / 2,
1054
+ (bbox_[1] + bbox_[3]) / 2,
1055
+ ]);
1056
+
1057
+ // Calculate zoom level
1058
+ const maxZoom = parseFloat(req.query.maxzoom);
1059
+ let z = calcZForBBox(bbox, w, h, req.query);
1060
+ if (maxZoom > 0) {
1061
+ z = Math.min(z, maxZoom);
1062
+ }
899
1063
 
900
- return respondImage(item, z, x, y, bearing, pitch, w, h, scale, format, res, next, overlay, 'static');
901
- });
1064
+ const x = center[0];
1065
+ const y = center[1];
1066
+
1067
+ const overlay = await renderOverlay(
1068
+ z,
1069
+ x,
1070
+ y,
1071
+ bearing,
1072
+ pitch,
1073
+ w,
1074
+ h,
1075
+ scale,
1076
+ paths,
1077
+ markers,
1078
+ req.query,
1079
+ );
1080
+
1081
+ return respondImage(
1082
+ item,
1083
+ z,
1084
+ x,
1085
+ y,
1086
+ bearing,
1087
+ pitch,
1088
+ w,
1089
+ h,
1090
+ scale,
1091
+ format,
1092
+ res,
1093
+ next,
1094
+ overlay,
1095
+ 'static',
1096
+ );
1097
+ },
1098
+ );
902
1099
  }
903
1100
 
904
1101
  app.get('/:id.json', (req, res, next) => {
@@ -907,8 +1104,13 @@ export const serve_rendered = {
907
1104
  return res.sendStatus(404);
908
1105
  }
909
1106
  const info = clone(item.tileJSON);
910
- info.tiles = getTileUrls(req, info.tiles,
911
- `styles/${req.params.id}`, info.format, item.publicUrl);
1107
+ info.tiles = getTileUrls(
1108
+ req,
1109
+ info.tiles,
1110
+ `styles/${req.params.id}`,
1111
+ info.format,
1112
+ item.publicUrl,
1113
+ );
912
1114
  return res.send(info);
913
1115
  });
914
1116
 
@@ -918,7 +1120,7 @@ export const serve_rendered = {
918
1120
  const map = {
919
1121
  renderers: [],
920
1122
  renderers_static: [],
921
- sources: {}
1123
+ sources: {},
922
1124
  };
923
1125
 
924
1126
  let styleJSON;
@@ -934,19 +1136,26 @@ export const serve_rendered = {
934
1136
  const dir = options.paths[protocol];
935
1137
  const file = unescape(req.url).substring(protocol.length + 3);
936
1138
  fs.readFile(path.join(dir, file), (err, data) => {
937
- callback(err, {data: data});
1139
+ callback(err, { data: data });
938
1140
  });
939
1141
  } else if (protocol === 'fonts') {
940
1142
  const parts = req.url.split('/');
941
1143
  const fontstack = unescape(parts[2]);
942
1144
  const range = parts[3].split('.')[0];
943
1145
  getFontsPbf(
944
- null, options.paths[protocol], fontstack, range, existingFonts
945
- ).then((concated) => {
946
- callback(null, {data: concated});
947
- }, (err) => {
948
- callback(err, {data: null});
949
- });
1146
+ null,
1147
+ options.paths[protocol],
1148
+ fontstack,
1149
+ range,
1150
+ existingFonts,
1151
+ ).then(
1152
+ (concated) => {
1153
+ callback(null, { data: concated });
1154
+ },
1155
+ (err) => {
1156
+ callback(err, { data: null });
1157
+ },
1158
+ );
950
1159
  } else if (protocol === 'mbtiles') {
951
1160
  const parts = req.url.split('/');
952
1161
  const sourceId = parts[2];
@@ -958,8 +1167,13 @@ export const serve_rendered = {
958
1167
  const format = parts[5].split('.')[1];
959
1168
  source.getTile(z, x, y, (err, data, headers) => {
960
1169
  if (err) {
961
- if (options.verbose) console.log('MBTiles error, serving empty', err);
962
- createEmptyResponse(sourceInfo.format, sourceInfo.color, callback);
1170
+ if (options.verbose)
1171
+ console.log('MBTiles error, serving empty', err);
1172
+ createEmptyResponse(
1173
+ sourceInfo.format,
1174
+ sourceInfo.color,
1175
+ callback,
1176
+ );
963
1177
  return;
964
1178
  }
965
1179
 
@@ -972,11 +1186,23 @@ export const serve_rendered = {
972
1186
  try {
973
1187
  response.data = zlib.unzipSync(data);
974
1188
  } catch (err) {
975
- console.log('Skipping incorrect header for tile mbtiles://%s/%s/%s/%s.pbf', id, z, x, y);
1189
+ console.log(
1190
+ 'Skipping incorrect header for tile mbtiles://%s/%s/%s/%s.pbf',
1191
+ id,
1192
+ z,
1193
+ x,
1194
+ y,
1195
+ );
976
1196
  }
977
1197
  if (options.dataDecoratorFunc) {
978
1198
  response.data = options.dataDecoratorFunc(
979
- sourceId, 'data', response.data, z, x, y);
1199
+ sourceId,
1200
+ 'data',
1201
+ response.data,
1202
+ z,
1203
+ x,
1204
+ y,
1205
+ );
980
1206
  }
981
1207
  } else {
982
1208
  response.data = data;
@@ -985,36 +1211,39 @@ export const serve_rendered = {
985
1211
  callback(null, response);
986
1212
  });
987
1213
  } else if (protocol === 'http' || protocol === 'https') {
988
- request({
989
- url: req.url,
990
- encoding: null,
991
- gzip: true
992
- }, (err, res, body) => {
993
- const parts = url.parse(req.url);
994
- const extension = path.extname(parts.pathname).toLowerCase();
995
- const format = extensionToFormat[extension] || '';
996
- if (err || res.statusCode < 200 || res.statusCode >= 300) {
997
- // console.log('HTTP error', err || res.statusCode);
998
- createEmptyResponse(format, '', callback);
999
- return;
1000
- }
1214
+ request(
1215
+ {
1216
+ url: req.url,
1217
+ encoding: null,
1218
+ gzip: true,
1219
+ },
1220
+ (err, res, body) => {
1221
+ const parts = url.parse(req.url);
1222
+ const extension = path.extname(parts.pathname).toLowerCase();
1223
+ const format = extensionToFormat[extension] || '';
1224
+ if (err || res.statusCode < 200 || res.statusCode >= 300) {
1225
+ // console.log('HTTP error', err || res.statusCode);
1226
+ createEmptyResponse(format, '', callback);
1227
+ return;
1228
+ }
1001
1229
 
1002
- const response = {};
1003
- if (res.headers.modified) {
1004
- response.modified = new Date(res.headers.modified);
1005
- }
1006
- if (res.headers.expires) {
1007
- response.expires = new Date(res.headers.expires);
1008
- }
1009
- if (res.headers.etag) {
1010
- response.etag = res.headers.etag;
1011
- }
1230
+ const response = {};
1231
+ if (res.headers.modified) {
1232
+ response.modified = new Date(res.headers.modified);
1233
+ }
1234
+ if (res.headers.expires) {
1235
+ response.expires = new Date(res.headers.expires);
1236
+ }
1237
+ if (res.headers.etag) {
1238
+ response.etag = res.headers.etag;
1239
+ }
1012
1240
 
1013
- response.data = body;
1014
- callback(null, response);
1015
- });
1241
+ response.data = body;
1242
+ callback(null, response);
1243
+ },
1244
+ );
1016
1245
  }
1017
- }
1246
+ },
1018
1247
  });
1019
1248
  renderer.load(styleJSON);
1020
1249
  createCallback(null, renderer);
@@ -1025,7 +1254,7 @@ export const serve_rendered = {
1025
1254
  create: createRenderer.bind(null, ratio),
1026
1255
  destroy: (renderer) => {
1027
1256
  renderer.release();
1028
- }
1257
+ },
1029
1258
  });
1030
1259
  };
1031
1260
 
@@ -1039,16 +1268,20 @@ export const serve_rendered = {
1039
1268
  }
1040
1269
 
1041
1270
  if (styleJSON.sprite && !httpTester.test(styleJSON.sprite)) {
1042
- styleJSON.sprite = 'sprites://' +
1271
+ styleJSON.sprite =
1272
+ 'sprites://' +
1043
1273
  styleJSON.sprite
1044
- .replace('{style}', path.basename(styleFile, '.json'))
1045
- .replace('{styleJsonFolder}', path.relative(options.paths.sprites, path.dirname(styleJSONPath)));
1274
+ .replace('{style}', path.basename(styleFile, '.json'))
1275
+ .replace(
1276
+ '{styleJsonFolder}',
1277
+ path.relative(options.paths.sprites, path.dirname(styleJSONPath)),
1278
+ );
1046
1279
  }
1047
1280
  if (styleJSON.glyphs && !httpTester.test(styleJSON.glyphs)) {
1048
1281
  styleJSON.glyphs = `fonts://${styleJSON.glyphs}`;
1049
1282
  }
1050
1283
 
1051
- for (const layer of (styleJSON.layers || [])) {
1284
+ for (const layer of styleJSON.layers || []) {
1052
1285
  if (layer && layer.paint) {
1053
1286
  // Remove (flatten) 3D buildings
1054
1287
  if (layer.paint['fill-extrusion-height']) {
@@ -1061,14 +1294,14 @@ export const serve_rendered = {
1061
1294
  }
1062
1295
 
1063
1296
  const tileJSON = {
1064
- 'tilejson': '2.0.0',
1065
- 'name': styleJSON.name,
1066
- 'attribution': '',
1067
- 'minzoom': 0,
1068
- 'maxzoom': 20,
1069
- 'bounds': [-180, -85.0511, 180, 85.0511],
1070
- 'format': 'png',
1071
- 'type': 'baselayer'
1297
+ tilejson: '2.0.0',
1298
+ name: styleJSON.name,
1299
+ attribution: '',
1300
+ minzoom: 0,
1301
+ maxzoom: 20,
1302
+ bounds: [-180, -85.0511, 180, 85.0511],
1303
+ format: 'png',
1304
+ type: 'baselayer',
1072
1305
  };
1073
1306
  const attributionOverride = params.tilejson && params.tilejson.attribution;
1074
1307
  Object.assign(tileJSON, params.tilejson || {});
@@ -1081,7 +1314,7 @@ export const serve_rendered = {
1081
1314
  map,
1082
1315
  dataProjWGStoInternalWGS: null,
1083
1316
  lastModified: new Date().toUTCString(),
1084
- watermark: params.watermark || options.watermark
1317
+ watermark: params.watermark || options.watermark,
1085
1318
  };
1086
1319
  repo[id] = repoobj;
1087
1320
 
@@ -1095,8 +1328,8 @@ export const serve_rendered = {
1095
1328
  delete source.url;
1096
1329
 
1097
1330
  let mbtilesFile = url.substring('mbtiles://'.length);
1098
- const fromData = mbtilesFile[0] === '{' &&
1099
- mbtilesFile[mbtilesFile.length - 1] === '}';
1331
+ const fromData =
1332
+ mbtilesFile[0] === '{' && mbtilesFile[mbtilesFile.length - 1] === '}';
1100
1333
 
1101
1334
  if (fromData) {
1102
1335
  mbtilesFile = mbtilesFile.substr(1, mbtilesFile.length - 2);
@@ -1111,52 +1344,58 @@ export const serve_rendered = {
1111
1344
  }
1112
1345
  }
1113
1346
 
1114
- queue.push(new Promise((resolve, reject) => {
1115
- mbtilesFile = path.resolve(options.paths.mbtiles, mbtilesFile);
1116
- const mbtilesFileStats = fs.statSync(mbtilesFile);
1117
- if (!mbtilesFileStats.isFile() || mbtilesFileStats.size === 0) {
1118
- throw Error(`Not valid MBTiles file: ${mbtilesFile}`);
1119
- }
1120
- map.sources[name] = new MBTiles(mbtilesFile + '?mode=ro', err => {
1121
- map.sources[name].getInfo((err, info) => {
1122
- if (err) {
1123
- console.error(err);
1124
- return;
1125
- }
1126
-
1127
- if (!repoobj.dataProjWGStoInternalWGS && info.proj4) {
1128
- // how to do this for multiple sources with different proj4 defs?
1129
- const to3857 = proj4('EPSG:3857');
1130
- const toDataProj = proj4(info.proj4);
1131
- repoobj.dataProjWGStoInternalWGS = (xy) => to3857.inverse(toDataProj.forward(xy));
1132
- }
1133
-
1134
- const type = source.type;
1135
- Object.assign(source, info);
1136
- source.type = type;
1137
- source.tiles = [
1138
- // meta url which will be detected when requested
1139
- `mbtiles://${name}/{z}/{x}/{y}.${info.format || 'pbf'}`
1140
- ];
1141
- delete source.scheme;
1142
-
1143
- if (options.dataDecoratorFunc) {
1144
- source = options.dataDecoratorFunc(name, 'tilejson', source);
1145
- }
1146
-
1147
- if (!attributionOverride &&
1148
- source.attribution && source.attribution.length > 0) {
1149
- if (!tileJSON.attribution.includes(source.attribution)) {
1150
- if (tileJSON.attribution.length > 0) {
1151
- tileJSON.attribution += ' | ';
1347
+ queue.push(
1348
+ new Promise((resolve, reject) => {
1349
+ mbtilesFile = path.resolve(options.paths.mbtiles, mbtilesFile);
1350
+ const mbtilesFileStats = fs.statSync(mbtilesFile);
1351
+ if (!mbtilesFileStats.isFile() || mbtilesFileStats.size === 0) {
1352
+ throw Error(`Not valid MBTiles file: ${mbtilesFile}`);
1353
+ }
1354
+ map.sources[name] = new MBTiles(mbtilesFile + '?mode=ro', (err) => {
1355
+ map.sources[name].getInfo((err, info) => {
1356
+ if (err) {
1357
+ console.error(err);
1358
+ return;
1359
+ }
1360
+
1361
+ if (!repoobj.dataProjWGStoInternalWGS && info.proj4) {
1362
+ // how to do this for multiple sources with different proj4 defs?
1363
+ const to3857 = proj4('EPSG:3857');
1364
+ const toDataProj = proj4(info.proj4);
1365
+ repoobj.dataProjWGStoInternalWGS = (xy) =>
1366
+ to3857.inverse(toDataProj.forward(xy));
1367
+ }
1368
+
1369
+ const type = source.type;
1370
+ Object.assign(source, info);
1371
+ source.type = type;
1372
+ source.tiles = [
1373
+ // meta url which will be detected when requested
1374
+ `mbtiles://${name}/{z}/{x}/{y}.${info.format || 'pbf'}`,
1375
+ ];
1376
+ delete source.scheme;
1377
+
1378
+ if (options.dataDecoratorFunc) {
1379
+ source = options.dataDecoratorFunc(name, 'tilejson', source);
1380
+ }
1381
+
1382
+ if (
1383
+ !attributionOverride &&
1384
+ source.attribution &&
1385
+ source.attribution.length > 0
1386
+ ) {
1387
+ if (!tileJSON.attribution.includes(source.attribution)) {
1388
+ if (tileJSON.attribution.length > 0) {
1389
+ tileJSON.attribution += ' | ';
1390
+ }
1391
+ tileJSON.attribution += source.attribution;
1152
1392
  }
1153
- tileJSON.attribution += source.attribution;
1154
1393
  }
1155
- }
1156
- resolve();
1394
+ resolve();
1395
+ });
1157
1396
  });
1158
- });
1159
- }));
1397
+ }),
1398
+ );
1160
1399
  }
1161
1400
  }
1162
1401
 
@@ -1170,7 +1409,12 @@ export const serve_rendered = {
1170
1409
  const minPoolSize = minPoolSizes[i];
1171
1410
  const maxPoolSize = Math.max(minPoolSize, maxPoolSizes[j]);
1172
1411
  map.renderers[s] = createPool(s, 'tile', minPoolSize, maxPoolSize);
1173
- map.renderers_static[s] = createPool(s, 'static', minPoolSize, maxPoolSize);
1412
+ map.renderers_static[s] = createPool(
1413
+ s,
1414
+ 'static',
1415
+ minPoolSize,
1416
+ maxPoolSize,
1417
+ );
1174
1418
  }
1175
1419
  });
1176
1420
 
@@ -1187,5 +1431,5 @@ export const serve_rendered = {
1187
1431
  });
1188
1432
  }
1189
1433
  delete repo[id];
1190
- }
1434
+ },
1191
1435
  };