tileserver-gl-light 4.4.10 → 4.5.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.
@@ -54,7 +54,6 @@ const cachedEmptyResponses = {
54
54
 
55
55
  /**
56
56
  * Create an appropriate mlgl response for http errors.
57
- *
58
57
  * @param {string} format The format (a sharp format or 'pbf').
59
58
  * @param {string} color The background color (or empty string for transparent).
60
59
  * @param {Function} callback The mlgl callback.
@@ -102,7 +101,6 @@ function createEmptyResponse(format, color, callback) {
102
101
  /**
103
102
  * Parses coordinate pair provided to pair of floats and ensures the resulting
104
103
  * pair is a longitude/latitude combination depending on lnglat query parameter.
105
- *
106
104
  * @param {List} coordinatePair Coordinate pair.
107
105
  * @param coordinates
108
106
  * @param {object} query Request query parameters.
@@ -127,7 +125,6 @@ const parseCoordinatePair = (coordinates, query) => {
127
125
 
128
126
  /**
129
127
  * Parses a coordinate pair from query arguments and optionally transforms it.
130
- *
131
128
  * @param {List} coordinatePair Coordinate pair.
132
129
  * @param {object} query Request query parameters.
133
130
  * @param {Function} transformer Optional transform function.
@@ -145,7 +142,6 @@ const parseCoordinates = (coordinatePair, query, transformer) => {
145
142
 
146
143
  /**
147
144
  * Parses paths provided via query into a list of path objects.
148
- *
149
145
  * @param {object} query Request query parameters.
150
146
  * @param {Function} transformer Optional transform function.
151
147
  */
@@ -166,21 +162,12 @@ const extractPathsFromQuery = (query, transformer) => {
166
162
  providedPath.includes('enc:') &&
167
163
  PATH_PATTERN.test(decodeURIComponent(providedPath))
168
164
  ) {
169
- const encodedPaths = providedPath.split(',');
170
- for (const path of encodedPaths) {
171
- const line = path
172
- .split('|')
173
- .filter(
174
- (x) =>
175
- !x.startsWith('fill') &&
176
- !x.startsWith('stroke') &&
177
- !x.startsWith('width'),
178
- )
179
- .join('')
180
- .replace('enc:', '');
181
- const coords = polyline.decode(line).map(([lat, lng]) => [lng, lat]);
182
- paths.push(coords);
183
- }
165
+ // +4 because 'enc:' is 4 characters, everything after 'enc:' is considered to be part of the polyline
166
+ const encIndex = providedPath.indexOf('enc:') + 4;
167
+ const coords = polyline
168
+ .decode(providedPath.substring(encIndex))
169
+ .map(([lat, lng]) => [lng, lat]);
170
+ paths.push(coords);
184
171
  } else {
185
172
  // Iterate through paths, parse and validate them
186
173
  const currentPath = [];
@@ -220,7 +207,6 @@ const extractPathsFromQuery = (query, transformer) => {
220
207
  * on marker object.
221
208
  * Options adhere to the following format
222
209
  * [optionName]:[optionValue]
223
- *
224
210
  * @param {List[String]} optionsList List of option strings.
225
211
  * @param {object} marker Marker object to configure.
226
212
  */
@@ -255,7 +241,6 @@ const parseMarkerOptions = (optionsList, marker) => {
255
241
 
256
242
  /**
257
243
  * Parses markers provided via query into a list of marker objects.
258
- *
259
244
  * @param {object} query Request query parameters.
260
245
  * @param {object} options Configuration options.
261
246
  * @param {Function} transformer Optional transform function.
@@ -335,7 +320,6 @@ const extractMarkersFromQuery = (query, options, transformer) => {
335
320
 
336
321
  /**
337
322
  * Transforms coordinates to pixels.
338
- *
339
323
  * @param {List[Number]} ll Longitude/Latitude coordinate pair.
340
324
  * @param {number} zoom Map zoom level.
341
325
  */
@@ -347,7 +331,6 @@ const precisePx = (ll, zoom) => {
347
331
 
348
332
  /**
349
333
  * Draws a marker in cavans context.
350
- *
351
334
  * @param {object} ctx Canvas context object.
352
335
  * @param {object} marker Marker object parsed by extractMarkersFromQuery.
353
336
  * @param {number} z Map zoom level.
@@ -419,7 +402,6 @@ const drawMarker = (ctx, marker, z) => {
419
402
  * Wraps drawing of markers into list of promises and awaits them.
420
403
  * It's required because images are expected to load asynchronous in canvas js
421
404
  * even when provided from a local disk.
422
- *
423
405
  * @param {object} ctx Canvas context object.
424
406
  * @param {List[Object]} markers Marker objects parsed by extractMarkersFromQuery.
425
407
  * @param {number} z Map zoom level.
@@ -438,117 +420,107 @@ const drawMarkers = async (ctx, markers, z) => {
438
420
 
439
421
  /**
440
422
  * Draws a list of coordinates onto a canvas and styles the resulting path.
441
- *
442
423
  * @param {object} ctx Canvas context object.
443
424
  * @param {List[Number]} path List of coordinates.
444
425
  * @param {object} query Request query parameters.
426
+ * @param {string} pathQuery Path query parameter.
445
427
  * @param {number} z Map zoom level.
446
428
  */
447
- const drawPath = (ctx, path, query, z) => {
448
- const renderPath = (splitPaths) => {
449
- if (!path || path.length < 2) {
450
- return null;
451
- }
429
+ const drawPath = (ctx, path, query, pathQuery, z) => {
430
+ const splitPaths = decodeURIComponent(pathQuery).split('|');
452
431
 
453
- ctx.beginPath();
432
+ if (!path || path.length < 2) {
433
+ return null;
434
+ }
454
435
 
455
- // Transform coordinates to pixel on canvas and draw lines between points
456
- for (const pair of path) {
457
- const px = precisePx(pair, z);
458
- ctx.lineTo(px[0], px[1]);
459
- }
436
+ ctx.beginPath();
460
437
 
461
- // Check if first coordinate matches last coordinate
462
- if (
463
- path[0][0] === path[path.length - 1][0] &&
464
- path[0][1] === path[path.length - 1][1]
465
- ) {
466
- ctx.closePath();
467
- }
438
+ // Transform coordinates to pixel on canvas and draw lines between points
439
+ for (const pair of path) {
440
+ const px = precisePx(pair, z);
441
+ ctx.lineTo(px[0], px[1]);
442
+ }
468
443
 
469
- // Optionally fill drawn shape with a rgba color from query
470
- const pathHasFill =
471
- splitPaths.filter((x) => x.startsWith('fill')).length > 0;
472
- if (query.fill !== undefined || pathHasFill) {
473
- if ('fill' in query) {
474
- ctx.fillStyle = query.fill || 'rgba(255,255,255,0.4)';
475
- }
476
- if (pathHasFill) {
477
- ctx.fillStyle = splitPaths
478
- .find((x) => x.startsWith('fill:'))
479
- .replace('fill:', '');
480
- }
481
- ctx.fill();
482
- }
444
+ // Check if first coordinate matches last coordinate
445
+ if (
446
+ path[0][0] === path[path.length - 1][0] &&
447
+ path[0][1] === path[path.length - 1][1]
448
+ ) {
449
+ ctx.closePath();
450
+ }
483
451
 
484
- // Get line width from query and fall back to 1 if not provided
485
- const pathHasWidth =
486
- splitPaths.filter((x) => x.startsWith('width')).length > 0;
487
- if (query.width !== undefined || pathHasWidth) {
488
- let lineWidth = 1;
489
- // Get line width from query
490
- if ('width' in query) {
491
- lineWidth = Number(query.width);
492
- }
493
- // Get line width from path in query
494
- if (pathHasWidth) {
495
- lineWidth = Number(
496
- splitPaths.find((x) => x.startsWith('width:')).replace('width:', ''),
497
- );
498
- }
499
- // Get border width from query and fall back to 10% of line width
500
- const borderWidth =
501
- query.borderwidth !== undefined
502
- ? parseFloat(query.borderwidth)
503
- : lineWidth * 0.1;
504
-
505
- // Set rendering style for the start and end points of the path
506
- // https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineCap
507
- ctx.lineCap = query.linecap || 'butt';
508
-
509
- // Set rendering style for overlapping segments of the path with differing directions
510
- // https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineJoin
511
- ctx.lineJoin = query.linejoin || 'miter';
512
-
513
- // In order to simulate a border we draw the path two times with the first
514
- // beeing the wider border part.
515
- if (query.border !== undefined && borderWidth > 0) {
516
- // We need to double the desired border width and add it to the line width
517
- // in order to get the desired border on each side of the line.
518
- ctx.lineWidth = lineWidth + borderWidth * 2;
519
- // Set border style as rgba
520
- ctx.strokeStyle = query.border;
521
- ctx.stroke();
522
- }
523
- ctx.lineWidth = lineWidth;
452
+ // Optionally fill drawn shape with a rgba color from query
453
+ const pathHasFill = splitPaths.filter((x) => x.startsWith('fill')).length > 0;
454
+ if (query.fill !== undefined || pathHasFill) {
455
+ if ('fill' in query) {
456
+ ctx.fillStyle = query.fill || 'rgba(255,255,255,0.4)';
524
457
  }
458
+ if (pathHasFill) {
459
+ ctx.fillStyle = splitPaths
460
+ .find((x) => x.startsWith('fill:'))
461
+ .replace('fill:', '');
462
+ }
463
+ ctx.fill();
464
+ }
525
465
 
526
- const pathHasStroke =
527
- splitPaths.filter((x) => x.startsWith('stroke')).length > 0;
528
- if (query.stroke !== undefined || pathHasStroke) {
529
- if ('stroke' in query) {
530
- ctx.strokeStyle = query.stroke;
531
- }
532
- // Path Width gets higher priority
533
- if (pathHasWidth) {
534
- ctx.strokeStyle = splitPaths
535
- .find((x) => x.startsWith('stroke:'))
536
- .replace('stroke:', '');
537
- }
538
- } else {
539
- ctx.strokeStyle = 'rgba(0,64,255,0.7)';
466
+ // Get line width from query and fall back to 1 if not provided
467
+ const pathHasWidth =
468
+ splitPaths.filter((x) => x.startsWith('width')).length > 0;
469
+ if (query.width !== undefined || pathHasWidth) {
470
+ let lineWidth = 1;
471
+ // Get line width from query
472
+ if ('width' in query) {
473
+ lineWidth = Number(query.width);
540
474
  }
541
- ctx.stroke();
542
- };
475
+ // Get line width from path in query
476
+ if (pathHasWidth) {
477
+ lineWidth = Number(
478
+ splitPaths.find((x) => x.startsWith('width:')).replace('width:', ''),
479
+ );
480
+ }
481
+ // Get border width from query and fall back to 10% of line width
482
+ const borderWidth =
483
+ query.borderwidth !== undefined
484
+ ? parseFloat(query.borderwidth)
485
+ : lineWidth * 0.1;
486
+
487
+ // Set rendering style for the start and end points of the path
488
+ // https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineCap
489
+ ctx.lineCap = query.linecap || 'butt';
490
+
491
+ // Set rendering style for overlapping segments of the path with differing directions
492
+ // https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineJoin
493
+ ctx.lineJoin = query.linejoin || 'miter';
494
+
495
+ // In order to simulate a border we draw the path two times with the first
496
+ // beeing the wider border part.
497
+ if (query.border !== undefined && borderWidth > 0) {
498
+ // We need to double the desired border width and add it to the line width
499
+ // in order to get the desired border on each side of the line.
500
+ ctx.lineWidth = lineWidth + borderWidth * 2;
501
+ // Set border style as rgba
502
+ ctx.strokeStyle = query.border;
503
+ ctx.stroke();
504
+ }
505
+ ctx.lineWidth = lineWidth;
506
+ }
543
507
 
544
- // Check if path in query is valid
545
- if (Array.isArray(query.path)) {
546
- for (let i = 0; i < query.path.length; i += 1) {
547
- renderPath(decodeURIComponent(query.path.at(i)).split('|'));
508
+ const pathHasStroke =
509
+ splitPaths.filter((x) => x.startsWith('stroke')).length > 0;
510
+ if (query.stroke !== undefined || pathHasStroke) {
511
+ if ('stroke' in query) {
512
+ ctx.strokeStyle = query.stroke;
513
+ }
514
+ // Path Stroke gets higher priority
515
+ if (pathHasStroke) {
516
+ ctx.strokeStyle = splitPaths
517
+ .find((x) => x.startsWith('stroke:'))
518
+ .replace('stroke:', '');
548
519
  }
549
520
  } else {
550
- renderPath(decodeURIComponent(query.path).split('|'));
521
+ ctx.strokeStyle = 'rgba(0,64,255,0.7)';
551
522
  }
523
+ ctx.stroke();
552
524
  };
553
525
 
554
526
  const renderOverlay = async (
@@ -592,9 +564,10 @@ const renderOverlay = async (
592
564
  }
593
565
 
594
566
  // Draw provided paths if any
595
- for (const path of paths) {
596
- drawPath(ctx, path, query, z);
597
- }
567
+ paths.forEach((path, i) => {
568
+ const pathQuery = Array.isArray(query.path) ? query.path.at(i) : query.path;
569
+ drawPath(ctx, path, query, pathQuery, z);
570
+ });
598
571
 
599
572
  // Await drawing of markers before rendering the canvas
600
573
  await drawMarkers(ctx, markers, z);
@@ -780,8 +753,9 @@ export const serve_rendered = {
780
753
  image.resize(width * scale, height * scale);
781
754
  }
782
755
 
756
+ var composite_array = [];
783
757
  if (opt_overlay) {
784
- image.composite([{ input: opt_overlay }]);
758
+ composite_array.push({ input: opt_overlay });
785
759
  }
786
760
  if (item.watermark) {
787
761
  const canvas = createCanvas(scale * width, scale * height);
@@ -794,7 +768,11 @@ export const serve_rendered = {
794
768
  ctx.fillStyle = 'rgba(0,0,0,.4)';
795
769
  ctx.fillText(item.watermark, 5, height - 5);
796
770
 
797
- image.composite([{ input: canvas.toBuffer() }]);
771
+ composite_array.push({ input: canvas.toBuffer() });
772
+ }
773
+
774
+ if (composite_array.length > 0) {
775
+ image.composite(composite_array);
798
776
  }
799
777
 
800
778
  const formatQuality = (options.formatQuality || {})[format];
@@ -892,35 +870,118 @@ export const serve_rendered = {
892
870
  app.get(
893
871
  util.format(staticPattern, centerPattern),
894
872
  async (req, res, next) => {
873
+ try {
874
+ const item = repo[req.params.id];
875
+ if (!item) {
876
+ return res.sendStatus(404);
877
+ }
878
+ const raw = req.params.raw;
879
+ const z = +req.params.z;
880
+ let x = +req.params.x;
881
+ let y = +req.params.y;
882
+ const bearing = +(req.params.bearing || '0');
883
+ const pitch = +(req.params.pitch || '0');
884
+ const w = req.params.width | 0;
885
+ const h = req.params.height | 0;
886
+ const scale = getScale(req.params.scale);
887
+ const format = req.params.format;
888
+
889
+ if (z < 0) {
890
+ return res.status(404).send('Invalid zoom');
891
+ }
892
+
893
+ const transformer = raw
894
+ ? mercator.inverse.bind(mercator)
895
+ : item.dataProjWGStoInternalWGS;
896
+
897
+ if (transformer) {
898
+ const ll = transformer([x, y]);
899
+ x = ll[0];
900
+ y = ll[1];
901
+ }
902
+
903
+ const paths = extractPathsFromQuery(req.query, transformer);
904
+ const markers = extractMarkersFromQuery(
905
+ req.query,
906
+ options,
907
+ transformer,
908
+ );
909
+ const overlay = await renderOverlay(
910
+ z,
911
+ x,
912
+ y,
913
+ bearing,
914
+ pitch,
915
+ w,
916
+ h,
917
+ scale,
918
+ paths,
919
+ markers,
920
+ req.query,
921
+ );
922
+
923
+ return respondImage(
924
+ item,
925
+ z,
926
+ x,
927
+ y,
928
+ bearing,
929
+ pitch,
930
+ w,
931
+ h,
932
+ scale,
933
+ format,
934
+ res,
935
+ next,
936
+ overlay,
937
+ 'static',
938
+ );
939
+ } catch (e) {
940
+ next(e);
941
+ }
942
+ },
943
+ );
944
+
945
+ const serveBounds = async (req, res, next) => {
946
+ try {
895
947
  const item = repo[req.params.id];
896
948
  if (!item) {
897
949
  return res.sendStatus(404);
898
950
  }
899
951
  const raw = req.params.raw;
900
- const z = +req.params.z;
901
- let x = +req.params.x;
902
- let y = +req.params.y;
903
- const bearing = +(req.params.bearing || '0');
904
- const pitch = +(req.params.pitch || '0');
905
- const w = req.params.width | 0;
906
- const h = req.params.height | 0;
907
- const scale = getScale(req.params.scale);
908
- const format = req.params.format;
909
-
910
- if (z < 0) {
911
- return res.status(404).send('Invalid zoom');
912
- }
952
+ const bbox = [
953
+ +req.params.minx,
954
+ +req.params.miny,
955
+ +req.params.maxx,
956
+ +req.params.maxy,
957
+ ];
958
+ let center = [(bbox[0] + bbox[2]) / 2, (bbox[1] + bbox[3]) / 2];
913
959
 
914
960
  const transformer = raw
915
961
  ? mercator.inverse.bind(mercator)
916
962
  : item.dataProjWGStoInternalWGS;
917
963
 
918
964
  if (transformer) {
919
- const ll = transformer([x, y]);
920
- x = ll[0];
921
- y = ll[1];
965
+ const minCorner = transformer(bbox.slice(0, 2));
966
+ const maxCorner = transformer(bbox.slice(2));
967
+ bbox[0] = minCorner[0];
968
+ bbox[1] = minCorner[1];
969
+ bbox[2] = maxCorner[0];
970
+ bbox[3] = maxCorner[1];
971
+ center = transformer(center);
922
972
  }
923
973
 
974
+ const w = req.params.width | 0;
975
+ const h = req.params.height | 0;
976
+ const scale = getScale(req.params.scale);
977
+ const format = req.params.format;
978
+
979
+ const z = calcZForBBox(bbox, w, h, req.query);
980
+ const x = center[0];
981
+ const y = center[1];
982
+ const bearing = 0;
983
+ const pitch = 0;
984
+
924
985
  const paths = extractPathsFromQuery(req.query, transformer);
925
986
  const markers = extractMarkersFromQuery(
926
987
  req.query,
@@ -940,7 +1001,6 @@ export const serve_rendered = {
940
1001
  markers,
941
1002
  req.query,
942
1003
  );
943
-
944
1004
  return respondImage(
945
1005
  item,
946
1006
  z,
@@ -957,83 +1017,9 @@ export const serve_rendered = {
957
1017
  overlay,
958
1018
  'static',
959
1019
  );
960
- },
961
- );
962
-
963
- const serveBounds = async (req, res, next) => {
964
- const item = repo[req.params.id];
965
- if (!item) {
966
- return res.sendStatus(404);
1020
+ } catch (e) {
1021
+ next(e);
967
1022
  }
968
- const raw = req.params.raw;
969
- const bbox = [
970
- +req.params.minx,
971
- +req.params.miny,
972
- +req.params.maxx,
973
- +req.params.maxy,
974
- ];
975
- let center = [(bbox[0] + bbox[2]) / 2, (bbox[1] + bbox[3]) / 2];
976
-
977
- const transformer = raw
978
- ? mercator.inverse.bind(mercator)
979
- : item.dataProjWGStoInternalWGS;
980
-
981
- if (transformer) {
982
- const minCorner = transformer(bbox.slice(0, 2));
983
- const maxCorner = transformer(bbox.slice(2));
984
- bbox[0] = minCorner[0];
985
- bbox[1] = minCorner[1];
986
- bbox[2] = maxCorner[0];
987
- bbox[3] = maxCorner[1];
988
- center = transformer(center);
989
- }
990
-
991
- const w = req.params.width | 0;
992
- const h = req.params.height | 0;
993
- const scale = getScale(req.params.scale);
994
- const format = req.params.format;
995
-
996
- const z = calcZForBBox(bbox, w, h, req.query);
997
- const x = center[0];
998
- const y = center[1];
999
- const bearing = 0;
1000
- const pitch = 0;
1001
-
1002
- const paths = extractPathsFromQuery(req.query, transformer);
1003
- const markers = extractMarkersFromQuery(
1004
- req.query,
1005
- options,
1006
- transformer,
1007
- );
1008
- const overlay = await renderOverlay(
1009
- z,
1010
- x,
1011
- y,
1012
- bearing,
1013
- pitch,
1014
- w,
1015
- h,
1016
- scale,
1017
- paths,
1018
- markers,
1019
- req.query,
1020
- );
1021
- return respondImage(
1022
- item,
1023
- z,
1024
- x,
1025
- y,
1026
- bearing,
1027
- pitch,
1028
- w,
1029
- h,
1030
- scale,
1031
- format,
1032
- res,
1033
- next,
1034
- overlay,
1035
- 'static',
1036
- );
1037
1023
  };
1038
1024
 
1039
1025
  const boundsPattern = util.format(
@@ -1073,97 +1059,101 @@ export const serve_rendered = {
1073
1059
  app.get(
1074
1060
  util.format(staticPattern, autoPattern),
1075
1061
  async (req, res, next) => {
1076
- const item = repo[req.params.id];
1077
- if (!item) {
1078
- return res.sendStatus(404);
1079
- }
1080
- const raw = req.params.raw;
1081
- const w = req.params.width | 0;
1082
- const h = req.params.height | 0;
1083
- const bearing = 0;
1084
- const pitch = 0;
1085
- const scale = getScale(req.params.scale);
1086
- const format = req.params.format;
1062
+ try {
1063
+ const item = repo[req.params.id];
1064
+ if (!item) {
1065
+ return res.sendStatus(404);
1066
+ }
1067
+ const raw = req.params.raw;
1068
+ const w = req.params.width | 0;
1069
+ const h = req.params.height | 0;
1070
+ const bearing = 0;
1071
+ const pitch = 0;
1072
+ const scale = getScale(req.params.scale);
1073
+ const format = req.params.format;
1074
+
1075
+ const transformer = raw
1076
+ ? mercator.inverse.bind(mercator)
1077
+ : item.dataProjWGStoInternalWGS;
1078
+
1079
+ const paths = extractPathsFromQuery(req.query, transformer);
1080
+ const markers = extractMarkersFromQuery(
1081
+ req.query,
1082
+ options,
1083
+ transformer,
1084
+ );
1087
1085
 
1088
- const transformer = raw
1089
- ? mercator.inverse.bind(mercator)
1090
- : item.dataProjWGStoInternalWGS;
1086
+ // Extract coordinates from markers
1087
+ const markerCoordinates = [];
1088
+ for (const marker of markers) {
1089
+ markerCoordinates.push(marker.location);
1090
+ }
1091
1091
 
1092
- const paths = extractPathsFromQuery(req.query, transformer);
1093
- const markers = extractMarkersFromQuery(
1094
- req.query,
1095
- options,
1096
- transformer,
1097
- );
1092
+ // Create array with coordinates from markers and path
1093
+ const coords = [].concat(paths.flat()).concat(markerCoordinates);
1098
1094
 
1099
- // Extract coordinates from markers
1100
- const markerCoordinates = [];
1101
- for (const marker of markers) {
1102
- markerCoordinates.push(marker.location);
1103
- }
1095
+ // Check if we have at least one coordinate to calculate a bounding box
1096
+ if (coords.length < 1) {
1097
+ return res.status(400).send('No coordinates provided');
1098
+ }
1104
1099
 
1105
- // Create array with coordinates from markers and path
1106
- const coords = [].concat(paths.flat()).concat(markerCoordinates);
1100
+ const bbox = [Infinity, Infinity, -Infinity, -Infinity];
1101
+ for (const pair of coords) {
1102
+ bbox[0] = Math.min(bbox[0], pair[0]);
1103
+ bbox[1] = Math.min(bbox[1], pair[1]);
1104
+ bbox[2] = Math.max(bbox[2], pair[0]);
1105
+ bbox[3] = Math.max(bbox[3], pair[1]);
1106
+ }
1107
1107
 
1108
- // Check if we have at least one coordinate to calculate a bounding box
1109
- if (coords.length < 1) {
1110
- return res.status(400).send('No coordinates provided');
1111
- }
1108
+ const bbox_ = mercator.convert(bbox, '900913');
1109
+ const center = mercator.inverse([
1110
+ (bbox_[0] + bbox_[2]) / 2,
1111
+ (bbox_[1] + bbox_[3]) / 2,
1112
+ ]);
1113
+
1114
+ // Calculate zoom level
1115
+ const maxZoom = parseFloat(req.query.maxzoom);
1116
+ let z = calcZForBBox(bbox, w, h, req.query);
1117
+ if (maxZoom > 0) {
1118
+ z = Math.min(z, maxZoom);
1119
+ }
1112
1120
 
1113
- const bbox = [Infinity, Infinity, -Infinity, -Infinity];
1114
- for (const pair of coords) {
1115
- bbox[0] = Math.min(bbox[0], pair[0]);
1116
- bbox[1] = Math.min(bbox[1], pair[1]);
1117
- bbox[2] = Math.max(bbox[2], pair[0]);
1118
- bbox[3] = Math.max(bbox[3], pair[1]);
1119
- }
1121
+ const x = center[0];
1122
+ const y = center[1];
1123
+
1124
+ const overlay = await renderOverlay(
1125
+ z,
1126
+ x,
1127
+ y,
1128
+ bearing,
1129
+ pitch,
1130
+ w,
1131
+ h,
1132
+ scale,
1133
+ paths,
1134
+ markers,
1135
+ req.query,
1136
+ );
1120
1137
 
1121
- const bbox_ = mercator.convert(bbox, '900913');
1122
- const center = mercator.inverse([
1123
- (bbox_[0] + bbox_[2]) / 2,
1124
- (bbox_[1] + bbox_[3]) / 2,
1125
- ]);
1126
-
1127
- // Calculate zoom level
1128
- const maxZoom = parseFloat(req.query.maxzoom);
1129
- let z = calcZForBBox(bbox, w, h, req.query);
1130
- if (maxZoom > 0) {
1131
- z = Math.min(z, maxZoom);
1138
+ return respondImage(
1139
+ item,
1140
+ z,
1141
+ x,
1142
+ y,
1143
+ bearing,
1144
+ pitch,
1145
+ w,
1146
+ h,
1147
+ scale,
1148
+ format,
1149
+ res,
1150
+ next,
1151
+ overlay,
1152
+ 'static',
1153
+ );
1154
+ } catch (e) {
1155
+ next(e);
1132
1156
  }
1133
-
1134
- const x = center[0];
1135
- const y = center[1];
1136
-
1137
- const overlay = await renderOverlay(
1138
- z,
1139
- x,
1140
- y,
1141
- bearing,
1142
- pitch,
1143
- w,
1144
- h,
1145
- scale,
1146
- paths,
1147
- markers,
1148
- req.query,
1149
- );
1150
-
1151
- return respondImage(
1152
- item,
1153
- z,
1154
- x,
1155
- y,
1156
- bearing,
1157
- pitch,
1158
- w,
1159
- h,
1160
- scale,
1161
- format,
1162
- res,
1163
- next,
1164
- overlay,
1165
- 'static',
1166
- );
1167
1157
  },
1168
1158
  );
1169
1159
  }
@@ -1374,6 +1364,9 @@ export const serve_rendered = {
1374
1364
  type: 'baselayer',
1375
1365
  };
1376
1366
  const attributionOverride = params.tilejson && params.tilejson.attribution;
1367
+ if (styleJSON.center && styleJSON.zoom) {
1368
+ tileJSON.center = styleJSON.center.concat(Math.round(styleJSON.zoom));
1369
+ }
1377
1370
  Object.assign(tileJSON, params.tilejson || {});
1378
1371
  tileJSON.tiles = params.domains || options.domains;
1379
1372
  fixTileJSONCenter(tileJSON);