tileserver-gl-light 4.5.0 → 4.5.2
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/.readthedocs.yaml +18 -0
- package/docs/config.rst +16 -1
- package/docs/deployment.rst +2 -2
- package/docs/endpoints.rst +14 -4
- package/package.json +4 -4
- package/src/serve_rendered.js +323 -305
- package/src/serve_style.js +1 -1
- 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
|
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Read the Docs configuration file for Sphinx projects
|
|
2
|
+
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
|
|
3
|
+
|
|
4
|
+
# Required
|
|
5
|
+
version: 2
|
|
6
|
+
|
|
7
|
+
# Set the version of Python and other tools you might need
|
|
8
|
+
build:
|
|
9
|
+
os: ubuntu-22.04
|
|
10
|
+
tools:
|
|
11
|
+
python: "3.11"
|
|
12
|
+
|
|
13
|
+
# Build documentation in the doc/help/ directory with Sphinx
|
|
14
|
+
sphinx:
|
|
15
|
+
configuration: docs/conf.py
|
|
16
|
+
|
|
17
|
+
formats:
|
|
18
|
+
- pdf
|
package/docs/config.rst
CHANGED
|
@@ -33,6 +33,8 @@ Example:
|
|
|
33
33
|
"serveAllStyles": false,
|
|
34
34
|
"serveStaticMaps": true,
|
|
35
35
|
"allowRemoteMarkerIcons": true,
|
|
36
|
+
"allowInlineMarkerImages": true,
|
|
37
|
+
"staticAttributionText": "© OpenMapTiles © OpenStreetMaps",
|
|
36
38
|
"tileMargin": 0
|
|
37
39
|
},
|
|
38
40
|
"styles": {
|
|
@@ -140,7 +142,13 @@ It is recommended to also use the ``serveAllFonts`` option when using this optio
|
|
|
140
142
|
-----------
|
|
141
143
|
|
|
142
144
|
Optional string to be rendered into the raster tiles (and static maps) as watermark (bottom-left corner).
|
|
143
|
-
|
|
145
|
+
Not used by default.
|
|
146
|
+
|
|
147
|
+
``staticAttributionText``
|
|
148
|
+
-----------
|
|
149
|
+
|
|
150
|
+
Optional string to be rendered in the static images endpoint. Text will be rendered in the bottom-right corner,
|
|
151
|
+
and styled similar to attribution on web-based maps (text only, links not supported).
|
|
144
152
|
Not used by default.
|
|
145
153
|
|
|
146
154
|
``allowRemoteMarkerIcons``
|
|
@@ -150,6 +158,13 @@ Allows the rendering of marker icons fetched via http(s) hyperlinks.
|
|
|
150
158
|
For security reasons only allow this if you can control the origins from where the markers are fetched!
|
|
151
159
|
Default is to disallow fetching of icons from remote sources.
|
|
152
160
|
|
|
161
|
+
``allowInlineMarkerImages``
|
|
162
|
+
--------------
|
|
163
|
+
Allows the rendering of inline marker icons or base64 urls.
|
|
164
|
+
For security reasons only allow this if you can control the origins from where the markers are fetched!
|
|
165
|
+
Not used by default.
|
|
166
|
+
|
|
167
|
+
|
|
153
168
|
``styles``
|
|
154
169
|
==========
|
|
155
170
|
|
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,12 +35,22 @@ Static images
|
|
|
35
35
|
|
|
36
36
|
* All the static image endpoints additionally support following query parameters:
|
|
37
37
|
|
|
38
|
-
* ``path`` -
|
|
38
|
+
* ``path`` - ``((fill|stroke|width)\:[^\|]+\|)*(enc:.+|-?\d+(\.\d*)?,-?\d+(\.\d*)?(\|-?\d+(\.\d*)?,-?\d+(\.\d*)?)+)``
|
|
39
|
+
|
|
40
|
+
* comma-separated ``lng,lat``, pipe-separated pairs
|
|
41
|
+
|
|
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
|
+
|
|
44
|
+
* `Google Encoded Polyline Format <https://developers.google.com/maps/documentation/utilities/polylinealgorithm>`_
|
|
45
|
+
|
|
46
|
+
* e.g. ``path=enc:_p~iF~ps|U_ulLnnqC_mqNvxq`@``
|
|
47
|
+
* If 'enc:' is used, the rest of the path parameter is considered to be part of the encoded polyline string -- do not specify the coordinate pairs.
|
|
48
|
+
|
|
49
|
+
* With options (fill|stroke|width)
|
|
50
|
+
|
|
51
|
+
* 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 ``path=stroke:blue|width:1|fill:yellow|enc:_p~iF~ps|U_ulLnnqC_mqNvxq`@``
|
|
39
52
|
|
|
40
|
-
* e.g. ``5.9,45.8|5.9,47.8|10.5,47.8|10.5,45.8|5.9,45.8``
|
|
41
53
|
* 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
54
|
|
|
45
55
|
* ``latlng`` - indicates coordinates are in ``lat,lng`` order rather than the usual ``lng,lat``
|
|
46
56
|
* ``fill`` - color to use as the fill (e.g. ``red``, ``rgba(255,255,255,0.5)``, ``#0000ff``)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tileserver-gl-light",
|
|
3
|
-
"version": "4.5.
|
|
3
|
+
"version": "4.5.2",
|
|
4
4
|
"description": "Map tile server for JSON GL styles - serving vector tiles",
|
|
5
5
|
"main": "src/main.js",
|
|
6
6
|
"bin": "src/main.js",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"@mapbox/glyph-pbf-composite": "0.0.3",
|
|
21
21
|
"@mapbox/mbtiles": "0.12.1",
|
|
22
|
-
"@mapbox/polyline": "^1.2.
|
|
22
|
+
"@mapbox/polyline": "^1.2.1",
|
|
23
23
|
"@mapbox/sphericalmercator": "1.2.0",
|
|
24
24
|
"@mapbox/vector-tile": "1.3.1",
|
|
25
25
|
"@maplibre/maplibre-gl-style-spec": "18.0.0",
|
|
@@ -27,14 +27,14 @@
|
|
|
27
27
|
"chokidar": "3.5.3",
|
|
28
28
|
"clone": "2.1.2",
|
|
29
29
|
"color": "4.2.3",
|
|
30
|
-
"commander": "11.
|
|
30
|
+
"commander": "11.1.0",
|
|
31
31
|
"cors": "2.8.5",
|
|
32
32
|
"express": "4.18.2",
|
|
33
33
|
"handlebars": "4.7.8",
|
|
34
34
|
"http-shutdown": "1.2.2",
|
|
35
35
|
"morgan": "1.10.0",
|
|
36
36
|
"pbf": "3.2.1",
|
|
37
|
-
"proj4": "2.9.
|
|
37
|
+
"proj4": "2.9.1",
|
|
38
38
|
"request": "2.88.2",
|
|
39
39
|
"sanitize-filename": "1.6.3",
|
|
40
40
|
"tileserver-gl-styles": "2.0.0"
|
package/src/serve_rendered.js
CHANGED
|
@@ -22,8 +22,8 @@ import { getFontsPbf, getTileUrls, fixTileJSONCenter } from './utils.js';
|
|
|
22
22
|
|
|
23
23
|
const FLOAT_PATTERN = '[+-]?(?:\\d+|\\d+.?\\d+)';
|
|
24
24
|
const PATH_PATTERN =
|
|
25
|
-
/^((fill|stroke|width)\:[^\|]+\|)*(
|
|
26
|
-
const httpTester =
|
|
25
|
+
/^((fill|stroke|width)\:[^\|]+\|)*(enc:.+|-?\d+(\.\d*)?,-?\d+(\.\d*)?(\|-?\d+(\.\d*)?,-?\d+(\.\d*)?)+)/;
|
|
26
|
+
const httpTester = /^\/\//;
|
|
27
27
|
|
|
28
28
|
const mercator = new SphericalMercator();
|
|
29
29
|
const getScale = (scale) => (scale || '@1x').slice(1, 2) | 0;
|
|
@@ -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
|
*/
|
|
@@ -162,25 +158,13 @@ const extractPathsFromQuery = (query, transformer) => {
|
|
|
162
158
|
// Iterate through paths, parse and validate them
|
|
163
159
|
for (const providedPath of providedPaths) {
|
|
164
160
|
// Logic for pushing coords to path when path includes google polyline
|
|
165
|
-
if (
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
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
|
-
}
|
|
161
|
+
if (providedPath.includes('enc:') && PATH_PATTERN.test(providedPath)) {
|
|
162
|
+
// +4 because 'enc:' is 4 characters, everything after 'enc:' is considered to be part of the polyline
|
|
163
|
+
const encIndex = providedPath.indexOf('enc:') + 4;
|
|
164
|
+
const coords = polyline
|
|
165
|
+
.decode(providedPath.substring(encIndex))
|
|
166
|
+
.map(([lat, lng]) => [lng, lat]);
|
|
167
|
+
paths.push(coords);
|
|
184
168
|
} else {
|
|
185
169
|
// Iterate through paths, parse and validate them
|
|
186
170
|
const currentPath = [];
|
|
@@ -220,7 +204,6 @@ const extractPathsFromQuery = (query, transformer) => {
|
|
|
220
204
|
* on marker object.
|
|
221
205
|
* Options adhere to the following format
|
|
222
206
|
* [optionName]:[optionValue]
|
|
223
|
-
*
|
|
224
207
|
* @param {List[String]} optionsList List of option strings.
|
|
225
208
|
* @param {object} marker Marker object to configure.
|
|
226
209
|
*/
|
|
@@ -255,7 +238,6 @@ const parseMarkerOptions = (optionsList, marker) => {
|
|
|
255
238
|
|
|
256
239
|
/**
|
|
257
240
|
* Parses markers provided via query into a list of marker objects.
|
|
258
|
-
*
|
|
259
241
|
* @param {object} query Request query parameters.
|
|
260
242
|
* @param {object} options Configuration options.
|
|
261
243
|
* @param {Function} transformer Optional transform function.
|
|
@@ -294,7 +276,10 @@ const extractMarkersFromQuery = (query, options, transformer) => {
|
|
|
294
276
|
let iconURI = markerParts[1];
|
|
295
277
|
// Check if icon is served via http otherwise marker icons are expected to
|
|
296
278
|
// be provided as filepaths relative to configured icon path
|
|
297
|
-
|
|
279
|
+
const isRemoteURL =
|
|
280
|
+
iconURI.startsWith('http://') || iconURI.startsWith('https://');
|
|
281
|
+
const isDataURL = iconURI.startsWith('data:');
|
|
282
|
+
if (!(isRemoteURL || isDataURL)) {
|
|
298
283
|
// Sanitize URI with sanitize-filename
|
|
299
284
|
// https://www.npmjs.com/package/sanitize-filename#details
|
|
300
285
|
iconURI = sanitize(iconURI);
|
|
@@ -307,7 +292,9 @@ const extractMarkersFromQuery = (query, options, transformer) => {
|
|
|
307
292
|
iconURI = path.resolve(options.paths.icons, iconURI);
|
|
308
293
|
|
|
309
294
|
// When we encounter a remote icon check if the configuration explicitly allows them.
|
|
310
|
-
} else if (options.allowRemoteMarkerIcons !== true) {
|
|
295
|
+
} else if (isRemoteURL && options.allowRemoteMarkerIcons !== true) {
|
|
296
|
+
continue;
|
|
297
|
+
} else if (isDataURL && options.allowInlineMarkerImages !== true) {
|
|
311
298
|
continue;
|
|
312
299
|
}
|
|
313
300
|
|
|
@@ -335,7 +322,6 @@ const extractMarkersFromQuery = (query, options, transformer) => {
|
|
|
335
322
|
|
|
336
323
|
/**
|
|
337
324
|
* Transforms coordinates to pixels.
|
|
338
|
-
*
|
|
339
325
|
* @param {List[Number]} ll Longitude/Latitude coordinate pair.
|
|
340
326
|
* @param {number} zoom Map zoom level.
|
|
341
327
|
*/
|
|
@@ -347,7 +333,6 @@ const precisePx = (ll, zoom) => {
|
|
|
347
333
|
|
|
348
334
|
/**
|
|
349
335
|
* Draws a marker in cavans context.
|
|
350
|
-
*
|
|
351
336
|
* @param {object} ctx Canvas context object.
|
|
352
337
|
* @param {object} marker Marker object parsed by extractMarkersFromQuery.
|
|
353
338
|
* @param {number} z Map zoom level.
|
|
@@ -419,7 +404,6 @@ const drawMarker = (ctx, marker, z) => {
|
|
|
419
404
|
* Wraps drawing of markers into list of promises and awaits them.
|
|
420
405
|
* It's required because images are expected to load asynchronous in canvas js
|
|
421
406
|
* even when provided from a local disk.
|
|
422
|
-
*
|
|
423
407
|
* @param {object} ctx Canvas context object.
|
|
424
408
|
* @param {List[Object]} markers Marker objects parsed by extractMarkersFromQuery.
|
|
425
409
|
* @param {number} z Map zoom level.
|
|
@@ -438,117 +422,107 @@ const drawMarkers = async (ctx, markers, z) => {
|
|
|
438
422
|
|
|
439
423
|
/**
|
|
440
424
|
* Draws a list of coordinates onto a canvas and styles the resulting path.
|
|
441
|
-
*
|
|
442
425
|
* @param {object} ctx Canvas context object.
|
|
443
426
|
* @param {List[Number]} path List of coordinates.
|
|
444
427
|
* @param {object} query Request query parameters.
|
|
428
|
+
* @param {string} pathQuery Path query parameter.
|
|
445
429
|
* @param {number} z Map zoom level.
|
|
446
430
|
*/
|
|
447
|
-
const drawPath = (ctx, path, query, z) => {
|
|
448
|
-
const
|
|
449
|
-
if (!path || path.length < 2) {
|
|
450
|
-
return null;
|
|
451
|
-
}
|
|
431
|
+
const drawPath = (ctx, path, query, pathQuery, z) => {
|
|
432
|
+
const splitPaths = pathQuery.split('|');
|
|
452
433
|
|
|
453
|
-
|
|
434
|
+
if (!path || path.length < 2) {
|
|
435
|
+
return null;
|
|
436
|
+
}
|
|
454
437
|
|
|
455
|
-
|
|
456
|
-
for (const pair of path) {
|
|
457
|
-
const px = precisePx(pair, z);
|
|
458
|
-
ctx.lineTo(px[0], px[1]);
|
|
459
|
-
}
|
|
438
|
+
ctx.beginPath();
|
|
460
439
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
ctx.closePath();
|
|
467
|
-
}
|
|
440
|
+
// Transform coordinates to pixel on canvas and draw lines between points
|
|
441
|
+
for (const pair of path) {
|
|
442
|
+
const px = precisePx(pair, z);
|
|
443
|
+
ctx.lineTo(px[0], px[1]);
|
|
444
|
+
}
|
|
468
445
|
|
|
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
|
-
}
|
|
446
|
+
// Check if first coordinate matches last coordinate
|
|
447
|
+
if (
|
|
448
|
+
path[0][0] === path[path.length - 1][0] &&
|
|
449
|
+
path[0][1] === path[path.length - 1][1]
|
|
450
|
+
) {
|
|
451
|
+
ctx.closePath();
|
|
452
|
+
}
|
|
483
453
|
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
if (
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
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;
|
|
454
|
+
// Optionally fill drawn shape with a rgba color from query
|
|
455
|
+
const pathHasFill = splitPaths.filter((x) => x.startsWith('fill')).length > 0;
|
|
456
|
+
if (query.fill !== undefined || pathHasFill) {
|
|
457
|
+
if ('fill' in query) {
|
|
458
|
+
ctx.fillStyle = query.fill || 'rgba(255,255,255,0.4)';
|
|
459
|
+
}
|
|
460
|
+
if (pathHasFill) {
|
|
461
|
+
ctx.fillStyle = splitPaths
|
|
462
|
+
.find((x) => x.startsWith('fill:'))
|
|
463
|
+
.replace('fill:', '');
|
|
524
464
|
}
|
|
465
|
+
ctx.fill();
|
|
466
|
+
}
|
|
525
467
|
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
468
|
+
// Get line width from query and fall back to 1 if not provided
|
|
469
|
+
const pathHasWidth =
|
|
470
|
+
splitPaths.filter((x) => x.startsWith('width')).length > 0;
|
|
471
|
+
if (query.width !== undefined || pathHasWidth) {
|
|
472
|
+
let lineWidth = 1;
|
|
473
|
+
// Get line width from query
|
|
474
|
+
if ('width' in query) {
|
|
475
|
+
lineWidth = Number(query.width);
|
|
476
|
+
}
|
|
477
|
+
// Get line width from path in query
|
|
478
|
+
if (pathHasWidth) {
|
|
479
|
+
lineWidth = Number(
|
|
480
|
+
splitPaths.find((x) => x.startsWith('width:')).replace('width:', ''),
|
|
481
|
+
);
|
|
540
482
|
}
|
|
541
|
-
|
|
542
|
-
|
|
483
|
+
// Get border width from query and fall back to 10% of line width
|
|
484
|
+
const borderWidth =
|
|
485
|
+
query.borderwidth !== undefined
|
|
486
|
+
? parseFloat(query.borderwidth)
|
|
487
|
+
: lineWidth * 0.1;
|
|
488
|
+
|
|
489
|
+
// Set rendering style for the start and end points of the path
|
|
490
|
+
// https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineCap
|
|
491
|
+
ctx.lineCap = query.linecap || 'butt';
|
|
492
|
+
|
|
493
|
+
// Set rendering style for overlapping segments of the path with differing directions
|
|
494
|
+
// https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineJoin
|
|
495
|
+
ctx.lineJoin = query.linejoin || 'miter';
|
|
496
|
+
|
|
497
|
+
// In order to simulate a border we draw the path two times with the first
|
|
498
|
+
// beeing the wider border part.
|
|
499
|
+
if (query.border !== undefined && borderWidth > 0) {
|
|
500
|
+
// We need to double the desired border width and add it to the line width
|
|
501
|
+
// in order to get the desired border on each side of the line.
|
|
502
|
+
ctx.lineWidth = lineWidth + borderWidth * 2;
|
|
503
|
+
// Set border style as rgba
|
|
504
|
+
ctx.strokeStyle = query.border;
|
|
505
|
+
ctx.stroke();
|
|
506
|
+
}
|
|
507
|
+
ctx.lineWidth = lineWidth;
|
|
508
|
+
}
|
|
543
509
|
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
510
|
+
const pathHasStroke =
|
|
511
|
+
splitPaths.filter((x) => x.startsWith('stroke')).length > 0;
|
|
512
|
+
if (query.stroke !== undefined || pathHasStroke) {
|
|
513
|
+
if ('stroke' in query) {
|
|
514
|
+
ctx.strokeStyle = query.stroke;
|
|
515
|
+
}
|
|
516
|
+
// Path Stroke gets higher priority
|
|
517
|
+
if (pathHasStroke) {
|
|
518
|
+
ctx.strokeStyle = splitPaths
|
|
519
|
+
.find((x) => x.startsWith('stroke:'))
|
|
520
|
+
.replace('stroke:', '');
|
|
548
521
|
}
|
|
549
522
|
} else {
|
|
550
|
-
|
|
523
|
+
ctx.strokeStyle = 'rgba(0,64,255,0.7)';
|
|
551
524
|
}
|
|
525
|
+
ctx.stroke();
|
|
552
526
|
};
|
|
553
527
|
|
|
554
528
|
const renderOverlay = async (
|
|
@@ -592,9 +566,10 @@ const renderOverlay = async (
|
|
|
592
566
|
}
|
|
593
567
|
|
|
594
568
|
// Draw provided paths if any
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
569
|
+
paths.forEach((path, i) => {
|
|
570
|
+
const pathQuery = Array.isArray(query.path) ? query.path.at(i) : query.path;
|
|
571
|
+
drawPath(ctx, path, query, pathQuery, z);
|
|
572
|
+
});
|
|
598
573
|
|
|
599
574
|
// Await drawing of markers before rendering the canvas
|
|
600
575
|
await drawMarkers(ctx, markers, z);
|
|
@@ -798,6 +773,35 @@ export const serve_rendered = {
|
|
|
798
773
|
composite_array.push({ input: canvas.toBuffer() });
|
|
799
774
|
}
|
|
800
775
|
|
|
776
|
+
if (opt_mode === 'static' && item.staticAttributionText) {
|
|
777
|
+
const canvas = createCanvas(scale * width, scale * height);
|
|
778
|
+
const ctx = canvas.getContext('2d');
|
|
779
|
+
ctx.scale(scale, scale);
|
|
780
|
+
|
|
781
|
+
ctx.font = '10px sans-serif';
|
|
782
|
+
const text = item.staticAttributionText;
|
|
783
|
+
const textMetrics = ctx.measureText(text);
|
|
784
|
+
const textWidth = textMetrics.width;
|
|
785
|
+
const textHeight = 14;
|
|
786
|
+
|
|
787
|
+
const padding = 6;
|
|
788
|
+
ctx.fillStyle = 'rgba(255, 255, 255, 0.8)';
|
|
789
|
+
ctx.fillRect(
|
|
790
|
+
width - textWidth - padding,
|
|
791
|
+
height - textHeight - padding,
|
|
792
|
+
textWidth + padding,
|
|
793
|
+
textHeight + padding,
|
|
794
|
+
);
|
|
795
|
+
ctx.fillStyle = 'rgba(0,0,0,.8)';
|
|
796
|
+
ctx.fillText(
|
|
797
|
+
item.staticAttributionText,
|
|
798
|
+
width - textWidth - padding / 2,
|
|
799
|
+
height - textHeight + 8,
|
|
800
|
+
);
|
|
801
|
+
|
|
802
|
+
composite_array.push({ input: canvas.toBuffer() });
|
|
803
|
+
}
|
|
804
|
+
|
|
801
805
|
if (composite_array.length > 0) {
|
|
802
806
|
image.composite(composite_array);
|
|
803
807
|
}
|
|
@@ -897,35 +901,118 @@ export const serve_rendered = {
|
|
|
897
901
|
app.get(
|
|
898
902
|
util.format(staticPattern, centerPattern),
|
|
899
903
|
async (req, res, next) => {
|
|
904
|
+
try {
|
|
905
|
+
const item = repo[req.params.id];
|
|
906
|
+
if (!item) {
|
|
907
|
+
return res.sendStatus(404);
|
|
908
|
+
}
|
|
909
|
+
const raw = req.params.raw;
|
|
910
|
+
const z = +req.params.z;
|
|
911
|
+
let x = +req.params.x;
|
|
912
|
+
let y = +req.params.y;
|
|
913
|
+
const bearing = +(req.params.bearing || '0');
|
|
914
|
+
const pitch = +(req.params.pitch || '0');
|
|
915
|
+
const w = req.params.width | 0;
|
|
916
|
+
const h = req.params.height | 0;
|
|
917
|
+
const scale = getScale(req.params.scale);
|
|
918
|
+
const format = req.params.format;
|
|
919
|
+
|
|
920
|
+
if (z < 0) {
|
|
921
|
+
return res.status(404).send('Invalid zoom');
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
const transformer = raw
|
|
925
|
+
? mercator.inverse.bind(mercator)
|
|
926
|
+
: item.dataProjWGStoInternalWGS;
|
|
927
|
+
|
|
928
|
+
if (transformer) {
|
|
929
|
+
const ll = transformer([x, y]);
|
|
930
|
+
x = ll[0];
|
|
931
|
+
y = ll[1];
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
const paths = extractPathsFromQuery(req.query, transformer);
|
|
935
|
+
const markers = extractMarkersFromQuery(
|
|
936
|
+
req.query,
|
|
937
|
+
options,
|
|
938
|
+
transformer,
|
|
939
|
+
);
|
|
940
|
+
const overlay = await renderOverlay(
|
|
941
|
+
z,
|
|
942
|
+
x,
|
|
943
|
+
y,
|
|
944
|
+
bearing,
|
|
945
|
+
pitch,
|
|
946
|
+
w,
|
|
947
|
+
h,
|
|
948
|
+
scale,
|
|
949
|
+
paths,
|
|
950
|
+
markers,
|
|
951
|
+
req.query,
|
|
952
|
+
);
|
|
953
|
+
|
|
954
|
+
return respondImage(
|
|
955
|
+
item,
|
|
956
|
+
z,
|
|
957
|
+
x,
|
|
958
|
+
y,
|
|
959
|
+
bearing,
|
|
960
|
+
pitch,
|
|
961
|
+
w,
|
|
962
|
+
h,
|
|
963
|
+
scale,
|
|
964
|
+
format,
|
|
965
|
+
res,
|
|
966
|
+
next,
|
|
967
|
+
overlay,
|
|
968
|
+
'static',
|
|
969
|
+
);
|
|
970
|
+
} catch (e) {
|
|
971
|
+
next(e);
|
|
972
|
+
}
|
|
973
|
+
},
|
|
974
|
+
);
|
|
975
|
+
|
|
976
|
+
const serveBounds = async (req, res, next) => {
|
|
977
|
+
try {
|
|
900
978
|
const item = repo[req.params.id];
|
|
901
979
|
if (!item) {
|
|
902
980
|
return res.sendStatus(404);
|
|
903
981
|
}
|
|
904
982
|
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
|
-
}
|
|
983
|
+
const bbox = [
|
|
984
|
+
+req.params.minx,
|
|
985
|
+
+req.params.miny,
|
|
986
|
+
+req.params.maxx,
|
|
987
|
+
+req.params.maxy,
|
|
988
|
+
];
|
|
989
|
+
let center = [(bbox[0] + bbox[2]) / 2, (bbox[1] + bbox[3]) / 2];
|
|
918
990
|
|
|
919
991
|
const transformer = raw
|
|
920
992
|
? mercator.inverse.bind(mercator)
|
|
921
993
|
: item.dataProjWGStoInternalWGS;
|
|
922
994
|
|
|
923
995
|
if (transformer) {
|
|
924
|
-
const
|
|
925
|
-
|
|
926
|
-
|
|
996
|
+
const minCorner = transformer(bbox.slice(0, 2));
|
|
997
|
+
const maxCorner = transformer(bbox.slice(2));
|
|
998
|
+
bbox[0] = minCorner[0];
|
|
999
|
+
bbox[1] = minCorner[1];
|
|
1000
|
+
bbox[2] = maxCorner[0];
|
|
1001
|
+
bbox[3] = maxCorner[1];
|
|
1002
|
+
center = transformer(center);
|
|
927
1003
|
}
|
|
928
1004
|
|
|
1005
|
+
const w = req.params.width | 0;
|
|
1006
|
+
const h = req.params.height | 0;
|
|
1007
|
+
const scale = getScale(req.params.scale);
|
|
1008
|
+
const format = req.params.format;
|
|
1009
|
+
|
|
1010
|
+
const z = calcZForBBox(bbox, w, h, req.query);
|
|
1011
|
+
const x = center[0];
|
|
1012
|
+
const y = center[1];
|
|
1013
|
+
const bearing = 0;
|
|
1014
|
+
const pitch = 0;
|
|
1015
|
+
|
|
929
1016
|
const paths = extractPathsFromQuery(req.query, transformer);
|
|
930
1017
|
const markers = extractMarkersFromQuery(
|
|
931
1018
|
req.query,
|
|
@@ -945,7 +1032,6 @@ export const serve_rendered = {
|
|
|
945
1032
|
markers,
|
|
946
1033
|
req.query,
|
|
947
1034
|
);
|
|
948
|
-
|
|
949
1035
|
return respondImage(
|
|
950
1036
|
item,
|
|
951
1037
|
z,
|
|
@@ -962,83 +1048,9 @@ export const serve_rendered = {
|
|
|
962
1048
|
overlay,
|
|
963
1049
|
'static',
|
|
964
1050
|
);
|
|
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);
|
|
1051
|
+
} catch (e) {
|
|
1052
|
+
next(e);
|
|
972
1053
|
}
|
|
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
1054
|
};
|
|
1043
1055
|
|
|
1044
1056
|
const boundsPattern = util.format(
|
|
@@ -1078,97 +1090,101 @@ export const serve_rendered = {
|
|
|
1078
1090
|
app.get(
|
|
1079
1091
|
util.format(staticPattern, autoPattern),
|
|
1080
1092
|
async (req, res, next) => {
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1093
|
+
try {
|
|
1094
|
+
const item = repo[req.params.id];
|
|
1095
|
+
if (!item) {
|
|
1096
|
+
return res.sendStatus(404);
|
|
1097
|
+
}
|
|
1098
|
+
const raw = req.params.raw;
|
|
1099
|
+
const w = req.params.width | 0;
|
|
1100
|
+
const h = req.params.height | 0;
|
|
1101
|
+
const bearing = 0;
|
|
1102
|
+
const pitch = 0;
|
|
1103
|
+
const scale = getScale(req.params.scale);
|
|
1104
|
+
const format = req.params.format;
|
|
1105
|
+
|
|
1106
|
+
const transformer = raw
|
|
1107
|
+
? mercator.inverse.bind(mercator)
|
|
1108
|
+
: item.dataProjWGStoInternalWGS;
|
|
1109
|
+
|
|
1110
|
+
const paths = extractPathsFromQuery(req.query, transformer);
|
|
1111
|
+
const markers = extractMarkersFromQuery(
|
|
1112
|
+
req.query,
|
|
1113
|
+
options,
|
|
1114
|
+
transformer,
|
|
1115
|
+
);
|
|
1092
1116
|
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1117
|
+
// Extract coordinates from markers
|
|
1118
|
+
const markerCoordinates = [];
|
|
1119
|
+
for (const marker of markers) {
|
|
1120
|
+
markerCoordinates.push(marker.location);
|
|
1121
|
+
}
|
|
1096
1122
|
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
req.query,
|
|
1100
|
-
options,
|
|
1101
|
-
transformer,
|
|
1102
|
-
);
|
|
1123
|
+
// Create array with coordinates from markers and path
|
|
1124
|
+
const coords = [].concat(paths.flat()).concat(markerCoordinates);
|
|
1103
1125
|
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
}
|
|
1126
|
+
// Check if we have at least one coordinate to calculate a bounding box
|
|
1127
|
+
if (coords.length < 1) {
|
|
1128
|
+
return res.status(400).send('No coordinates provided');
|
|
1129
|
+
}
|
|
1109
1130
|
|
|
1110
|
-
|
|
1111
|
-
|
|
1131
|
+
const bbox = [Infinity, Infinity, -Infinity, -Infinity];
|
|
1132
|
+
for (const pair of coords) {
|
|
1133
|
+
bbox[0] = Math.min(bbox[0], pair[0]);
|
|
1134
|
+
bbox[1] = Math.min(bbox[1], pair[1]);
|
|
1135
|
+
bbox[2] = Math.max(bbox[2], pair[0]);
|
|
1136
|
+
bbox[3] = Math.max(bbox[3], pair[1]);
|
|
1137
|
+
}
|
|
1112
1138
|
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1139
|
+
const bbox_ = mercator.convert(bbox, '900913');
|
|
1140
|
+
const center = mercator.inverse([
|
|
1141
|
+
(bbox_[0] + bbox_[2]) / 2,
|
|
1142
|
+
(bbox_[1] + bbox_[3]) / 2,
|
|
1143
|
+
]);
|
|
1144
|
+
|
|
1145
|
+
// Calculate zoom level
|
|
1146
|
+
const maxZoom = parseFloat(req.query.maxzoom);
|
|
1147
|
+
let z = calcZForBBox(bbox, w, h, req.query);
|
|
1148
|
+
if (maxZoom > 0) {
|
|
1149
|
+
z = Math.min(z, maxZoom);
|
|
1150
|
+
}
|
|
1117
1151
|
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1152
|
+
const x = center[0];
|
|
1153
|
+
const y = center[1];
|
|
1154
|
+
|
|
1155
|
+
const overlay = await renderOverlay(
|
|
1156
|
+
z,
|
|
1157
|
+
x,
|
|
1158
|
+
y,
|
|
1159
|
+
bearing,
|
|
1160
|
+
pitch,
|
|
1161
|
+
w,
|
|
1162
|
+
h,
|
|
1163
|
+
scale,
|
|
1164
|
+
paths,
|
|
1165
|
+
markers,
|
|
1166
|
+
req.query,
|
|
1167
|
+
);
|
|
1125
1168
|
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1169
|
+
return respondImage(
|
|
1170
|
+
item,
|
|
1171
|
+
z,
|
|
1172
|
+
x,
|
|
1173
|
+
y,
|
|
1174
|
+
bearing,
|
|
1175
|
+
pitch,
|
|
1176
|
+
w,
|
|
1177
|
+
h,
|
|
1178
|
+
scale,
|
|
1179
|
+
format,
|
|
1180
|
+
res,
|
|
1181
|
+
next,
|
|
1182
|
+
overlay,
|
|
1183
|
+
'static',
|
|
1184
|
+
);
|
|
1185
|
+
} catch (e) {
|
|
1186
|
+
next(e);
|
|
1137
1187
|
}
|
|
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
1188
|
},
|
|
1173
1189
|
);
|
|
1174
1190
|
}
|
|
@@ -1393,6 +1409,8 @@ export const serve_rendered = {
|
|
|
1393
1409
|
dataProjWGStoInternalWGS: null,
|
|
1394
1410
|
lastModified: new Date().toUTCString(),
|
|
1395
1411
|
watermark: params.watermark || options.watermark,
|
|
1412
|
+
staticAttributionText:
|
|
1413
|
+
params.staticAttributionText || options.staticAttributionText,
|
|
1396
1414
|
};
|
|
1397
1415
|
repo[id] = repoobj;
|
|
1398
1416
|
|
package/src/serve_style.js
CHANGED
|
@@ -9,7 +9,7 @@ import { validate } from '@maplibre/maplibre-gl-style-spec';
|
|
|
9
9
|
|
|
10
10
|
import { getPublicUrl } from './utils.js';
|
|
11
11
|
|
|
12
|
-
const httpTester =
|
|
12
|
+
const httpTester = /^\/\//;
|
|
13
13
|
|
|
14
14
|
const fixUrl = (req, url, publicUrl, opt_nokey) => {
|
|
15
15
|
if (!url || typeof url !== 'string' || url.indexOf('local://') !== 0) {
|
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=' + encodeURIComponent('enc:{{biGwvyGoUi@s_A|{@'),
|
|
184
|
+
);
|
|
185
|
+
});
|
|
174
186
|
});
|
|
175
187
|
|
|
176
188
|
describe('invalid requests return 4xx', function () {
|