tileserver-gl-light 4.1.2 → 4.2.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/.eslintrc.cjs +32 -0
- package/.gitattributes +11 -0
- package/.github/dependabot.yml +19 -0
- package/.github/workflows/codeql.yml +35 -0
- package/.github/workflows/ct.yml +57 -0
- package/.github/workflows/release.yml +103 -0
- package/.husky/commit-msg +21 -0
- package/.husky/pre-push +4 -0
- package/Dockerfile +4 -1
- package/commitlint.config.cjs +3 -0
- package/docker-entrypoint.sh +1 -1
- package/docs/installation.rst +1 -1
- package/docs/usage.rst +6 -0
- package/lint-staged.config.cjs +4 -0
- package/package.json +29 -12
- package/prettier.config.cjs +13 -0
- package/publish.js +17 -9
- package/run.sh +1 -1
- package/src/healthcheck.js +18 -0
- package/src/main.js +57 -65
- package/src/serve_data.js +102 -82
- package/src/serve_font.js +19 -10
- package/src/serve_light.js +5 -6
- package/src/serve_rendered.js +554 -310
- package/src/serve_style.js +31 -16
- package/src/server.js +248 -156
- package/src/utils.js +62 -43
- package/test/metadata.js +44 -43
- package/test/setup.js +8 -7
- package/test/static.js +127 -31
- package/test/style.js +31 -26
- package/test/tiles_data.js +5 -5
- package/test/tiles_rendered.js +7 -7
- package/.travis.yml +0 -21
package/src/serve_rendered.js
CHANGED
|
@@ -7,19 +7,19 @@ import url from 'url';
|
|
|
7
7
|
import util from 'util';
|
|
8
8
|
import zlib from 'zlib';
|
|
9
9
|
import sharp from 'sharp'; // sharp has to be required before node-canvas. see https://github.com/lovell/sharp/issues/371
|
|
10
|
-
import {createCanvas, Image} from 'canvas';
|
|
10
|
+
import { createCanvas, Image } from 'canvas';
|
|
11
11
|
import clone from 'clone';
|
|
12
12
|
import Color from 'color';
|
|
13
13
|
import express from 'express';
|
|
14
|
-
import sanitize from
|
|
14
|
+
import sanitize from 'sanitize-filename';
|
|
15
15
|
import SphericalMercator from '@mapbox/sphericalmercator';
|
|
16
16
|
import mlgl from '@maplibre/maplibre-gl-native';
|
|
17
17
|
import MBTiles from '@mapbox/mbtiles';
|
|
18
18
|
import proj4 from 'proj4';
|
|
19
19
|
import request from 'request';
|
|
20
|
-
import {getFontsPbf, getTileUrls, fixTileJSONCenter} from './utils.js';
|
|
20
|
+
import { getFontsPbf, getTileUrls, fixTileJSONCenter } from './utils.js';
|
|
21
21
|
|
|
22
|
-
const FLOAT_PATTERN = '[+-]?(?:\\d+|\\d
|
|
22
|
+
const FLOAT_PATTERN = '[+-]?(?:\\d+|\\d+.?\\d+)';
|
|
23
23
|
const httpTester = /^(http(s)?:)?\/\//;
|
|
24
24
|
|
|
25
25
|
const mercator = new SphericalMercator();
|
|
@@ -38,7 +38,7 @@ const extensionToFormat = {
|
|
|
38
38
|
'.jpg': 'jpeg',
|
|
39
39
|
'.jpeg': 'jpeg',
|
|
40
40
|
'.png': 'png',
|
|
41
|
-
'.webp': 'webp'
|
|
41
|
+
'.webp': 'webp',
|
|
42
42
|
};
|
|
43
43
|
|
|
44
44
|
/**
|
|
@@ -46,18 +46,19 @@ const extensionToFormat = {
|
|
|
46
46
|
* string is for unknown or unsupported formats.
|
|
47
47
|
*/
|
|
48
48
|
const cachedEmptyResponses = {
|
|
49
|
-
'': Buffer.alloc(0)
|
|
49
|
+
'': Buffer.alloc(0),
|
|
50
50
|
};
|
|
51
51
|
|
|
52
52
|
/**
|
|
53
53
|
* Create an appropriate mlgl response for http errors.
|
|
54
|
+
*
|
|
54
55
|
* @param {string} format The format (a sharp format or 'pbf').
|
|
55
56
|
* @param {string} color The background color (or empty string for transparent).
|
|
56
57
|
* @param {Function} callback The mlgl callback.
|
|
57
58
|
*/
|
|
58
59
|
function createEmptyResponse(format, color, callback) {
|
|
59
60
|
if (!format || format === 'pbf') {
|
|
60
|
-
callback(null, {data: cachedEmptyResponses['']});
|
|
61
|
+
callback(null, { data: cachedEmptyResponses[''] });
|
|
61
62
|
return;
|
|
62
63
|
}
|
|
63
64
|
|
|
@@ -71,7 +72,7 @@ function createEmptyResponse(format, color, callback) {
|
|
|
71
72
|
const cacheKey = `${format},${color}`;
|
|
72
73
|
const data = cachedEmptyResponses[cacheKey];
|
|
73
74
|
if (data) {
|
|
74
|
-
callback(null, {data: data});
|
|
75
|
+
callback(null, { data: data });
|
|
75
76
|
return;
|
|
76
77
|
}
|
|
77
78
|
|
|
@@ -83,21 +84,25 @@ function createEmptyResponse(format, color, callback) {
|
|
|
83
84
|
raw: {
|
|
84
85
|
width: 1,
|
|
85
86
|
height: 1,
|
|
86
|
-
channels: channels
|
|
87
|
-
}
|
|
88
|
-
})
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
87
|
+
channels: channels,
|
|
88
|
+
},
|
|
89
|
+
})
|
|
90
|
+
.toFormat(format)
|
|
91
|
+
.toBuffer((err, buffer, info) => {
|
|
92
|
+
if (!err) {
|
|
93
|
+
cachedEmptyResponses[cacheKey] = buffer;
|
|
94
|
+
}
|
|
95
|
+
callback(null, { data: buffer });
|
|
96
|
+
});
|
|
94
97
|
}
|
|
95
98
|
|
|
96
99
|
/**
|
|
97
100
|
* Parses coordinate pair provided to pair of floats and ensures the resulting
|
|
98
101
|
* pair is a longitude/latitude combination depending on lnglat query parameter.
|
|
102
|
+
*
|
|
99
103
|
* @param {List} coordinatePair Coordinate pair.
|
|
100
|
-
* @param
|
|
104
|
+
* @param coordinates
|
|
105
|
+
* @param {object} query Request query parameters.
|
|
101
106
|
*/
|
|
102
107
|
const parseCoordinatePair = (coordinates, query) => {
|
|
103
108
|
const firstCoordinate = parseFloat(coordinates[0]);
|
|
@@ -119,8 +124,9 @@ const parseCoordinatePair = (coordinates, query) => {
|
|
|
119
124
|
|
|
120
125
|
/**
|
|
121
126
|
* Parses a coordinate pair from query arguments and optionally transforms it.
|
|
127
|
+
*
|
|
122
128
|
* @param {List} coordinatePair Coordinate pair.
|
|
123
|
-
* @param {
|
|
129
|
+
* @param {object} query Request query parameters.
|
|
124
130
|
* @param {Function} transformer Optional transform function.
|
|
125
131
|
*/
|
|
126
132
|
const parseCoordinates = (coordinatePair, query, transformer) => {
|
|
@@ -134,10 +140,10 @@ const parseCoordinates = (coordinatePair, query, transformer) => {
|
|
|
134
140
|
return parsedCoordinates;
|
|
135
141
|
};
|
|
136
142
|
|
|
137
|
-
|
|
138
143
|
/**
|
|
139
144
|
* Parses paths provided via query into a list of path objects.
|
|
140
|
-
*
|
|
145
|
+
*
|
|
146
|
+
* @param {object} query Request query parameters.
|
|
141
147
|
* @param {Function} transformer Optional transform function.
|
|
142
148
|
*/
|
|
143
149
|
const extractPathsFromQuery = (query, transformer) => {
|
|
@@ -180,9 +186,8 @@ const extractPathsFromQuery = (query, transformer) => {
|
|
|
180
186
|
|
|
181
187
|
// Extend list of paths with current path if it contains coordinates
|
|
182
188
|
if (currentPath.length) {
|
|
183
|
-
paths.push(currentPath)
|
|
189
|
+
paths.push(currentPath);
|
|
184
190
|
}
|
|
185
|
-
|
|
186
191
|
}
|
|
187
192
|
return paths;
|
|
188
193
|
};
|
|
@@ -192,8 +197,9 @@ const extractPathsFromQuery = (query, transformer) => {
|
|
|
192
197
|
* on marker object.
|
|
193
198
|
* Options adhere to the following format
|
|
194
199
|
* [optionName]:[optionValue]
|
|
200
|
+
*
|
|
195
201
|
* @param {List[String]} optionsList List of option strings.
|
|
196
|
-
* @param {
|
|
202
|
+
* @param {object} marker Marker object to configure.
|
|
197
203
|
*/
|
|
198
204
|
const parseMarkerOptions = (optionsList, marker) => {
|
|
199
205
|
for (const options of optionsList) {
|
|
@@ -207,7 +213,7 @@ const parseMarkerOptions = (optionsList, marker) => {
|
|
|
207
213
|
// Scale factor to up- or downscale icon
|
|
208
214
|
case 'scale':
|
|
209
215
|
// Scale factors must not be negative
|
|
210
|
-
marker.scale = Math.abs(parseFloat(optionParts[1]))
|
|
216
|
+
marker.scale = Math.abs(parseFloat(optionParts[1]));
|
|
211
217
|
break;
|
|
212
218
|
// Icon offset as positive or negative pixel value in the following
|
|
213
219
|
// format [offsetX],[offsetY] where [offsetY] is optional
|
|
@@ -226,8 +232,9 @@ const parseMarkerOptions = (optionsList, marker) => {
|
|
|
226
232
|
|
|
227
233
|
/**
|
|
228
234
|
* Parses markers provided via query into a list of marker objects.
|
|
229
|
-
*
|
|
230
|
-
* @param {
|
|
235
|
+
*
|
|
236
|
+
* @param {object} query Request query parameters.
|
|
237
|
+
* @param {object} options Configuration options.
|
|
231
238
|
* @param {Function} transformer Optional transform function.
|
|
232
239
|
*/
|
|
233
240
|
const extractMarkersFromQuery = (query, options, transformer) => {
|
|
@@ -240,8 +247,9 @@ const extractMarkersFromQuery = (query, options, transformer) => {
|
|
|
240
247
|
|
|
241
248
|
// Check if multiple markers have been provided and mimic a list if it's a
|
|
242
249
|
// single maker.
|
|
243
|
-
const providedMarkers = Array.isArray(query.marker)
|
|
244
|
-
|
|
250
|
+
const providedMarkers = Array.isArray(query.marker)
|
|
251
|
+
? query.marker
|
|
252
|
+
: [query.marker];
|
|
245
253
|
|
|
246
254
|
// Iterate through provided markers which can have one of the following
|
|
247
255
|
// formats
|
|
@@ -266,7 +274,7 @@ const extractMarkersFromQuery = (query, options, transformer) => {
|
|
|
266
274
|
if (!(iconURI.startsWith('http://') || iconURI.startsWith('https://'))) {
|
|
267
275
|
// Sanitize URI with sanitize-filename
|
|
268
276
|
// https://www.npmjs.com/package/sanitize-filename#details
|
|
269
|
-
iconURI = sanitize(iconURI)
|
|
277
|
+
iconURI = sanitize(iconURI);
|
|
270
278
|
|
|
271
279
|
// If the selected icon is not part of available icons skip it
|
|
272
280
|
if (!options.paths.availableIcons.includes(iconURI)) {
|
|
@@ -275,7 +283,7 @@ const extractMarkersFromQuery = (query, options, transformer) => {
|
|
|
275
283
|
|
|
276
284
|
iconURI = path.resolve(options.paths.icons, iconURI);
|
|
277
285
|
|
|
278
|
-
|
|
286
|
+
// When we encounter a remote icon check if the configuration explicitly allows them.
|
|
279
287
|
} else if (options.allowRemoteMarkerIcons !== true) {
|
|
280
288
|
continue;
|
|
281
289
|
}
|
|
@@ -298,15 +306,15 @@ const extractMarkersFromQuery = (query, options, transformer) => {
|
|
|
298
306
|
|
|
299
307
|
// Add marker to list
|
|
300
308
|
markers.push(marker);
|
|
301
|
-
|
|
302
309
|
}
|
|
303
310
|
return markers;
|
|
304
311
|
};
|
|
305
312
|
|
|
306
313
|
/**
|
|
307
314
|
* Transforms coordinates to pixels.
|
|
315
|
+
*
|
|
308
316
|
* @param {List[Number]} ll Longitude/Latitude coordinate pair.
|
|
309
|
-
* @param {
|
|
317
|
+
* @param {number} zoom Map zoom level.
|
|
310
318
|
*/
|
|
311
319
|
const precisePx = (ll, zoom) => {
|
|
312
320
|
const px = mercator.px(ll, 20);
|
|
@@ -316,12 +324,13 @@ const precisePx = (ll, zoom) => {
|
|
|
316
324
|
|
|
317
325
|
/**
|
|
318
326
|
* Draws a marker in cavans context.
|
|
319
|
-
*
|
|
320
|
-
* @param {
|
|
321
|
-
* @param {
|
|
327
|
+
*
|
|
328
|
+
* @param {object} ctx Canvas context object.
|
|
329
|
+
* @param {object} marker Marker object parsed by extractMarkersFromQuery.
|
|
330
|
+
* @param {number} z Map zoom level.
|
|
322
331
|
*/
|
|
323
332
|
const drawMarker = (ctx, marker, z) => {
|
|
324
|
-
return new Promise(resolve => {
|
|
333
|
+
return new Promise((resolve) => {
|
|
325
334
|
const img = new Image();
|
|
326
335
|
const pixelCoords = precisePx(marker.location, z);
|
|
327
336
|
|
|
@@ -340,15 +349,15 @@ const drawMarker = (ctx, marker, z) => {
|
|
|
340
349
|
// scaled as well. Additionally offsets are provided as either positive or
|
|
341
350
|
// negative values so we always add them
|
|
342
351
|
if (marker.offsetX) {
|
|
343
|
-
xCoordinate = xCoordinate +
|
|
352
|
+
xCoordinate = xCoordinate + marker.offsetX * scale;
|
|
344
353
|
}
|
|
345
354
|
if (marker.offsetY) {
|
|
346
|
-
yCoordinate = yCoordinate +
|
|
355
|
+
yCoordinate = yCoordinate + marker.offsetY * scale;
|
|
347
356
|
}
|
|
348
357
|
|
|
349
358
|
return {
|
|
350
|
-
|
|
351
|
-
|
|
359
|
+
x: xCoordinate,
|
|
360
|
+
y: yCoordinate,
|
|
352
361
|
};
|
|
353
362
|
};
|
|
354
363
|
|
|
@@ -375,19 +384,22 @@ const drawMarker = (ctx, marker, z) => {
|
|
|
375
384
|
};
|
|
376
385
|
|
|
377
386
|
img.onload = drawOnCanvas;
|
|
378
|
-
img.onerror = err => {
|
|
387
|
+
img.onerror = (err) => {
|
|
388
|
+
throw err;
|
|
389
|
+
};
|
|
379
390
|
img.src = marker.icon;
|
|
380
391
|
});
|
|
381
|
-
}
|
|
392
|
+
};
|
|
382
393
|
|
|
383
394
|
/**
|
|
384
395
|
* Draws a list of markers onto a canvas.
|
|
385
396
|
* Wraps drawing of markers into list of promises and awaits them.
|
|
386
397
|
* It's required because images are expected to load asynchronous in canvas js
|
|
387
398
|
* even when provided from a local disk.
|
|
388
|
-
*
|
|
399
|
+
*
|
|
400
|
+
* @param {object} ctx Canvas context object.
|
|
389
401
|
* @param {List[Object]} markers Marker objects parsed by extractMarkersFromQuery.
|
|
390
|
-
* @param {
|
|
402
|
+
* @param {number} z Map zoom level.
|
|
391
403
|
*/
|
|
392
404
|
const drawMarkers = async (ctx, markers, z) => {
|
|
393
405
|
const markerPromises = [];
|
|
@@ -399,14 +411,15 @@ const drawMarkers = async (ctx, markers, z) => {
|
|
|
399
411
|
|
|
400
412
|
// Await marker drawings before continuing
|
|
401
413
|
await Promise.all(markerPromises);
|
|
402
|
-
}
|
|
414
|
+
};
|
|
403
415
|
|
|
404
416
|
/**
|
|
405
417
|
* Draws a list of coordinates onto a canvas and styles the resulting path.
|
|
406
|
-
*
|
|
418
|
+
*
|
|
419
|
+
* @param {object} ctx Canvas context object.
|
|
407
420
|
* @param {List[Number]} path List of coordinates.
|
|
408
|
-
* @param {
|
|
409
|
-
* @param {
|
|
421
|
+
* @param {object} query Request query parameters.
|
|
422
|
+
* @param {number} z Map zoom level.
|
|
410
423
|
*/
|
|
411
424
|
const drawPath = (ctx, path, query, z) => {
|
|
412
425
|
if (!path || path.length < 2) {
|
|
@@ -422,8 +435,10 @@ const drawPath = (ctx, path, query, z) => {
|
|
|
422
435
|
}
|
|
423
436
|
|
|
424
437
|
// Check if first coordinate matches last coordinate
|
|
425
|
-
if (
|
|
426
|
-
path[0][
|
|
438
|
+
if (
|
|
439
|
+
path[0][0] === path[path.length - 1][0] &&
|
|
440
|
+
path[0][1] === path[path.length - 1][1]
|
|
441
|
+
) {
|
|
427
442
|
ctx.closePath();
|
|
428
443
|
}
|
|
429
444
|
|
|
@@ -434,14 +449,15 @@ const drawPath = (ctx, path, query, z) => {
|
|
|
434
449
|
}
|
|
435
450
|
|
|
436
451
|
// Get line width from query and fall back to 1 if not provided
|
|
437
|
-
const lineWidth = query.width !== undefined ?
|
|
438
|
-
parseFloat(query.width) : 1;
|
|
452
|
+
const lineWidth = query.width !== undefined ? parseFloat(query.width) : 1;
|
|
439
453
|
|
|
440
454
|
// Ensure line width is valid
|
|
441
455
|
if (lineWidth > 0) {
|
|
442
456
|
// Get border width from query and fall back to 10% of line width
|
|
443
|
-
const borderWidth =
|
|
444
|
-
|
|
457
|
+
const borderWidth =
|
|
458
|
+
query.borderwidth !== undefined
|
|
459
|
+
? parseFloat(query.borderwidth)
|
|
460
|
+
: lineWidth * 0.1;
|
|
445
461
|
|
|
446
462
|
// Set rendering style for the start and end points of the path
|
|
447
463
|
// https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineCap
|
|
@@ -456,7 +472,7 @@ const drawPath = (ctx, path, query, z) => {
|
|
|
456
472
|
if (query.border !== undefined && borderWidth > 0) {
|
|
457
473
|
// We need to double the desired border width and add it to the line width
|
|
458
474
|
// in order to get the desired border on each side of the line.
|
|
459
|
-
ctx.lineWidth = lineWidth +
|
|
475
|
+
ctx.lineWidth = lineWidth + borderWidth * 2;
|
|
460
476
|
// Set border style as rgba
|
|
461
477
|
ctx.strokeStyle = query.border;
|
|
462
478
|
ctx.stroke();
|
|
@@ -466,9 +482,21 @@ const drawPath = (ctx, path, query, z) => {
|
|
|
466
482
|
ctx.strokeStyle = query.stroke || 'rgba(0,64,255,0.7)';
|
|
467
483
|
ctx.stroke();
|
|
468
484
|
}
|
|
469
|
-
}
|
|
485
|
+
};
|
|
470
486
|
|
|
471
|
-
const renderOverlay = async (
|
|
487
|
+
const renderOverlay = async (
|
|
488
|
+
z,
|
|
489
|
+
x,
|
|
490
|
+
y,
|
|
491
|
+
bearing,
|
|
492
|
+
pitch,
|
|
493
|
+
w,
|
|
494
|
+
h,
|
|
495
|
+
scale,
|
|
496
|
+
paths,
|
|
497
|
+
markers,
|
|
498
|
+
query,
|
|
499
|
+
) => {
|
|
472
500
|
if ((!paths || paths.length === 0) && (!markers || markers.length === 0)) {
|
|
473
501
|
return null;
|
|
474
502
|
}
|
|
@@ -479,7 +507,7 @@ const renderOverlay = async (z, x, y, bearing, pitch, w, h, scale, paths, marker
|
|
|
479
507
|
const maxEdge = center[1] + h / 2;
|
|
480
508
|
const minEdge = center[1] - h / 2;
|
|
481
509
|
if (maxEdge > mapHeight) {
|
|
482
|
-
center[1] -=
|
|
510
|
+
center[1] -= maxEdge - mapHeight;
|
|
483
511
|
} else if (minEdge < 0) {
|
|
484
512
|
center[1] -= minEdge;
|
|
485
513
|
}
|
|
@@ -489,7 +517,7 @@ const renderOverlay = async (z, x, y, bearing, pitch, w, h, scale, paths, marker
|
|
|
489
517
|
ctx.scale(scale, scale);
|
|
490
518
|
if (bearing) {
|
|
491
519
|
ctx.translate(w / 2, h / 2);
|
|
492
|
-
ctx.rotate(-bearing / 180 * Math.PI);
|
|
520
|
+
ctx.rotate((-bearing / 180) * Math.PI);
|
|
493
521
|
ctx.translate(-center[0], -center[1]);
|
|
494
522
|
} else {
|
|
495
523
|
// optimized path
|
|
@@ -510,18 +538,18 @@ const renderOverlay = async (z, x, y, bearing, pitch, w, h, scale, paths, marker
|
|
|
510
538
|
const calcZForBBox = (bbox, w, h, query) => {
|
|
511
539
|
let z = 25;
|
|
512
540
|
|
|
513
|
-
const padding = query.padding !== undefined ?
|
|
514
|
-
parseFloat(query.padding) : 0.1;
|
|
541
|
+
const padding = query.padding !== undefined ? parseFloat(query.padding) : 0.1;
|
|
515
542
|
|
|
516
543
|
const minCorner = mercator.px([bbox[0], bbox[3]], z);
|
|
517
544
|
const maxCorner = mercator.px([bbox[2], bbox[1]], z);
|
|
518
545
|
const w_ = w / (1 + 2 * padding);
|
|
519
546
|
const h_ = h / (1 + 2 * padding);
|
|
520
547
|
|
|
521
|
-
z -=
|
|
548
|
+
z -=
|
|
549
|
+
Math.max(
|
|
522
550
|
Math.log((maxCorner[0] - minCorner[0]) / w_),
|
|
523
|
-
Math.log((maxCorner[1] - minCorner[1]) / h_)
|
|
524
|
-
|
|
551
|
+
Math.log((maxCorner[1] - minCorner[1]) / h_),
|
|
552
|
+
) / Math.LN2;
|
|
525
553
|
|
|
526
554
|
z = Math.max(Math.log(Math.max(w, h) / 256) / Math.LN2, Math.min(25, z));
|
|
527
555
|
|
|
@@ -563,14 +591,36 @@ export const serve_rendered = {
|
|
|
563
591
|
|
|
564
592
|
const app = express().disable('x-powered-by');
|
|
565
593
|
|
|
566
|
-
const respondImage = (
|
|
567
|
-
|
|
568
|
-
|
|
594
|
+
const respondImage = (
|
|
595
|
+
item,
|
|
596
|
+
z,
|
|
597
|
+
lon,
|
|
598
|
+
lat,
|
|
599
|
+
bearing,
|
|
600
|
+
pitch,
|
|
601
|
+
width,
|
|
602
|
+
height,
|
|
603
|
+
scale,
|
|
604
|
+
format,
|
|
605
|
+
res,
|
|
606
|
+
next,
|
|
607
|
+
opt_overlay,
|
|
608
|
+
opt_mode = 'tile',
|
|
609
|
+
) => {
|
|
610
|
+
if (
|
|
611
|
+
Math.abs(lon) > 180 ||
|
|
612
|
+
Math.abs(lat) > 85.06 ||
|
|
613
|
+
lon !== lon ||
|
|
614
|
+
lat !== lat
|
|
615
|
+
) {
|
|
569
616
|
return res.status(400).send('Invalid center');
|
|
570
617
|
}
|
|
571
|
-
if (
|
|
618
|
+
if (
|
|
619
|
+
Math.min(width, height) <= 0 ||
|
|
572
620
|
Math.max(width, height) * scale > (options.maxSize || 2048) ||
|
|
573
|
-
width !== width ||
|
|
621
|
+
width !== width ||
|
|
622
|
+
height !== height
|
|
623
|
+
) {
|
|
574
624
|
return res.status(400).send('Invalid size');
|
|
575
625
|
}
|
|
576
626
|
if (format === 'png' || format === 'webp') {
|
|
@@ -594,7 +644,7 @@ export const serve_rendered = {
|
|
|
594
644
|
bearing: bearing,
|
|
595
645
|
pitch: pitch,
|
|
596
646
|
width: width,
|
|
597
|
-
height: height
|
|
647
|
+
height: height,
|
|
598
648
|
};
|
|
599
649
|
if (z === 0) {
|
|
600
650
|
params.width *= 2;
|
|
@@ -611,7 +661,10 @@ export const serve_rendered = {
|
|
|
611
661
|
pool.release(renderer);
|
|
612
662
|
if (err) {
|
|
613
663
|
console.error(err);
|
|
614
|
-
return res
|
|
664
|
+
return res
|
|
665
|
+
.status(500)
|
|
666
|
+
.header('Content-Type', 'text/plain')
|
|
667
|
+
.send(err);
|
|
615
668
|
}
|
|
616
669
|
|
|
617
670
|
// Fix semi-transparent outlines on raw, premultiplied input
|
|
@@ -634,18 +687,21 @@ export const serve_rendered = {
|
|
|
634
687
|
raw: {
|
|
635
688
|
width: params.width * scale,
|
|
636
689
|
height: params.height * scale,
|
|
637
|
-
channels: 4
|
|
638
|
-
}
|
|
690
|
+
channels: 4,
|
|
691
|
+
},
|
|
639
692
|
});
|
|
640
693
|
|
|
641
694
|
if (z > 2 && tileMargin > 0) {
|
|
642
695
|
const [_, y] = mercator.px(params.center, z);
|
|
643
|
-
let yoffset = Math.max(
|
|
696
|
+
let yoffset = Math.max(
|
|
697
|
+
Math.min(0, y - 128 - tileMargin),
|
|
698
|
+
y + 128 + tileMargin - Math.pow(2, z + 8),
|
|
699
|
+
);
|
|
644
700
|
image.extract({
|
|
645
701
|
left: tileMargin * scale,
|
|
646
702
|
top: (tileMargin + yoffset) * scale,
|
|
647
703
|
width: width * scale,
|
|
648
|
-
height: height * scale
|
|
704
|
+
height: height * scale,
|
|
649
705
|
});
|
|
650
706
|
}
|
|
651
707
|
|
|
@@ -655,7 +711,7 @@ export const serve_rendered = {
|
|
|
655
711
|
}
|
|
656
712
|
|
|
657
713
|
if (opt_overlay) {
|
|
658
|
-
image.composite([{input: opt_overlay}]);
|
|
714
|
+
image.composite([{ input: opt_overlay }]);
|
|
659
715
|
}
|
|
660
716
|
if (item.watermark) {
|
|
661
717
|
const canvas = createCanvas(scale * width, scale * height);
|
|
@@ -668,17 +724,17 @@ export const serve_rendered = {
|
|
|
668
724
|
ctx.fillStyle = 'rgba(0,0,0,.4)';
|
|
669
725
|
ctx.fillText(item.watermark, 5, height - 5);
|
|
670
726
|
|
|
671
|
-
image.composite([{input: canvas.toBuffer()}]);
|
|
727
|
+
image.composite([{ input: canvas.toBuffer() }]);
|
|
672
728
|
}
|
|
673
729
|
|
|
674
730
|
const formatQuality = (options.formatQuality || {})[format];
|
|
675
731
|
|
|
676
732
|
if (format === 'png') {
|
|
677
|
-
image.png({adaptiveFiltering: false});
|
|
733
|
+
image.png({ adaptiveFiltering: false });
|
|
678
734
|
} else if (format === 'jpeg') {
|
|
679
|
-
image.jpeg({quality: formatQuality || 80});
|
|
735
|
+
image.jpeg({ quality: formatQuality || 80 });
|
|
680
736
|
} else if (format === 'webp') {
|
|
681
|
-
image.webp({quality: formatQuality || 90});
|
|
737
|
+
image.webp({ quality: formatQuality || 90 });
|
|
682
738
|
}
|
|
683
739
|
image.toBuffer((err, buffer, info) => {
|
|
684
740
|
if (!buffer) {
|
|
@@ -687,7 +743,7 @@ export const serve_rendered = {
|
|
|
687
743
|
|
|
688
744
|
res.set({
|
|
689
745
|
'Last-Modified': item.lastModified,
|
|
690
|
-
'Content-Type': `image/${format}
|
|
746
|
+
'Content-Type': `image/${format}`,
|
|
691
747
|
});
|
|
692
748
|
return res.status(200).send(buffer);
|
|
693
749
|
});
|
|
@@ -695,80 +751,144 @@ export const serve_rendered = {
|
|
|
695
751
|
});
|
|
696
752
|
};
|
|
697
753
|
|
|
698
|
-
app.get(
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
return res.sendStatus(404);
|
|
702
|
-
}
|
|
703
|
-
|
|
704
|
-
const modifiedSince = req.get('if-modified-since'); const cc = req.get('cache-control');
|
|
705
|
-
if (modifiedSince && (!cc || cc.indexOf('no-cache') === -1)) {
|
|
706
|
-
if (new Date(item.lastModified) <= new Date(modifiedSince)) {
|
|
707
|
-
return res.sendStatus(304);
|
|
708
|
-
}
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
const z = req.params.z | 0;
|
|
712
|
-
const x = req.params.x | 0;
|
|
713
|
-
const y = req.params.y | 0;
|
|
714
|
-
const scale = getScale(req.params.scale);
|
|
715
|
-
const format = req.params.format;
|
|
716
|
-
if (z < 0 || x < 0 || y < 0 ||
|
|
717
|
-
z > 22 || x >= Math.pow(2, z) || y >= Math.pow(2, z)) {
|
|
718
|
-
return res.status(404).send('Out of bounds');
|
|
719
|
-
}
|
|
720
|
-
const tileSize = 256;
|
|
721
|
-
const tileCenter = mercator.ll([
|
|
722
|
-
((x + 0.5) / (1 << z)) * (256 << z),
|
|
723
|
-
((y + 0.5) / (1 << z)) * (256 << z)
|
|
724
|
-
], z);
|
|
725
|
-
return respondImage(item, z, tileCenter[0], tileCenter[1], 0, 0, tileSize, tileSize, scale, format, res, next);
|
|
726
|
-
});
|
|
727
|
-
|
|
728
|
-
if (options.serveStaticMaps !== false) {
|
|
729
|
-
const staticPattern =
|
|
730
|
-
`/:id/static/:raw(raw)?/%s/:width(\\d+)x:height(\\d+):scale(${scalePattern})?.:format([\\w]+)`;
|
|
731
|
-
|
|
732
|
-
const centerPattern =
|
|
733
|
-
util.format(':x(%s),:y(%s),:z(%s)(@:bearing(%s)(,:pitch(%s))?)?',
|
|
734
|
-
FLOAT_PATTERN, FLOAT_PATTERN, FLOAT_PATTERN,
|
|
735
|
-
FLOAT_PATTERN, FLOAT_PATTERN);
|
|
736
|
-
|
|
737
|
-
app.get(util.format(staticPattern, centerPattern), async (req, res, next) => {
|
|
754
|
+
app.get(
|
|
755
|
+
`/:id/:z(\\d+)/:x(\\d+)/:y(\\d+):scale(${scalePattern})?.:format([\\w]+)`,
|
|
756
|
+
(req, res, next) => {
|
|
738
757
|
const item = repo[req.params.id];
|
|
739
758
|
if (!item) {
|
|
740
759
|
return res.sendStatus(404);
|
|
741
760
|
}
|
|
742
|
-
|
|
743
|
-
const
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
761
|
+
|
|
762
|
+
const modifiedSince = req.get('if-modified-since');
|
|
763
|
+
const cc = req.get('cache-control');
|
|
764
|
+
if (modifiedSince && (!cc || cc.indexOf('no-cache') === -1)) {
|
|
765
|
+
if (new Date(item.lastModified) <= new Date(modifiedSince)) {
|
|
766
|
+
return res.sendStatus(304);
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
const z = req.params.z | 0;
|
|
771
|
+
const x = req.params.x | 0;
|
|
772
|
+
const y = req.params.y | 0;
|
|
750
773
|
const scale = getScale(req.params.scale);
|
|
751
774
|
const format = req.params.format;
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
775
|
+
if (
|
|
776
|
+
z < 0 ||
|
|
777
|
+
x < 0 ||
|
|
778
|
+
y < 0 ||
|
|
779
|
+
z > 22 ||
|
|
780
|
+
x >= Math.pow(2, z) ||
|
|
781
|
+
y >= Math.pow(2, z)
|
|
782
|
+
) {
|
|
783
|
+
return res.status(404).send('Out of bounds');
|
|
755
784
|
}
|
|
785
|
+
const tileSize = 256;
|
|
786
|
+
const tileCenter = mercator.ll(
|
|
787
|
+
[
|
|
788
|
+
((x + 0.5) / (1 << z)) * (256 << z),
|
|
789
|
+
((y + 0.5) / (1 << z)) * (256 << z),
|
|
790
|
+
],
|
|
791
|
+
z,
|
|
792
|
+
);
|
|
793
|
+
return respondImage(
|
|
794
|
+
item,
|
|
795
|
+
z,
|
|
796
|
+
tileCenter[0],
|
|
797
|
+
tileCenter[1],
|
|
798
|
+
0,
|
|
799
|
+
0,
|
|
800
|
+
tileSize,
|
|
801
|
+
tileSize,
|
|
802
|
+
scale,
|
|
803
|
+
format,
|
|
804
|
+
res,
|
|
805
|
+
next,
|
|
806
|
+
);
|
|
807
|
+
},
|
|
808
|
+
);
|
|
756
809
|
|
|
757
|
-
|
|
758
|
-
|
|
810
|
+
if (options.serveStaticMaps !== false) {
|
|
811
|
+
const staticPattern = `/:id/static/:raw(raw)?/%s/:width(\\d+)x:height(\\d+):scale(${scalePattern})?.:format([\\w]+)`;
|
|
812
|
+
|
|
813
|
+
const centerPattern = util.format(
|
|
814
|
+
':x(%s),:y(%s),:z(%s)(@:bearing(%s)(,:pitch(%s))?)?',
|
|
815
|
+
FLOAT_PATTERN,
|
|
816
|
+
FLOAT_PATTERN,
|
|
817
|
+
FLOAT_PATTERN,
|
|
818
|
+
FLOAT_PATTERN,
|
|
819
|
+
FLOAT_PATTERN,
|
|
820
|
+
);
|
|
821
|
+
|
|
822
|
+
app.get(
|
|
823
|
+
util.format(staticPattern, centerPattern),
|
|
824
|
+
async (req, res, next) => {
|
|
825
|
+
const item = repo[req.params.id];
|
|
826
|
+
if (!item) {
|
|
827
|
+
return res.sendStatus(404);
|
|
828
|
+
}
|
|
829
|
+
const raw = req.params.raw;
|
|
830
|
+
const z = +req.params.z;
|
|
831
|
+
let x = +req.params.x;
|
|
832
|
+
let y = +req.params.y;
|
|
833
|
+
const bearing = +(req.params.bearing || '0');
|
|
834
|
+
const pitch = +(req.params.pitch || '0');
|
|
835
|
+
const w = req.params.width | 0;
|
|
836
|
+
const h = req.params.height | 0;
|
|
837
|
+
const scale = getScale(req.params.scale);
|
|
838
|
+
const format = req.params.format;
|
|
839
|
+
|
|
840
|
+
if (z < 0) {
|
|
841
|
+
return res.status(404).send('Invalid zoom');
|
|
842
|
+
}
|
|
759
843
|
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
y = ll[1];
|
|
764
|
-
}
|
|
844
|
+
const transformer = raw
|
|
845
|
+
? mercator.inverse.bind(mercator)
|
|
846
|
+
: item.dataProjWGStoInternalWGS;
|
|
765
847
|
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
848
|
+
if (transformer) {
|
|
849
|
+
const ll = transformer([x, y]);
|
|
850
|
+
x = ll[0];
|
|
851
|
+
y = ll[1];
|
|
852
|
+
}
|
|
769
853
|
|
|
770
|
-
|
|
771
|
-
|
|
854
|
+
const paths = extractPathsFromQuery(req.query, transformer);
|
|
855
|
+
const markers = extractMarkersFromQuery(
|
|
856
|
+
req.query,
|
|
857
|
+
options,
|
|
858
|
+
transformer,
|
|
859
|
+
);
|
|
860
|
+
const overlay = await renderOverlay(
|
|
861
|
+
z,
|
|
862
|
+
x,
|
|
863
|
+
y,
|
|
864
|
+
bearing,
|
|
865
|
+
pitch,
|
|
866
|
+
w,
|
|
867
|
+
h,
|
|
868
|
+
scale,
|
|
869
|
+
paths,
|
|
870
|
+
markers,
|
|
871
|
+
req.query,
|
|
872
|
+
);
|
|
873
|
+
|
|
874
|
+
return respondImage(
|
|
875
|
+
item,
|
|
876
|
+
z,
|
|
877
|
+
x,
|
|
878
|
+
y,
|
|
879
|
+
bearing,
|
|
880
|
+
pitch,
|
|
881
|
+
w,
|
|
882
|
+
h,
|
|
883
|
+
scale,
|
|
884
|
+
format,
|
|
885
|
+
res,
|
|
886
|
+
next,
|
|
887
|
+
overlay,
|
|
888
|
+
'static',
|
|
889
|
+
);
|
|
890
|
+
},
|
|
891
|
+
);
|
|
772
892
|
|
|
773
893
|
const serveBounds = async (req, res, next) => {
|
|
774
894
|
const item = repo[req.params.id];
|
|
@@ -776,11 +896,17 @@ export const serve_rendered = {
|
|
|
776
896
|
return res.sendStatus(404);
|
|
777
897
|
}
|
|
778
898
|
const raw = req.params.raw;
|
|
779
|
-
const bbox = [
|
|
899
|
+
const bbox = [
|
|
900
|
+
+req.params.minx,
|
|
901
|
+
+req.params.miny,
|
|
902
|
+
+req.params.maxx,
|
|
903
|
+
+req.params.maxy,
|
|
904
|
+
];
|
|
780
905
|
let center = [(bbox[0] + bbox[2]) / 2, (bbox[1] + bbox[3]) / 2];
|
|
781
906
|
|
|
782
|
-
const transformer = raw
|
|
783
|
-
mercator.inverse.bind(mercator)
|
|
907
|
+
const transformer = raw
|
|
908
|
+
? mercator.inverse.bind(mercator)
|
|
909
|
+
: item.dataProjWGStoInternalWGS;
|
|
784
910
|
|
|
785
911
|
if (transformer) {
|
|
786
912
|
const minCorner = transformer(bbox.slice(0, 2));
|
|
@@ -804,14 +930,49 @@ export const serve_rendered = {
|
|
|
804
930
|
const pitch = 0;
|
|
805
931
|
|
|
806
932
|
const paths = extractPathsFromQuery(req.query, transformer);
|
|
807
|
-
const markers = extractMarkersFromQuery(
|
|
808
|
-
|
|
809
|
-
|
|
933
|
+
const markers = extractMarkersFromQuery(
|
|
934
|
+
req.query,
|
|
935
|
+
options,
|
|
936
|
+
transformer,
|
|
937
|
+
);
|
|
938
|
+
const overlay = await renderOverlay(
|
|
939
|
+
z,
|
|
940
|
+
x,
|
|
941
|
+
y,
|
|
942
|
+
bearing,
|
|
943
|
+
pitch,
|
|
944
|
+
w,
|
|
945
|
+
h,
|
|
946
|
+
scale,
|
|
947
|
+
paths,
|
|
948
|
+
markers,
|
|
949
|
+
req.query,
|
|
950
|
+
);
|
|
951
|
+
return respondImage(
|
|
952
|
+
item,
|
|
953
|
+
z,
|
|
954
|
+
x,
|
|
955
|
+
y,
|
|
956
|
+
bearing,
|
|
957
|
+
pitch,
|
|
958
|
+
w,
|
|
959
|
+
h,
|
|
960
|
+
scale,
|
|
961
|
+
format,
|
|
962
|
+
res,
|
|
963
|
+
next,
|
|
964
|
+
overlay,
|
|
965
|
+
'static',
|
|
966
|
+
);
|
|
810
967
|
};
|
|
811
968
|
|
|
812
|
-
const boundsPattern =
|
|
813
|
-
|
|
814
|
-
|
|
969
|
+
const boundsPattern = util.format(
|
|
970
|
+
':minx(%s),:miny(%s),:maxx(%s),:maxy(%s)',
|
|
971
|
+
FLOAT_PATTERN,
|
|
972
|
+
FLOAT_PATTERN,
|
|
973
|
+
FLOAT_PATTERN,
|
|
974
|
+
FLOAT_PATTERN,
|
|
975
|
+
);
|
|
815
976
|
|
|
816
977
|
app.get(util.format(staticPattern, boundsPattern), serveBounds);
|
|
817
978
|
|
|
@@ -839,66 +1000,102 @@ export const serve_rendered = {
|
|
|
839
1000
|
|
|
840
1001
|
const autoPattern = 'auto';
|
|
841
1002
|
|
|
842
|
-
app.get(
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
}
|
|
874
|
-
|
|
875
|
-
const bbox = [Infinity, Infinity, -Infinity, -Infinity];
|
|
876
|
-
for (const pair of coords) {
|
|
877
|
-
bbox[0] = Math.min(bbox[0], pair[0]);
|
|
878
|
-
bbox[1] = Math.min(bbox[1], pair[1]);
|
|
879
|
-
bbox[2] = Math.max(bbox[2], pair[0]);
|
|
880
|
-
bbox[3] = Math.max(bbox[3], pair[1]);
|
|
881
|
-
}
|
|
1003
|
+
app.get(
|
|
1004
|
+
util.format(staticPattern, autoPattern),
|
|
1005
|
+
async (req, res, next) => {
|
|
1006
|
+
const item = repo[req.params.id];
|
|
1007
|
+
if (!item) {
|
|
1008
|
+
return res.sendStatus(404);
|
|
1009
|
+
}
|
|
1010
|
+
const raw = req.params.raw;
|
|
1011
|
+
const w = req.params.width | 0;
|
|
1012
|
+
const h = req.params.height | 0;
|
|
1013
|
+
const bearing = 0;
|
|
1014
|
+
const pitch = 0;
|
|
1015
|
+
const scale = getScale(req.params.scale);
|
|
1016
|
+
const format = req.params.format;
|
|
1017
|
+
|
|
1018
|
+
const transformer = raw
|
|
1019
|
+
? mercator.inverse.bind(mercator)
|
|
1020
|
+
: item.dataProjWGStoInternalWGS;
|
|
1021
|
+
|
|
1022
|
+
const paths = extractPathsFromQuery(req.query, transformer);
|
|
1023
|
+
const markers = extractMarkersFromQuery(
|
|
1024
|
+
req.query,
|
|
1025
|
+
options,
|
|
1026
|
+
transformer,
|
|
1027
|
+
);
|
|
1028
|
+
|
|
1029
|
+
// Extract coordinates from markers
|
|
1030
|
+
const markerCoordinates = [];
|
|
1031
|
+
for (const marker of markers) {
|
|
1032
|
+
markerCoordinates.push(marker.location);
|
|
1033
|
+
}
|
|
882
1034
|
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
[(bbox_[0] + bbox_[2]) / 2, (bbox_[1] + bbox_[3]) / 2]
|
|
886
|
-
);
|
|
1035
|
+
// Create array with coordinates from markers and path
|
|
1036
|
+
const coords = [].concat(paths.flat()).concat(markerCoordinates);
|
|
887
1037
|
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
z = Math.min(z, maxZoom);
|
|
893
|
-
}
|
|
1038
|
+
// Check if we have at least one coordinate to calculate a bounding box
|
|
1039
|
+
if (coords.length < 1) {
|
|
1040
|
+
return res.status(400).send('No coordinates provided');
|
|
1041
|
+
}
|
|
894
1042
|
|
|
895
|
-
|
|
896
|
-
|
|
1043
|
+
const bbox = [Infinity, Infinity, -Infinity, -Infinity];
|
|
1044
|
+
for (const pair of coords) {
|
|
1045
|
+
bbox[0] = Math.min(bbox[0], pair[0]);
|
|
1046
|
+
bbox[1] = Math.min(bbox[1], pair[1]);
|
|
1047
|
+
bbox[2] = Math.max(bbox[2], pair[0]);
|
|
1048
|
+
bbox[3] = Math.max(bbox[3], pair[1]);
|
|
1049
|
+
}
|
|
897
1050
|
|
|
898
|
-
|
|
1051
|
+
const bbox_ = mercator.convert(bbox, '900913');
|
|
1052
|
+
const center = mercator.inverse([
|
|
1053
|
+
(bbox_[0] + bbox_[2]) / 2,
|
|
1054
|
+
(bbox_[1] + bbox_[3]) / 2,
|
|
1055
|
+
]);
|
|
1056
|
+
|
|
1057
|
+
// Calculate zoom level
|
|
1058
|
+
const maxZoom = parseFloat(req.query.maxzoom);
|
|
1059
|
+
let z = calcZForBBox(bbox, w, h, req.query);
|
|
1060
|
+
if (maxZoom > 0) {
|
|
1061
|
+
z = Math.min(z, maxZoom);
|
|
1062
|
+
}
|
|
899
1063
|
|
|
900
|
-
|
|
901
|
-
|
|
1064
|
+
const x = center[0];
|
|
1065
|
+
const y = center[1];
|
|
1066
|
+
|
|
1067
|
+
const overlay = await renderOverlay(
|
|
1068
|
+
z,
|
|
1069
|
+
x,
|
|
1070
|
+
y,
|
|
1071
|
+
bearing,
|
|
1072
|
+
pitch,
|
|
1073
|
+
w,
|
|
1074
|
+
h,
|
|
1075
|
+
scale,
|
|
1076
|
+
paths,
|
|
1077
|
+
markers,
|
|
1078
|
+
req.query,
|
|
1079
|
+
);
|
|
1080
|
+
|
|
1081
|
+
return respondImage(
|
|
1082
|
+
item,
|
|
1083
|
+
z,
|
|
1084
|
+
x,
|
|
1085
|
+
y,
|
|
1086
|
+
bearing,
|
|
1087
|
+
pitch,
|
|
1088
|
+
w,
|
|
1089
|
+
h,
|
|
1090
|
+
scale,
|
|
1091
|
+
format,
|
|
1092
|
+
res,
|
|
1093
|
+
next,
|
|
1094
|
+
overlay,
|
|
1095
|
+
'static',
|
|
1096
|
+
);
|
|
1097
|
+
},
|
|
1098
|
+
);
|
|
902
1099
|
}
|
|
903
1100
|
|
|
904
1101
|
app.get('/:id.json', (req, res, next) => {
|
|
@@ -907,8 +1104,13 @@ export const serve_rendered = {
|
|
|
907
1104
|
return res.sendStatus(404);
|
|
908
1105
|
}
|
|
909
1106
|
const info = clone(item.tileJSON);
|
|
910
|
-
info.tiles = getTileUrls(
|
|
911
|
-
|
|
1107
|
+
info.tiles = getTileUrls(
|
|
1108
|
+
req,
|
|
1109
|
+
info.tiles,
|
|
1110
|
+
`styles/${req.params.id}`,
|
|
1111
|
+
info.format,
|
|
1112
|
+
item.publicUrl,
|
|
1113
|
+
);
|
|
912
1114
|
return res.send(info);
|
|
913
1115
|
});
|
|
914
1116
|
|
|
@@ -918,7 +1120,7 @@ export const serve_rendered = {
|
|
|
918
1120
|
const map = {
|
|
919
1121
|
renderers: [],
|
|
920
1122
|
renderers_static: [],
|
|
921
|
-
sources: {}
|
|
1123
|
+
sources: {},
|
|
922
1124
|
};
|
|
923
1125
|
|
|
924
1126
|
let styleJSON;
|
|
@@ -934,19 +1136,26 @@ export const serve_rendered = {
|
|
|
934
1136
|
const dir = options.paths[protocol];
|
|
935
1137
|
const file = unescape(req.url).substring(protocol.length + 3);
|
|
936
1138
|
fs.readFile(path.join(dir, file), (err, data) => {
|
|
937
|
-
callback(err, {data: data});
|
|
1139
|
+
callback(err, { data: data });
|
|
938
1140
|
});
|
|
939
1141
|
} else if (protocol === 'fonts') {
|
|
940
1142
|
const parts = req.url.split('/');
|
|
941
1143
|
const fontstack = unescape(parts[2]);
|
|
942
1144
|
const range = parts[3].split('.')[0];
|
|
943
1145
|
getFontsPbf(
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
1146
|
+
null,
|
|
1147
|
+
options.paths[protocol],
|
|
1148
|
+
fontstack,
|
|
1149
|
+
range,
|
|
1150
|
+
existingFonts,
|
|
1151
|
+
).then(
|
|
1152
|
+
(concated) => {
|
|
1153
|
+
callback(null, { data: concated });
|
|
1154
|
+
},
|
|
1155
|
+
(err) => {
|
|
1156
|
+
callback(err, { data: null });
|
|
1157
|
+
},
|
|
1158
|
+
);
|
|
950
1159
|
} else if (protocol === 'mbtiles') {
|
|
951
1160
|
const parts = req.url.split('/');
|
|
952
1161
|
const sourceId = parts[2];
|
|
@@ -958,8 +1167,13 @@ export const serve_rendered = {
|
|
|
958
1167
|
const format = parts[5].split('.')[1];
|
|
959
1168
|
source.getTile(z, x, y, (err, data, headers) => {
|
|
960
1169
|
if (err) {
|
|
961
|
-
if (options.verbose)
|
|
962
|
-
|
|
1170
|
+
if (options.verbose)
|
|
1171
|
+
console.log('MBTiles error, serving empty', err);
|
|
1172
|
+
createEmptyResponse(
|
|
1173
|
+
sourceInfo.format,
|
|
1174
|
+
sourceInfo.color,
|
|
1175
|
+
callback,
|
|
1176
|
+
);
|
|
963
1177
|
return;
|
|
964
1178
|
}
|
|
965
1179
|
|
|
@@ -972,11 +1186,23 @@ export const serve_rendered = {
|
|
|
972
1186
|
try {
|
|
973
1187
|
response.data = zlib.unzipSync(data);
|
|
974
1188
|
} catch (err) {
|
|
975
|
-
console.log(
|
|
1189
|
+
console.log(
|
|
1190
|
+
'Skipping incorrect header for tile mbtiles://%s/%s/%s/%s.pbf',
|
|
1191
|
+
id,
|
|
1192
|
+
z,
|
|
1193
|
+
x,
|
|
1194
|
+
y,
|
|
1195
|
+
);
|
|
976
1196
|
}
|
|
977
1197
|
if (options.dataDecoratorFunc) {
|
|
978
1198
|
response.data = options.dataDecoratorFunc(
|
|
979
|
-
|
|
1199
|
+
sourceId,
|
|
1200
|
+
'data',
|
|
1201
|
+
response.data,
|
|
1202
|
+
z,
|
|
1203
|
+
x,
|
|
1204
|
+
y,
|
|
1205
|
+
);
|
|
980
1206
|
}
|
|
981
1207
|
} else {
|
|
982
1208
|
response.data = data;
|
|
@@ -985,36 +1211,39 @@ export const serve_rendered = {
|
|
|
985
1211
|
callback(null, response);
|
|
986
1212
|
});
|
|
987
1213
|
} else if (protocol === 'http' || protocol === 'https') {
|
|
988
|
-
request(
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1214
|
+
request(
|
|
1215
|
+
{
|
|
1216
|
+
url: req.url,
|
|
1217
|
+
encoding: null,
|
|
1218
|
+
gzip: true,
|
|
1219
|
+
},
|
|
1220
|
+
(err, res, body) => {
|
|
1221
|
+
const parts = url.parse(req.url);
|
|
1222
|
+
const extension = path.extname(parts.pathname).toLowerCase();
|
|
1223
|
+
const format = extensionToFormat[extension] || '';
|
|
1224
|
+
if (err || res.statusCode < 200 || res.statusCode >= 300) {
|
|
1225
|
+
// console.log('HTTP error', err || res.statusCode);
|
|
1226
|
+
createEmptyResponse(format, '', callback);
|
|
1227
|
+
return;
|
|
1228
|
+
}
|
|
1001
1229
|
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1230
|
+
const response = {};
|
|
1231
|
+
if (res.headers.modified) {
|
|
1232
|
+
response.modified = new Date(res.headers.modified);
|
|
1233
|
+
}
|
|
1234
|
+
if (res.headers.expires) {
|
|
1235
|
+
response.expires = new Date(res.headers.expires);
|
|
1236
|
+
}
|
|
1237
|
+
if (res.headers.etag) {
|
|
1238
|
+
response.etag = res.headers.etag;
|
|
1239
|
+
}
|
|
1012
1240
|
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1241
|
+
response.data = body;
|
|
1242
|
+
callback(null, response);
|
|
1243
|
+
},
|
|
1244
|
+
);
|
|
1016
1245
|
}
|
|
1017
|
-
}
|
|
1246
|
+
},
|
|
1018
1247
|
});
|
|
1019
1248
|
renderer.load(styleJSON);
|
|
1020
1249
|
createCallback(null, renderer);
|
|
@@ -1025,7 +1254,7 @@ export const serve_rendered = {
|
|
|
1025
1254
|
create: createRenderer.bind(null, ratio),
|
|
1026
1255
|
destroy: (renderer) => {
|
|
1027
1256
|
renderer.release();
|
|
1028
|
-
}
|
|
1257
|
+
},
|
|
1029
1258
|
});
|
|
1030
1259
|
};
|
|
1031
1260
|
|
|
@@ -1039,16 +1268,20 @@ export const serve_rendered = {
|
|
|
1039
1268
|
}
|
|
1040
1269
|
|
|
1041
1270
|
if (styleJSON.sprite && !httpTester.test(styleJSON.sprite)) {
|
|
1042
|
-
styleJSON.sprite =
|
|
1271
|
+
styleJSON.sprite =
|
|
1272
|
+
'sprites://' +
|
|
1043
1273
|
styleJSON.sprite
|
|
1044
|
-
|
|
1045
|
-
|
|
1274
|
+
.replace('{style}', path.basename(styleFile, '.json'))
|
|
1275
|
+
.replace(
|
|
1276
|
+
'{styleJsonFolder}',
|
|
1277
|
+
path.relative(options.paths.sprites, path.dirname(styleJSONPath)),
|
|
1278
|
+
);
|
|
1046
1279
|
}
|
|
1047
1280
|
if (styleJSON.glyphs && !httpTester.test(styleJSON.glyphs)) {
|
|
1048
1281
|
styleJSON.glyphs = `fonts://${styleJSON.glyphs}`;
|
|
1049
1282
|
}
|
|
1050
1283
|
|
|
1051
|
-
for (const layer of
|
|
1284
|
+
for (const layer of styleJSON.layers || []) {
|
|
1052
1285
|
if (layer && layer.paint) {
|
|
1053
1286
|
// Remove (flatten) 3D buildings
|
|
1054
1287
|
if (layer.paint['fill-extrusion-height']) {
|
|
@@ -1061,14 +1294,14 @@ export const serve_rendered = {
|
|
|
1061
1294
|
}
|
|
1062
1295
|
|
|
1063
1296
|
const tileJSON = {
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1297
|
+
tilejson: '2.0.0',
|
|
1298
|
+
name: styleJSON.name,
|
|
1299
|
+
attribution: '',
|
|
1300
|
+
minzoom: 0,
|
|
1301
|
+
maxzoom: 20,
|
|
1302
|
+
bounds: [-180, -85.0511, 180, 85.0511],
|
|
1303
|
+
format: 'png',
|
|
1304
|
+
type: 'baselayer',
|
|
1072
1305
|
};
|
|
1073
1306
|
const attributionOverride = params.tilejson && params.tilejson.attribution;
|
|
1074
1307
|
Object.assign(tileJSON, params.tilejson || {});
|
|
@@ -1081,7 +1314,7 @@ export const serve_rendered = {
|
|
|
1081
1314
|
map,
|
|
1082
1315
|
dataProjWGStoInternalWGS: null,
|
|
1083
1316
|
lastModified: new Date().toUTCString(),
|
|
1084
|
-
watermark: params.watermark || options.watermark
|
|
1317
|
+
watermark: params.watermark || options.watermark,
|
|
1085
1318
|
};
|
|
1086
1319
|
repo[id] = repoobj;
|
|
1087
1320
|
|
|
@@ -1095,8 +1328,8 @@ export const serve_rendered = {
|
|
|
1095
1328
|
delete source.url;
|
|
1096
1329
|
|
|
1097
1330
|
let mbtilesFile = url.substring('mbtiles://'.length);
|
|
1098
|
-
const fromData =
|
|
1099
|
-
mbtilesFile[mbtilesFile.length - 1] === '}';
|
|
1331
|
+
const fromData =
|
|
1332
|
+
mbtilesFile[0] === '{' && mbtilesFile[mbtilesFile.length - 1] === '}';
|
|
1100
1333
|
|
|
1101
1334
|
if (fromData) {
|
|
1102
1335
|
mbtilesFile = mbtilesFile.substr(1, mbtilesFile.length - 2);
|
|
@@ -1111,52 +1344,58 @@ export const serve_rendered = {
|
|
|
1111
1344
|
}
|
|
1112
1345
|
}
|
|
1113
1346
|
|
|
1114
|
-
queue.push(
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
map.sources[name]
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
if (
|
|
1150
|
-
|
|
1151
|
-
|
|
1347
|
+
queue.push(
|
|
1348
|
+
new Promise((resolve, reject) => {
|
|
1349
|
+
mbtilesFile = path.resolve(options.paths.mbtiles, mbtilesFile);
|
|
1350
|
+
const mbtilesFileStats = fs.statSync(mbtilesFile);
|
|
1351
|
+
if (!mbtilesFileStats.isFile() || mbtilesFileStats.size === 0) {
|
|
1352
|
+
throw Error(`Not valid MBTiles file: ${mbtilesFile}`);
|
|
1353
|
+
}
|
|
1354
|
+
map.sources[name] = new MBTiles(mbtilesFile + '?mode=ro', (err) => {
|
|
1355
|
+
map.sources[name].getInfo((err, info) => {
|
|
1356
|
+
if (err) {
|
|
1357
|
+
console.error(err);
|
|
1358
|
+
return;
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
if (!repoobj.dataProjWGStoInternalWGS && info.proj4) {
|
|
1362
|
+
// how to do this for multiple sources with different proj4 defs?
|
|
1363
|
+
const to3857 = proj4('EPSG:3857');
|
|
1364
|
+
const toDataProj = proj4(info.proj4);
|
|
1365
|
+
repoobj.dataProjWGStoInternalWGS = (xy) =>
|
|
1366
|
+
to3857.inverse(toDataProj.forward(xy));
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
const type = source.type;
|
|
1370
|
+
Object.assign(source, info);
|
|
1371
|
+
source.type = type;
|
|
1372
|
+
source.tiles = [
|
|
1373
|
+
// meta url which will be detected when requested
|
|
1374
|
+
`mbtiles://${name}/{z}/{x}/{y}.${info.format || 'pbf'}`,
|
|
1375
|
+
];
|
|
1376
|
+
delete source.scheme;
|
|
1377
|
+
|
|
1378
|
+
if (options.dataDecoratorFunc) {
|
|
1379
|
+
source = options.dataDecoratorFunc(name, 'tilejson', source);
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
if (
|
|
1383
|
+
!attributionOverride &&
|
|
1384
|
+
source.attribution &&
|
|
1385
|
+
source.attribution.length > 0
|
|
1386
|
+
) {
|
|
1387
|
+
if (!tileJSON.attribution.includes(source.attribution)) {
|
|
1388
|
+
if (tileJSON.attribution.length > 0) {
|
|
1389
|
+
tileJSON.attribution += ' | ';
|
|
1390
|
+
}
|
|
1391
|
+
tileJSON.attribution += source.attribution;
|
|
1152
1392
|
}
|
|
1153
|
-
tileJSON.attribution += source.attribution;
|
|
1154
1393
|
}
|
|
1155
|
-
|
|
1156
|
-
|
|
1394
|
+
resolve();
|
|
1395
|
+
});
|
|
1157
1396
|
});
|
|
1158
|
-
})
|
|
1159
|
-
|
|
1397
|
+
}),
|
|
1398
|
+
);
|
|
1160
1399
|
}
|
|
1161
1400
|
}
|
|
1162
1401
|
|
|
@@ -1170,7 +1409,12 @@ export const serve_rendered = {
|
|
|
1170
1409
|
const minPoolSize = minPoolSizes[i];
|
|
1171
1410
|
const maxPoolSize = Math.max(minPoolSize, maxPoolSizes[j]);
|
|
1172
1411
|
map.renderers[s] = createPool(s, 'tile', minPoolSize, maxPoolSize);
|
|
1173
|
-
map.renderers_static[s] = createPool(
|
|
1412
|
+
map.renderers_static[s] = createPool(
|
|
1413
|
+
s,
|
|
1414
|
+
'static',
|
|
1415
|
+
minPoolSize,
|
|
1416
|
+
maxPoolSize,
|
|
1417
|
+
);
|
|
1174
1418
|
}
|
|
1175
1419
|
});
|
|
1176
1420
|
|
|
@@ -1187,5 +1431,5 @@ export const serve_rendered = {
|
|
|
1187
1431
|
});
|
|
1188
1432
|
}
|
|
1189
1433
|
delete repo[id];
|
|
1190
|
-
}
|
|
1434
|
+
},
|
|
1191
1435
|
};
|