tileserver-gl 4.5.0 → 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.
@@ -13,11 +13,11 @@ jobs:
13
13
  steps:
14
14
  - name: Check out repository ✨ (non-dependabot)
15
15
  if: ${{ github.actor != 'dependabot[bot]' }}
16
- uses: actions/checkout@v3
16
+ uses: actions/checkout@v4
17
17
 
18
18
  - name: Check out repository 🎉 (dependabot)
19
19
  if: ${{ github.actor == 'dependabot[bot]' }}
20
- uses: actions/checkout@v3
20
+ uses: actions/checkout@v4
21
21
  with:
22
22
  ref: ${{ github.event.pull_request.head.sha }}
23
23
 
@@ -23,7 +23,7 @@ jobs:
23
23
  language: [javascript]
24
24
  steps:
25
25
  - name: Checkout
26
- uses: actions/checkout@v3
26
+ uses: actions/checkout@v4
27
27
  - name: Initialize CodeQL
28
28
  uses: github/codeql-action/init@v2
29
29
  with:
@@ -13,11 +13,11 @@ jobs:
13
13
  steps:
14
14
  - name: Check out repository ✨ (non-dependabot)
15
15
  if: ${{ github.actor != 'dependabot[bot]' }}
16
- uses: actions/checkout@v3
16
+ uses: actions/checkout@v4
17
17
 
18
18
  - name: Check out repository 🎉 (dependabot)
19
19
  if: ${{ github.actor == 'dependabot[bot]' }}
20
- uses: actions/checkout@v3
20
+ uses: actions/checkout@v4
21
21
  with:
22
22
  ref: ${{ github.event.pull_request.head.sha }}
23
23
 
@@ -19,7 +19,7 @@ jobs:
19
19
  runs-on: ubuntu-20.04
20
20
  steps:
21
21
  - name: Check out repository ✨
22
- uses: actions/checkout@v3
22
+ uses: actions/checkout@v4
23
23
 
24
24
  - name: Update apt-get 🚀
25
25
  run: sudo apt-get update -qq
@@ -21,7 +21,7 @@ Endpoint data can be configured to be cached by Cloudflare. For example to cache
21
21
 
22
22
  Create a rule which matches ``hostname (equal)`` and ``URI Path (ends with)`` for ``.pbf`` and ``.json`` fields. Set cache status to eligible for cache to enable the caching and overwrite the ``Edge TTL`` with ``Browser TTL`` to be 7 days (depends on your application usage).
23
23
 
24
- This will ensure that Cloudflare will cache your tiles on Cloudflare side for seven days aswell on the client side. If the tileserver is down or user has no internet access it will try to use cached tiles from Cloudflare or local.
24
+ This will ensure that your tiles are cached on the client side and by Cloudflare for seven days. If the tileserver is down or user has no internet access it will try to use cached tiles.
25
25
 
26
26
  Note that ``Browser TTL`` will overwrite expiration dates on the client device. If you rebuild your maps, old tiles will be rendered until it expires or cache is cleared on the client device.
27
27
 
@@ -115,7 +115,7 @@ An example nginx reverse proxy server configuration for HTTPS connections. It en
115
115
 
