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.
@@ -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;
@@ -634,18 +684,21 @@ export const serve_rendered = {
634
684
  raw: {
635
685
  width: params.width * scale,
636
686
  height: params.height * scale,
637
- channels: 4
638
- }
687
+ channels: 4,
688
+ },
639
689
  });
640
690
 
641
691
  if (z > 2 && tileMargin > 0) {
642
692
  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));
693
+ let yoffset = Math.max(
694
+ Math.min(0, y - 128 - tileMargin),
695
+ y + 128 + tileMargin - Math.pow(2, z + 8),
696
+ );
644
697
  image.extract({
645
698
  left: tileMargin * scale,
646
699
  top: (tileMargin + yoffset) * scale,
647
700
  width: width * scale,
648
- height: height * scale
701
+ height: height * scale,
649
702
  });
650
703
  }
651
704
 
@@ -655,7 +708,7 @@ export const serve_rendered = {
655
708
  }
656
709
 
657
710
  if (opt_overlay) {
658
- image.composite([{input: opt_overlay}]);
711
+ image.composite([{ input: opt_overlay }]);
659
712
  }
660
713
  if (item.watermark) {
661
714
  const canvas = createCanvas(scale * width, scale * height);
@@ -668,17 +721,17 @@ export const serve_rendered = {
668
721
  ctx.fillStyle = 'rgba(0,0,0,.4)';
669
722
  ctx.fillText(item.watermark, 5, height - 5);
670
723
 
671
- image.composite([{input: canvas.toBuffer()}]);
724
+ image.composite([{ input: canvas.toBuffer() }]);
672
725
  }
673
726
 
674
727
  const formatQuality = (options.formatQuality || {})[format];
675
728
 
676
729
  if (format === 'png') {
677
- image.png({adaptiveFiltering: false});
730
+ image.png({ adaptiveFiltering: false });
678
731
  } else if (format === 'jpeg') {
679
- image.jpeg({quality: formatQuality || 80});
732
+ image.jpeg({ quality: formatQuality || 80 });
680
733
  } else if (format === 'webp') {
681
- image.webp({quality: formatQuality || 90});
734
+ image.webp({ quality: formatQuality || 90 });
682
735
  }
683
736
  image.toBuffer((err, buffer, info) => {
684
737
  if (!buffer) {
@@ -687,7 +740,7 @@ export const serve_rendered = {
687
740
 
688
741
  res.set({
689
742
  'Last-Modified': item.lastModified,
690
- 'Content-Type': `image/${format}`
743
+ 'Content-Type': `image/${format}`,
691
744
  });
692
745
  return res.status(200).send(buffer);
693
746
  });
@@ -695,80 +748,144 @@ export const serve_rendered = {
695
748
  });
696
749
  };
697
750
 
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) => {
751
+ app.get(
752
+ `/:id/:z(\\d+)/:x(\\d+)/:y(\\d+):scale(${scalePattern})?.:format([\\w]+)`,
753
+ (req, res, next) => {
738
754
  const item = repo[req.params.id];
739
755
  if (!item) {
740
756
  return res.sendStatus(404);
741
757
  }
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;
758
+
759
+ const modifiedSince = req.get('if-modified-since');
760
+ const cc = req.get('cache-control');
761
+ if (modifiedSince && (!cc || cc.indexOf('no-cache') === -1)) {
762
+ if (new Date(item.lastModified) <= new Date(modifiedSince)) {
763
+ return res.sendStatus(304);
764
+ }
765
+ }
766
+
767
+ const z = req.params.z | 0;
768
+ const x = req.params.x | 0;
769
+ const y = req.params.y | 0;
750
770
  const scale = getScale(req.params.scale);
751
771
  const format = req.params.format;
752
-
753
- if (z < 0) {
754
- return res.status(404).send('Invalid zoom');
772
+ if (
773
+ z < 0 ||
774
+ x < 0 ||
775
+ y < 0 ||
776
+ z > 22 ||
777
+ x >= Math.pow(2, z) ||
778
+ y >= Math.pow(2, z)
779
+ ) {
780
+ return res.status(404).send('Out of bounds');
755
781
  }
782
+ const tileSize = 256;
783
+ const tileCenter = mercator.ll(
784
+ [
785
+ ((x + 0.5) / (1 << z)) * (256 << z),
786
+ ((y + 0.5) / (1 << z)) * (256 << z),
787
+ ],
788
+ z,
789
+ );
790
+ return respondImage(
791
+ item,
792
+ z,
793
+ tileCenter[0],
794
+ tileCenter[1],
795
+ 0,
796
+ 0,
797
+ tileSize,
798
+ tileSize,
799
+ scale,
800
+ format,
801
+ res,
802
+ next,
803
+ );
804
+ },
805
+ );
756
806
 
757
- const transformer = raw ?
758
- mercator.inverse.bind(mercator) : item.dataProjWGStoInternalWGS;
807
+ if (options.serveStaticMaps !== false) {
808
+ const staticPattern = `/:id/static/:raw(raw)?/%s/:width(\\d+)x:height(\\d+):scale(${scalePattern})?.:format([\\w]+)`;
809
+
810
+ const centerPattern = util.format(
811
+ ':x(%s),:y(%s),:z(%s)(@:bearing(%s)(,:pitch(%s))?)?',
812
+ FLOAT_PATTERN,
813
+ FLOAT_PATTERN,
814
+ FLOAT_PATTERN,
815
+ FLOAT_PATTERN,
816
+ FLOAT_PATTERN,
817
+ );
818
+
819
+ app.get(
820
+ util.format(staticPattern, centerPattern),
821
+ async (req, res, next) => {
822
+ const item = repo[req.params.id];
823
+ if (!item) {
824
+ return res.sendStatus(404);
825
+ }
826
+ const raw = req.params.raw;
827
+ const z = +req.params.z;
828
+ let x = +req.params.x;
829
+ let y = +req.params.y;
830
+ const bearing = +(req.params.bearing || '0');
831
+ const pitch = +(req.params.pitch || '0');
832
+ const w = req.params.width | 0;
833
+ const h = req.params.height | 0;
834
+ const scale = getScale(req.params.scale);
835
+ const format = req.params.format;
836
+
837
+ if (z < 0) {
838
+ return res.status(404).send('Invalid zoom');
839
+ }
759
840
 
760
- if (transformer) {
761
- const ll = transformer([x, y]);
762
- x = ll[0];
763
- y = ll[1];
764
- }
841
+ const transformer = raw
842
+ ? mercator.inverse.bind(mercator)
843
+ : item.dataProjWGStoInternalWGS;
765
844
 
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);
845
+ if (transformer) {
846
+ const ll = transformer([x, y]);
847
+ x = ll[0];
848
+ y = ll[1];
849
+ }
769
850
 
770
- return respondImage(item, z, x, y, bearing, pitch, w, h, scale, format, res, next, overlay, 'static');
771
- });
851
+ const paths = extractPathsFromQuery(req.query, transformer);
852
+ const markers = extractMarkersFromQuery(
853
+ req.query,
854
+ options,
855
+ transformer,
856
+ );
857
+ const overlay = await renderOverlay(
858
+ z,
859
+ x,
860
+ y,
861
+ bearing,
862
+ pitch,
863
+ w,
864
+ h,
865
+ scale,
866
+ paths,
867
+ markers,
868
+ req.query,
869
+ );
870
+
871
+ return respondImage(
872
+ item,
873
+ z,
874
+ x,
875
+ y,
876
+ bearing,
877
+ pitch,
878
+ w,
879
+ h,
880
+ scale,
881
+ format,
882
+ res,
883
+ next,
884
+ overlay,
885
+ 'static',
886
+ );
887
+ },
888
+ );
772
889
 
