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.
- package/.github/workflows/ci.yml +2 -2
- package/.github/workflows/codeql.yml +1 -1
- package/.github/workflows/ct.yml +2 -2
- package/.github/workflows/release.yml +1 -1
- package/docs/deployment.rst +2 -2
- package/docs/endpoints.rst +8 -7
- package/package.json +7 -7
- package/src/serve_rendered.js +282 -297
- package/src/server.js +0 -2
- package/src/utils.js +0 -1
- package/test/static.js +12 -0
package/.github/workflows/ci.yml
CHANGED
|
@@ -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@
|
|
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@
|
|
20
|
+
uses: actions/checkout@v4
|
|
21
21
|
with:
|
|
22
22
|
ref: ${{ github.event.pull_request.head.sha }}
|
|
23
23
|
|
package/.github/workflows/ct.yml
CHANGED
|
@@ -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@
|
|
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@
|
|
20
|
+
uses: actions/checkout@v4
|
|
21
21
|
with:
|
|
22
22
|
ref: ${{ github.event.pull_request.head.sha }}
|
|
23
23
|
|
package/docs/deployment.rst
CHANGED
|
@@ -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
|
|
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
|
-
#
|
|
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/;
|
package/docs/endpoints.rst
CHANGED
|
@@ -35,13 +35,14 @@ Static images
|
|
|
35
35
|
|
|
36
36
|
* All the static image endpoints additionally support following query parameters:
|
|
37
37
|
|
|
38
|
-
* ``path``
|
|
39
|
-
|
|
40
|
-
*
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
52
|
-
"eslint": "^8.
|
|
51
|
+
"chai": "4.3.8",
|
|
52
|
+
"eslint": "^8.50.0",
|
|
53
53
|
"eslint-config-prettier": "^9.0.0",
|
|
54
|
-
"eslint-plugin-jsdoc": "^46.
|
|
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": "^
|
|
58
|
+
"lint-staged": "^14.0.1",
|
|
59
59
|
"mocha": "^10.2.0",
|
|
60
60
|
"prettier": "^2.8.8",
|
|
61
61
|
"should": "^13.2.3",
|
package/src/serve_rendered.js
CHANGED
|
@@ -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
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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
|
|
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
|
-
|
|
432
|
+
if (!path || path.length < 2) {
|
|
433
|
+
return null;
|
|
434
|
+
}
|
|
454
435
|
|
|
455
|
-
|
|
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
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
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
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
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
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
if (
|
|
488
|
-
|
|
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
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
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
|
-
|
|
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
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
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
|
-
|
|
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
|
-
|
|
596
|
-
|
|
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
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
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
|
|
925
|
-
|
|
926
|
-
|
|
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
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
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
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1086
|
+
// Extract coordinates from markers
|
|
1087
|
+
const markerCoordinates = [];
|
|
1088
|
+
for (const marker of markers) {
|
|
1089
|
+
markerCoordinates.push(marker.location);
|
|
1090
|
+
}
|
|
1096
1091
|
|
|
1097
|
-
|
|
1098
|
-
|
|
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
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
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
|
-
|
|
1111
|
-
|
|
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
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
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
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
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
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
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
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 () {
|