116
116
  location / {
117
117
  # This include directive sets up required headers for proxy and proxy cache.
118
- # Aswell it includes the required ``X-Forwarded-*`` headers for tileserver to propely generate tiles.
118
+ # As well it includes the required ``X-Forwarded-*`` headers for tileserver to properly generate tiles.
119
119
  include proxy_params;
120
120
 
121
121
  proxy_pass http://127.0.0.1:8080/;
@@ -35,13 +35,14 @@ Static images
35
35
 
36
36
  * All the static image endpoints additionally support following query parameters:
37
37
 
38
- * ``path`` - comma-separated ``lng,lat``, pipe-separated pairs
39
-
40
- * e.g. ``5.9,45.8|5.9,47.8|10.5,47.8|10.5,45.8|5.9,45.8``
41
- * can be provided multiple times
42
- * or pass the path as per [Maptiler Cloud API](https://docs.maptiler.com/cloud/api/static-maps/)
43
- * Match pattern: ((fill|stroke|width)\:[^\|]+\|)*((enc:.+)|((-?\d+\.?\d*,-?\d+\.?\d*\|)+(-?\d+\.?\d*,-?\d+\.?\d*)))
44
-
38
+ * ``path``
39
+ * can be provided multiple times
40
+ * syntax
41
+ * comma-separated ``lng,lat``, pipe-separated pairs
42
+ * e.g. ``path=5.9,45.8|5.9,47.8|10.5,47.8|10.5,45.8|5.9,45.8``
43
+ * Match pattern with options ``((fill|stroke|width)\:[^\|]+\|)*((enc:.+)|((-?\d+\.?\d*,-?\d+\.?\d*\|)+(-?\d+\.?\d*,-?\d+\.?\d*)))``
44
+ * e.g. ``path=stroke:yellow|width:2|fill:green|5.9,45.8|5.9,47.8|10.5,47.8|10.5,45.8|5.9,45.8`` or ``stroke:blue|enc:_p~iF~ps|U_ulLnnqC_mqNvxq`@``
45
+ * 'enc:' is specified in `Google Encoded Polyline Format <https://developers.google.com/maps/documentation/utilities/polylinealgorithm>`. If used, the rest of the path parameter is considered to be part of the encoded polyline string -- do not specify the coordinate pairs.
45
46
  * ``latlng`` - indicates coordinates are in ``lat,lng`` order rather than the usual ``lng,lat``
46
47
  * ``fill`` - color to use as the fill (e.g. ``red``, ``rgba(255,255,255,0.5)``, ``#0000ff``)
47
48
  * ``stroke`` - color of the path stroke
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tileserver-gl",
3
- "version": "4.5.0",
3
+ "version": "4.5.1",
4
4
  "description": "Map tile server for JSON GL styles - vector and server side generated raster tiles",
5
5
  "main": "src/main.js",
6
6
  "bin": "src/main.js",
@@ -20,7 +20,7 @@
20
20
  "dependencies": {
21
21
  "@mapbox/glyph-pbf-composite": "0.0.3",
22
22
  "@mapbox/mbtiles": "0.12.1",
23
- "@mapbox/polyline": "^1.2.0",
23
+ "@mapbox/polyline": "^1.2.1",
24
24
  "@mapbox/sphericalmercator": "1.2.0",
25
25
  "@mapbox/vector-tile": "1.3.1",
26
26
  "@maplibre/maplibre-gl-native": "5.2.0",
@@ -40,7 +40,7 @@
40
40
  "proj4": "2.9.0",
41
41
  "request": "2.88.2",
42
42
  "sanitize-filename": "1.6.3",
43
- "sharp": "0.32.4",
43
+ "sharp": "0.32.6",
44
44
  "tileserver-gl-styles": "2.0.0"
45
45
  },
46
46
  "devDependencies": {
@@ -48,14 +48,14 @@
48
48
  "@commitlint/config-conventional": "^17.7.0",
49
49
  "@typescript-eslint/eslint-plugin": "^5.61.0",
50
50
  "@typescript-eslint/parser": "^5.62.0",
51
- "chai": "4.3.7",
52
- "eslint": "^8.47.0",
51
+ "chai": "4.3.8",
52
+ "eslint": "^8.50.0",
53
53
  "eslint-config-prettier": "^9.0.0",
54
- "eslint-plugin-jsdoc": "^46.4.6",
54
+ "eslint-plugin-jsdoc": "^46.8.2",
55
55
  "eslint-plugin-prettier": "^4.2.1",
56
56
  "eslint-plugin-security": "^1.7.1",
57
57
  "husky": "^8.0.3",
58
- "lint-staged": "^13.2.3",
58
+ "lint-staged": "^14.0.1",
59
59
  "mocha": "^10.2.0",
60
60
  "prettier": "^2.8.8",
61
61
  "should": "^13.2.3",
@@ -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);
@@ -897,35 +870,118 @@ export const serve_rendered = {
897
870
  app.get(
898
871
  util.format(staticPattern, centerPattern),
899
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 {
900
947
  const item = repo[req.params.id];
901
948
  if (!item) {
902
949
  return res.sendStatus(404);
903
950
  }
904
951
  const raw = req.params.raw;
905
- const z = +req.params.z;
906
- let x = +req.params.x;
907
- let y = +req.params.y;
908
- const bearing = +(req.params.bearing || '0');
909
- const pitch = +(req.params.pitch || '0');
910
- const w = req.params.width | 0;
911
- const h = req.params.height | 0;
912
- const scale = getScale(req.params.scale);
913
- const format = req.params.format;
914
-
915
- if (z < 0) {
916
- return res.status(404).send('Invalid zoom');
917
- }
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];
918
959
 
919
960
  const transformer = raw
920
961
  ? mercator.inverse.bind(mercator)
921
962
  : item.dataProjWGStoInternalWGS;
922
963
 
923
964
  if (transformer) {
924
- const ll = transformer([x, y]);
925
- x = ll[0];
926
- 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);
927
972
  }
928
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
+
929
985
  const paths = extractPathsFromQuery(req.query, transformer);
930
986
  const markers = extractMarkersFromQuery(
931
987
  req.query,
@@ -945,7 +1001,6 @@ export const serve_rendered = {
945
1001
  markers,
946
1002
  req.query,
947
1003
  );
948
-
949
1004
  return respondImage(
950
1005
  item,
951
1006
  z,
@@ -962,83 +1017,9 @@ export const serve_rendered = {
962
1017
  overlay,
963
1018
  'static',
964
1019
  );
965
- },
966
- );
967
-
968
- const serveBounds = async (req, res, next) => {
969
- const item = repo[req.params.id];
970
- if (!item) {
971
- return res.sendStatus(404);
1020
+ } catch (e) {
1021
+ next(e);
972
1022
  }
973
- const raw = req.params.raw;
974
- const bbox = [
975
- +req.params.minx,
976
- +req.params.miny,
977
- +req.params.maxx,
978
- +req.params.maxy,
979
- ];
980
- let center = [(bbox[0] + bbox[2]) / 2, (bbox[1] + bbox[3]) / 2];
981
-
982
- const transformer = raw
983
- ? mercator.inverse.bind(mercator)
984
- : item.dataProjWGStoInternalWGS;
985
-
986
- if (transformer) {
987
- const minCorner = transformer(bbox.slice(0, 2));
988
- const maxCorner = transformer(bbox.slice(2));
989
- bbox[0] = minCorner[0];
990
- bbox[1] = minCorner[1];
991
- bbox[2] = maxCorner[0];
992
- bbox[3] = maxCorner[1];
993
- center = transformer(center);
994
- }
995
-
996
- const w = req.params.width | 0;
997
- const h = req.params.height | 0;
998
- const scale = getScale(req.params.scale);
999
- const format = req.params.format;
1000
-
1001
- const z = calcZForBBox(bbox, w, h, req.query);
1002
- const x = center[0];
1003
- const y = center[1];
1004
- const bearing = 0;
1005
- const pitch = 0;
1006
-
1007
- const paths = extractPathsFromQuery(req.query, transformer);
1008
- const markers = extractMarkersFromQuery(
1009
- req.query,
1010
- options,
1011
- transformer,
1012
- );
1013
- const overlay = await renderOverlay(
1014
- z,
1015
- x,
1016
- y,
1017
- bearing,
1018
- pitch,
1019
- w,
1020
- h,
1021
- scale,
1022
- paths,
1023
- markers,
1024
- req.query,
1025
- );
1026
- return respondImage(
1027
- item,
1028
- z,
1029
- x,
1030
- y,
1031
- bearing,
1032
- pitch,
1033
- w,
1034
- h,
1035
- scale,
1036
- format,
1037
- res,
1038
- next,
1039
- overlay,
1040
- 'static',
1041
- );
1042
1023
  };
1043
1024
 
1044
1025
  const boundsPattern = util.format(
@@ -1078,97 +1059,101 @@ export const serve_rendered = {
1078
1059
  app.get(
1079
1060
  util.format(staticPattern, autoPattern),
1080
1061
  async (req, res, next) => {
1081
- const item = repo[req.params.id];
1082
- if (!item) {
1083
- return res.sendStatus(404);
1084
- }
1085
- const raw = req.params.raw;
1086
- const w = req.params.width | 0;
1087
- const h = req.params.height | 0;
1088
- const bearing = 0;
1089
- const pitch = 0;
1090
- const scale = getScale(req.params.scale);
1091
- 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
+ );
1092
1085
 
1093
- const transformer = raw
1094
- ? mercator.inverse.bind(mercator)
1095
- : item.dataProjWGStoInternalWGS;
1086
+ // Extract coordinates from markers
1087
+ const markerCoordinates = [];
1088
+ for (const marker of markers) {
1089
+ markerCoordinates.push(marker.location);
1090
+ }
1096
1091
 
1097
- const paths = extractPathsFromQuery(req.query, transformer);
1098
- const markers = extractMarkersFromQuery(
1099
- req.query,
1100
- options,
1101
- transformer,
1102
- );
1092
+ // Create array with coordinates from markers and path
1093
+ const coords = [].concat(paths.flat()).concat(markerCoordinates);
1103
1094
 
1104
- // Extract coordinates from markers
1105
- const markerCoordinates = [];
1106
- for (const marker of markers) {
1107
- markerCoordinates.push(marker.location);
1108
- }
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
+ }
1109
1099
 
1110
- // Create array with coordinates from markers and path
1111
- 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
+ }
1112
1107
 
1113
- // Check if we have at least one coordinate to calculate a bounding box
1114
- if (coords.length < 1) {
1115
- return res.status(400).send('No coordinates provided');
1116
- }
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
+ }
1117
1120
 
1118
- const bbox = [Infinity, Infinity, -Infinity, -Infinity];
1119
- for (const pair of coords) {
1120
- bbox[0] = Math.min(bbox[0], pair[0]);
1121
- bbox[1] = Math.min(bbox[1], pair[1]);
1122
- bbox[2] = Math.max(bbox[2], pair[0]);
1123
- bbox[3] = Math.max(bbox[3], pair[1]);
1124
- }
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
+ );
1125
1137
 
1126
- const bbox_ = mercator.convert(bbox, '900913');
1127
- const center = mercator.inverse([
1128
- (bbox_[0] + bbox_[2]) / 2,
1129
- (bbox_[1] + bbox_[3]) / 2,
1130
- ]);
1131
-
1132
- // Calculate zoom level
1133
- const maxZoom = parseFloat(req.query.maxzoom);
1134
- let z = calcZForBBox(bbox, w, h, req.query);
1135
- if (maxZoom > 0) {
1136
- 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);
1137
1156
  }
1138
-
1139
- const x = center[0];
1140
- const y = center[1];
1141
-
1142
- const overlay = await renderOverlay(
1143
- z,
1144
- x,
1145
- y,
1146
- bearing,
1147
- pitch,
1148
- w,
1149
- h,
1150
- scale,
1151
- paths,
1152
- markers,
1153
- req.query,
1154
- );
1155
-
1156
- return respondImage(
1157
- item,
1158
- z,
1159
- x,
1160
- y,
1161
- bearing,
1162
- pitch,
1163
- w,
1164
- h,
1165
- scale,
1166
- format,
1167
- res,
1168
- next,
1169
- overlay,
1170
- 'static',
1171
- );
1172
1157
  },
1173
1158
  );