773
890
  const serveBounds = async (req, res, next) => {
774
891
  const item = repo[req.params.id];
@@ -776,11 +893,17 @@ export const serve_rendered = {
776
893
  return res.sendStatus(404);
777
894
  }
778
895
  const raw = req.params.raw;
779
- const bbox = [+req.params.minx, +req.params.miny, +req.params.maxx, +req.params.maxy];
896
+ const bbox = [
897
+ +req.params.minx,
898
+ +req.params.miny,
899
+ +req.params.maxx,
900
+ +req.params.maxy,
901
+ ];
780
902
  let center = [(bbox[0] + bbox[2]) / 2, (bbox[1] + bbox[3]) / 2];
781
903
 
782
- const transformer = raw ?
783
- mercator.inverse.bind(mercator) : item.dataProjWGStoInternalWGS;
904
+ const transformer = raw
905
+ ? mercator.inverse.bind(mercator)
906
+ : item.dataProjWGStoInternalWGS;
784
907
 
785
908
  if (transformer) {
786
909
  const minCorner = transformer(bbox.slice(0, 2));
@@ -804,14 +927,49 @@ export const serve_rendered = {
804
927
  const pitch = 0;
805
928
 
806
929
  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');
930
+ const markers = extractMarkersFromQuery(
931
+ req.query,
932
+ options,
933
+ transformer,
934
+ );
935
+ const overlay = await renderOverlay(
936
+ z,
937
+ x,
938
+ y,
939
+ bearing,
940
+ pitch,
941
+ w,
942
+ h,
943
+ scale,
944
+ paths,
945
+ markers,
946
+ req.query,
947
+ );
948
+ return respondImage(
949
+ item,
950
+ z,
951
+ x,
952
+ y,
953
+ bearing,
954
+ pitch,
955
+ w,
956
+ h,
957
+ scale,
958
+ format,
959
+ res,
960
+ next,
961
+ overlay,
962
+ 'static',
963
+ );
810
964
  };
811
965
 
812
- const boundsPattern =
813
- util.format(':minx(%s),:miny(%s),:maxx(%s),:maxy(%s)',
814
- FLOAT_PATTERN, FLOAT_PATTERN, FLOAT_PATTERN, FLOAT_PATTERN);
966
+ const boundsPattern = util.format(
967
+ ':minx(%s),:miny(%s),:maxx(%s),:maxy(%s)',
968
+ FLOAT_PATTERN,
969
+ FLOAT_PATTERN,
970
+ FLOAT_PATTERN,
971
+ FLOAT_PATTERN,
972
+ );
815
973
 
816
974
  app.get(util.format(staticPattern, boundsPattern), serveBounds);
817
975
 
@@ -839,66 +997,102 @@ export const serve_rendered = {
839
997
 
840
998
  const autoPattern = 'auto';
841
999
 
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
- }
1000
+ app.get(
1001
+ util.format(staticPattern, autoPattern),
1002
+ async (req, res, next) => {
1003
+ const item = repo[req.params.id];
1004
+ if (!item) {
1005
+ return res.sendStatus(404);
1006
+ }
1007
+ const raw = req.params.raw;
1008
+ const w = req.params.width | 0;
1009
+ const h = req.params.height | 0;
1010
+ const bearing = 0;
1011
+ const pitch = 0;
1012
+ const scale = getScale(req.params.scale);
1013
+ const format = req.params.format;
1014
+
1015
+ const transformer = raw
1016
+ ? mercator.inverse.bind(mercator)
1017
+ : item.dataProjWGStoInternalWGS;
1018
+
1019
+ const paths = extractPathsFromQuery(req.query, transformer);
1020
+ const markers = extractMarkersFromQuery(
1021
+ req.query,
1022
+ options,
1023
+ transformer,
1024
+ );
1025
+
1026
+ // Extract coordinates from markers
1027
+ const markerCoordinates = [];
1028
+ for (const marker of markers) {
1029
+ markerCoordinates.push(marker.location);
1030
+ }
882
1031
 
883
- const bbox_ = mercator.convert(bbox, '900913');
884
- const center = mercator.inverse(
885
- [(bbox_[0] + bbox_[2]) / 2, (bbox_[1] + bbox_[3]) / 2]
886
- );
1032
+ // Create array with coordinates from markers and path
1033
+ const coords = [].concat(paths.flat()).concat(markerCoordinates);
887
1034
 
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
- }
1035
+ // Check if we have at least one coordinate to calculate a bounding box
1036
+ if (coords.length < 1) {
1037
+ return res.status(400).send('No coordinates provided');
1038
+ }
894
1039
 
895
- const x = center[0];
896
- const y = center[1];
1040
+ const bbox = [Infinity, Infinity, -Infinity, -Infinity];
1041
+ for (const pair of coords) {
1042
+ bbox[0] = Math.min(bbox[0], pair[0]);
1043
+ bbox[1] = Math.min(bbox[1], pair[1]);
1044
+ bbox[2] = Math.max(bbox[2], pair[0]);
1045
+ bbox[3] = Math.max(bbox[3], pair[1]);
1046
+ }
897
1047
 
898
- const overlay = await renderOverlay(z, x, y, bearing, pitch, w, h, scale, paths, markers, req.query);
1048
+ const bbox_ = mercator.convert(bbox, '900913');
1049
+ const center = mercator.inverse([
1050
+ (bbox_[0] + bbox_[2]) / 2,
1051
+ (bbox_[1] + bbox_[3]) / 2,
1052
+ ]);
1053
+
1054
+ // Calculate zoom level
1055
+ const maxZoom = parseFloat(req.query.maxzoom);
1056
+ let z = calcZForBBox(bbox, w, h, req.query);
1057
+ if (maxZoom > 0) {
1058
+ z = Math.min(z, maxZoom);
1059
+ }
899
1060
 
900
- return respondImage(item, z, x, y, bearing, pitch, w, h, scale, format, res, next, overlay, 'static');
901
- });
1061
+ const x = center[0];
1062
+ const y = center[1];
1063
+
1064
+ const overlay = await renderOverlay(
1065
+ z,
1066
+ x,
1067
+ y,
1068
+ bearing,
1069
+ pitch,
1070
+ w,
1071
+ h,
1072
+ scale,
1073
+ paths,
1074
+ markers,
1075
+ req.query,
1076
+ );
1077
+
1078
+ return respondImage(
1079
+ item,
1080
+ z,
1081
+ x,
1082
+ y,
1083
+ bearing,
1084
+ pitch,
1085
+ w,
1086
+ h,
1087
+ scale,
1088
+ format,
1089
+ res,
1090
+ next,
1091
+ overlay,
1092
+ 'static',
1093
+ );
1094
+ },
1095
+ );
902
1096
  }
903
1097
 
904
1098
  app.get('/:id.json', (req, res, next) => {
@@ -907,8 +1101,13 @@ export const serve_rendered = {
907
1101
  return res.sendStatus(404);
908
1102
  }
909
1103
  const info = clone(item.tileJSON);
910
- info.tiles = getTileUrls(req, info.tiles,
911
- `styles/${req.params.id}`, info.format, item.publicUrl);
1104
+ info.tiles = getTileUrls(
1105
+ req,
1106
+ info.tiles,
1107
+ `styles/${req.params.id}`,
1108
+ info.format,
1109
+ item.publicUrl,
1110
+ );
912
1111
  return res.send(info);
913
1112
  });
914
1113
 
@@ -918,7 +1117,7 @@ export const serve_rendered = {
918
1117
  const map = {
919
1118
  renderers: [],
920
1119
  renderers_static: [],
921
- sources: {}
1120
+ sources: {},
922
1121
  };
923
1122
 
924
1123
  let styleJSON;
@@ -934,19 +1133,26 @@ export const serve_rendered = {
934
1133
  const dir = options.paths[protocol];
935
1134
  const file = unescape(req.url).substring(protocol.length + 3);
936
1135
  fs.readFile(path.join(dir, file), (err, data) => {
937
- callback(err, {data: data});
1136
+ callback(err, { data: data });
938
1137
  });
939
1138
  } else if (protocol === 'fonts') {
940
1139
  const parts = req.url.split('/');
941
1140
  const fontstack = unescape(parts[2]);
942
1141
  const range = parts[3].split('.')[0];
943
1142
  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
- });
1143
+ null,
1144
+ options.paths[protocol],
1145
+ fontstack,
1146
+ range,
1147
+ existingFonts,
1148
+ ).then(
1149
+ (concated) => {
1150
+ callback(null, { data: concated });
1151
+ },
1152
+ (err) => {
1153
+ callback(err, { data: null });
1154
+ },
1155
+ );
950
1156
  } else if (protocol === 'mbtiles') {
951
1157
  const parts = req.url.split('/');
952
1158
  const sourceId = parts[2];
@@ -958,8 +1164,13 @@ export const serve_rendered = {
958
1164
  const format = parts[5].split('.')[1];
959
1165
  source.getTile(z, x, y, (err, data, headers) => {
960
1166
  if (err) {
961
- if (options.verbose) console.log('MBTiles error, serving empty', err);
962
- createEmptyResponse(sourceInfo.format, sourceInfo.color, callback);
1167
+ if (options.verbose)
1168
+ console.log('MBTiles error, serving empty', err);
1169
+ createEmptyResponse(
1170
+ sourceInfo.format,
1171
+ sourceInfo.color,
1172
+ callback,
1173
+ );
963
1174
  return;
964
1175
  }
965
1176
 
@@ -972,11 +1183,23 @@ export const serve_rendered = {
972
1183
  try {
973
1184
  response.data = zlib.unzipSync(data);
974
1185
  } catch (err) {
975
- console.log('Skipping incorrect header for tile mbtiles://%s/%s/%s/%s.pbf', id, z, x, y);
1186
+ console.log(
1187
+ 'Skipping incorrect header for tile mbtiles://%s/%s/%s/%s.pbf',
1188
+ id,
1189
+ z,
1190
+ x,
1191
+ y,
1192
+ );
976
1193
  }
977
1194
  if (options.dataDecoratorFunc) {
978
1195
  response.data = options.dataDecoratorFunc(
979
- sourceId, 'data', response.data, z, x, y);
1196
+ sourceId,
1197
+ 'data',
1198
+ response.data,
1199
+ z,
1200
+ x,
1201
+ y,
1202
+ );
980
1203
  }
981
1204
  } else {
982
1205
  response.data = data;
@@ -985,36 +1208,39 @@ export const serve_rendered = {
985
1208
  callback(null, response);
986
1209
  });
987
1210
  } 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
- }
1211
+ request(
1212
+ {
1213
+ url: req.url,
1214
+ encoding: null,
1215
+ gzip: true,
1216
+ },
1217
+ (err, res, body) => {
1218
+ const parts = url.parse(req.url);
1219
+ const extension = path.extname(parts.pathname).toLowerCase();
1220
+ const format = extensionToFormat[extension] || '';
1221
+ if (err || res.statusCode < 200 || res.statusCode >= 300) {
1222
+ // console.log('HTTP error', err || res.statusCode);
1223
+ createEmptyResponse(format, '', callback);
1224
+ return;
1225
+ }
1001
1226
 
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
- }
1227
+ const response = {};
1228
+ if (res.headers.modified) {
1229
+ response.modified = new Date(res.headers.modified);
1230
+ }
1231
+ if (res.headers.expires) {
1232
+ response.expires = new Date(res.headers.expires);
1233
+ }
1234
+ if (res.headers.etag) {
1235
+ response.etag = res.headers.etag;
1236
+ }
1012
1237
 
1013
- response.data = body;
1014
- callback(null, response);
1015
- });
1238
+ response.data = body;
1239
+ callback(null, response);
1240
+ },
1241
+ );
1016
1242
  }