1174
1159
  }
package/src/server.js CHANGED
@@ -114,7 +114,6 @@ function start(opts) {
114
114
  /**
115
115
  * Recursively get all files within a directory.
116
116
  * Inspired by https://stackoverflow.com/a/45130990/10133863
117
- *
118
117
  * @param {string} directory Absolute path to a directory to get files from.
119
118
  */
120
119
  const getFiles = async (directory) => {
@@ -592,7 +591,6 @@ function start(opts) {
592
591
 
593
592
  /**
594
593
  * Stop the server gracefully
595
- *
596
594
  * @param {string} signal Name of the received signal
597
595
  */
598
596
  function stopGracefully(signal) {
package/src/utils.js CHANGED
@@ -8,7 +8,6 @@ import glyphCompose from '@mapbox/glyph-pbf-composite';
8
8
 
9
9
  /**
10
10
  * Generate new URL object
11
- *
12
11
  * @param req
13
12
  * @params {object} req - Express request
14
13
  * @returns {URL} object
package/test/static.js CHANGED
@@ -171,6 +171,18 @@ describe('Static endpoints', function () {
171
171
  '?path=-10,-10|-20,-20',
172
172
  );
173
173
  });
174
+
175
+ describe('encoded path', function () {
176
+ testStatic(
177
+ prefix,
178
+ 'auto/20x20',
179
+ 'png',
180
+ 200,
181
+ 2,
182
+ /image\/png/,
183
+ '?path=' + decodeURIComponent('enc:{{biGwvyGoUi@s_A|{@'),
184
+ );
185
+ });
174
186
  });
175
187
 
176
188
  describe('invalid requests return 4xx', function () {