1017
- }
1243
+ },
1018
1244
  });
1019
1245
  renderer.load(styleJSON);
1020
1246
  createCallback(null, renderer);
@@ -1025,7 +1251,7 @@ export const serve_rendered = {
1025
1251
  create: createRenderer.bind(null, ratio),
1026
1252
  destroy: (renderer) => {
1027
1253
  renderer.release();
1028
- }
1254
+ },
1029
1255
  });
1030
1256
  };
1031
1257
 
@@ -1039,16 +1265,20 @@ export const serve_rendered = {
1039
1265
  }
1040
1266
 
1041
1267
  if (styleJSON.sprite && !httpTester.test(styleJSON.sprite)) {
1042
- styleJSON.sprite = 'sprites://' +
1268
+ styleJSON.sprite =
1269
+ 'sprites://' +
1043
1270
  styleJSON.sprite
1044
- .replace('{style}', path.basename(styleFile, '.json'))
1045
- .replace('{styleJsonFolder}', path.relative(options.paths.sprites, path.dirname(styleJSONPath)));
1271
+ .replace('{style}', path.basename(styleFile, '.json'))
1272
+ .replace(
1273
+ '{styleJsonFolder}',
1274
+ path.relative(options.paths.sprites, path.dirname(styleJSONPath)),
1275
+ );
1046
1276
  }
1047
1277
  if (styleJSON.glyphs && !httpTester.test(styleJSON.glyphs)) {
1048
1278
  styleJSON.glyphs = `fonts://${styleJSON.glyphs}`;
1049
1279
  }
1050
1280
 
1051
- for (const layer of (styleJSON.layers || [])) {
1281
+ for (const layer of styleJSON.layers || []) {
1052
1282
  if (layer && layer.paint) {
1053
1283
  // Remove (flatten) 3D buildings
1054
1284
  if (layer.paint['fill-extrusion-height']) {
@@ -1061,14 +1291,14 @@ export const serve_rendered = {
1061
1291
  }
1062
1292
 
1063
1293
  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'
1294
+ tilejson: '2.0.0',
1295
+ name: styleJSON.name,
1296
+ attribution: '',
1297
+ minzoom: 0,
1298
+ maxzoom: 20,
1299
+ bounds: [-180, -85.0511, 180, 85.0511],
1300
+ format: 'png',
1301
+ type: 'baselayer',
1072
1302
  };
1073
1303
  const attributionOverride = params.tilejson && params.tilejson.attribution;
1074
1304
  Object.assign(tileJSON, params.tilejson || {});
@@ -1081,7 +1311,7 @@ export const serve_rendered = {
1081
1311
  map,
1082
1312
  dataProjWGStoInternalWGS: null,
1083
1313
  lastModified: new Date().toUTCString(),
1084
- watermark: params.watermark || options.watermark
1314
+ watermark: params.watermark || options.watermark,
1085
1315
  };
1086
1316
  repo[id] = repoobj;
1087
1317
 
@@ -1095,8 +1325,8 @@ export const serve_rendered = {
1095
1325
  delete source.url;
1096
1326
 
1097
1327
  let mbtilesFile = url.substring('mbtiles://'.length);
1098
- const fromData = mbtilesFile[0] === '{' &&
1099
- mbtilesFile[mbtilesFile.length - 1] === '}';
1328
+ const fromData =
1329
+ mbtilesFile[0] === '{' && mbtilesFile[mbtilesFile.length - 1] === '}';
1100
1330
 
1101
1331
  if (fromData) {
1102
1332
  mbtilesFile = mbtilesFile.substr(1, mbtilesFile.length - 2);
@@ -1111,52 +1341,58 @@ export const serve_rendered = {
1111
1341
  }
1112
1342
  }
1113
1343
 
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 += ' | ';
1344
+ queue.push(
1345
+ new Promise((resolve, reject) => {
1346
+ mbtilesFile = path.resolve(options.paths.mbtiles, mbtilesFile);
1347
+ const mbtilesFileStats = fs.statSync(mbtilesFile);
1348
+ if (!mbtilesFileStats.isFile() || mbtilesFileStats.size === 0) {
1349
+ throw Error(`Not valid MBTiles file: ${mbtilesFile}`);
1350
+ }
1351
+ map.sources[name] = new MBTiles(mbtilesFile + '?mode=ro', (err) => {
1352
+ map.sources[name].getInfo((err, info) => {
1353
+ if (err) {
1354
+ console.error(err);
1355
+ return;
1356
+ }
1357
+
1358
+ if (!repoobj.dataProjWGStoInternalWGS && info.proj4) {
1359
+ // how to do this for multiple sources with different proj4 defs?
1360
+ const to3857 = proj4('EPSG:3857');
1361
+ const toDataProj = proj4(info.proj4);
1362
+ repoobj.dataProjWGStoInternalWGS = (xy) =>
1363
+ to3857.inverse(toDataProj.forward(xy));
1364
+ }
1365
+
1366
+ const type = source.type;
1367
+ Object.assign(source, info);
1368
+ source.type = type;
1369
+ source.tiles = [
1370
+ // meta url which will be detected when requested
1371
+ `mbtiles://${name}/{z}/{x}/{y}.${info.format || 'pbf'}`,
1372
+ ];
1373
+ delete source.scheme;
1374
+
1375
+ if (options.dataDecoratorFunc) {
1376
+ source = options.dataDecoratorFunc(name, 'tilejson', source);
1377
+ }
1378
+
1379
+ if (
1380
+ !attributionOverride &&
1381
+ source.attribution &&
1382
+ source.attribution.length > 0
1383
+ ) {
1384
+ if (!tileJSON.attribution.includes(source.attribution)) {
1385
+ if (tileJSON.attribution.length > 0) {
1386
+ tileJSON.attribution += ' | ';
1387
+ }
1388
+ tileJSON.attribution += source.attribution;
1152
1389
  }
1153
- tileJSON.attribution += source.attribution;
1154
1390
  }
1155
- }
1156
- resolve();
1391
+ resolve();
1392
+ });
1157
1393
  });
1158
- });
1159
- }));
1394
+ }),
1395
+ );
1160
1396
  }
1161
1397
  }
1162
1398
 
@@ -1170,7 +1406,12 @@ export const serve_rendered = {
1170
1406
  const minPoolSize = minPoolSizes[i];
1171
1407
  const maxPoolSize = Math.max(minPoolSize, maxPoolSizes[j]);
1172
1408
  map.renderers[s] = createPool(s, 'tile', minPoolSize, maxPoolSize);
1173
- map.renderers_static[s] = createPool(s, 'static', minPoolSize, maxPoolSize);
1409
+ map.renderers_static[s] = createPool(
1410
+ s,
1411
+ 'static',
1412
+ minPoolSize,
1413
+ maxPoolSize,
1414
+ );
1174
1415
  }
1175
1416
  });
1176
1417
 
@@ -1187,5 +1428,5 @@ export const serve_rendered = {
1187
1428
  });
1188
1429
  }
1189
1430
  delete repo[id];
1190
- }
1431
+ },
1191
1432
  };