tileserver-gl-light 4.1.2 → 4.2.0
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/.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/lint-staged.config.cjs +4 -0
- package/package.json +28 -11
- 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 +99 -82
- package/src/serve_font.js +19 -10
- package/src/serve_light.js +5 -6
- package/src/serve_rendered.js +550 -309
- 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/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;
|
|
@@ -634,18 +684,21 @@ export const serve_rendered = {
|
|
|
634
684
|
raw: {
|
|
635
685
|
width: params.width * scale,
|
|
636
686
|
height: params.height * scale,
|
|
637
|
-
channels: 4
|
|
638
|
-
}
|
|
687
|
+
channels: 4,
|
|
688
|
+
},
|
|
639
689
|
});
|
|
640
690
|
|
|
641
691
|
if (z > 2 && tileMargin > 0) {
|
|
642
692
|
const [_, y] = mercator.px(params.center, z);
|
|
643
|
-
let yoffset = Math.max(
|
|
693
|
+
let yoffset = Math.max(
|
|
694
|
+
Math.min(0, y - 128 - tileMargin),
|
|
695
|
+
y + 128 + tileMargin - Math.pow(2, z + 8),
|
|
696
|
+
);
|
|
644
697
|
image.extract({
|
|
645
698
|
left: tileMargin * scale,
|
|
646
699
|
top: (tileMargin + yoffset) * scale,
|
|
647
700
|
width: width * scale,
|
|
648
|
-
height: height * scale
|
|
701
|
+
height: height * scale,
|
|
649
702
|
});
|
|
650
703
|
}
|
|
651
704
|
|
|
@@ -655,7 +708,7 @@ export const serve_rendered = {
|
|
|
655
708
|
}
|
|
656
709
|
|
|
657
710
|
if (opt_overlay) {
|
|
658
|
-
image.composite([{input: opt_overlay}]);
|
|
711
|
+
image.composite([{ input: opt_overlay }]);
|
|
659
712
|
}
|
|
660
713
|
if (item.watermark) {
|
|
661
714
|
const canvas = createCanvas(scale * width, scale * height);
|
|
@@ -668,17 +721,17 @@ export const serve_rendered = {
|
|
|
668
721
|
ctx.fillStyle = 'rgba(0,0,0,.4)';
|
|
669
722
|
ctx.fillText(item.watermark, 5, height - 5);
|
|
670
723
|
|
|
671
|
-
image.composite([{input: canvas.toBuffer()}]);
|
|
724
|
+
image.composite([{ input: canvas.toBuffer() }]);
|
|
672
725
|
}
|
|
673
726
|
|
|
674
727
|
const formatQuality = (options.formatQuality || {})[format];
|
|
675
728
|
|
|
676
729
|
if (format === 'png') {
|
|
677
|
-
image.png({adaptiveFiltering: false});
|
|
730
|
+
image.png({ adaptiveFiltering: false });
|
|
678
731
|
} else if (format === 'jpeg') {
|
|
679
|
-
image.jpeg({quality: formatQuality || 80});
|
|
732
|
+
image.jpeg({ quality: formatQuality || 80 });
|
|
680
733
|
} else if (format === 'webp') {
|
|
681
|
-
image.webp({quality: formatQuality || 90});
|
|
734
|
+
image.webp({ quality: formatQuality || 90 });
|
|
682
735
|
}
|
|
683
736
|
image.toBuffer((err, buffer, info) => {
|
|
684
737
|
if (!buffer) {
|
|
@@ -687,7 +740,7 @@ export const serve_rendered = {
|
|
|
687
740
|
|
|
688
741
|
res.set({
|
|
689
742
|
'Last-Modified': item.lastModified,
|
|
690
|
-
'Content-Type': `image/${format}
|
|
743
|
+
'Content-Type': `image/${format}`,
|
|
691
744
|
});
|
|
692
745
|
return res.status(200).send(buffer);
|
|
693
746
|
});
|
|
@@ -695,80 +748,144 @@ export const serve_rendered = {
|
|
|
695
748
|
});
|
|
696
749
|
};
|
|
697
750
|
|
|
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) => {
|
|
751
|
+
app.get(
|
|
752
|
+
`/:id/:z(\\d+)/:x(\\d+)/:y(\\d+):scale(${scalePattern})?.:format([\\w]+)`,
|
|
753
|
+
(req, res, next) => {
|
|
738
754
|
const item = repo[req.params.id];
|
|
739
755
|
if (!item) {
|
|
740
756
|
return res.sendStatus(404);
|
|
741
757
|
}
|
|
742
|
-
|
|
743
|
-
const
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
758
|
+
|
|
759
|
+
const modifiedSince = req.get('if-modified-since');
|
|
760
|
+
const cc = req.get('cache-control');
|
|
761
|
+
if (modifiedSince && (!cc || cc.indexOf('no-cache') === -1)) {
|
|
762
|
+
if (new Date(item.lastModified) <= new Date(modifiedSince)) {
|
|
763
|
+
return res.sendStatus(304);
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
const z = req.params.z | 0;
|
|
768
|
+
const x = req.params.x | 0;
|
|
769
|
+
const y = req.params.y | 0;
|
|
750
770
|
const scale = getScale(req.params.scale);
|
|
751
771
|
const format = req.params.format;
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
772
|
+
if (
|
|
773
|
+
z < 0 ||
|
|
774
|
+
x < 0 ||
|
|
775
|
+
y < 0 ||
|
|
776
|
+
z > 22 ||
|
|
777
|
+
x >= Math.pow(2, z) ||
|
|
778
|
+
y >= Math.pow(2, z)
|
|
779
|
+
) {
|
|
780
|
+
return res.status(404).send('Out of bounds');
|
|
755
781
|
}
|
|
782
|
+
const tileSize = 256;
|
|
783
|
+
const tileCenter = mercator.ll(
|
|
784
|
+
[
|
|
785
|
+
((x + 0.5) / (1 << z)) * (256 << z),
|
|
786
|
+
((y + 0.5) / (1 << z)) * (256 << z),
|
|
787
|
+
],
|
|
788
|
+
z,
|
|
789
|
+
);
|
|
790
|
+
return respondImage(
|
|
791
|
+
item,
|
|
792
|
+
z,
|
|
793
|
+
tileCenter[0],
|
|
794
|
+
tileCenter[1],
|
|
795
|
+
0,
|
|
796
|
+
0,
|
|
797
|
+
tileSize,
|
|
798
|
+
tileSize,
|
|
799
|
+
scale,
|
|
800
|
+
format,
|
|
801
|
+
res,
|
|
802
|
+
next,
|
|
803
|
+
);
|
|
804
|
+
},
|
|
805
|
+
);
|
|
756
806
|
|
|
757
|
-
|
|
758
|
-
|
|
807
|
+
if (options.serveStaticMaps !== false) {
|
|
808
|
+
const staticPattern = `/:id/static/:raw(raw)?/%s/:width(\\d+)x:height(\\d+):scale(${scalePattern})?.:format([\\w]+)`;
|
|
809
|
+
|
|
810
|
+
const centerPattern = util.format(
|
|
811
|
+
':x(%s),:y(%s),:z(%s)(@:bearing(%s)(,:pitch(%s))?)?',
|
|
812
|
+
FLOAT_PATTERN,
|
|
813
|
+
FLOAT_PATTERN,
|
|
814
|
+
FLOAT_PATTERN,
|
|
815
|
+
FLOAT_PATTERN,
|
|
816
|
+
FLOAT_PATTERN,
|
|
817
|
+
);
|
|
818
|
+
|
|
819
|
+
app.get(
|
|
820
|
+
util.format(staticPattern, centerPattern),
|
|
821
|
+
async (req, res, next) => {
|
|
822
|
+
const item = repo[req.params.id];
|
|
823
|
+
if (!item) {
|
|
824
|
+
return res.sendStatus(404);
|
|
825
|
+
}
|
|
826
|
+
const raw = req.params.raw;
|
|
827
|
+
const z = +req.params.z;
|
|
828
|
+
let x = +req.params.x;
|
|
829
|
+
let y = +req.params.y;
|
|
830
|
+
const bearing = +(req.params.bearing || '0');
|
|
831
|
+
const pitch = +(req.params.pitch || '0');
|
|
832
|
+
const w = req.params.width | 0;
|
|
833
|
+
const h = req.params.height | 0;
|
|
834
|
+
const scale = getScale(req.params.scale);
|
|
835
|
+
const format = req.params.format;
|
|
836
|
+
|
|
837
|
+
if (z < 0) {
|
|
838
|
+
return res.status(404).send('Invalid zoom');
|
|
839
|
+
}
|
|
759
840
|
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
y = ll[1];
|
|
764
|
-
}
|
|
841
|
+
const transformer = raw
|
|
842
|
+
? mercator.inverse.bind(mercator)
|
|
843
|
+
: item.dataProjWGStoInternalWGS;
|
|
765
844
|
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
845
|
+
if (transformer) {
|
|
846
|
+
const ll = transformer([x, y]);
|
|
847
|
+
x = ll[0];
|
|
848
|
+
y = ll[1];
|
|
849
|
+
}
|
|
769
850
|
|
|
770
|
-
|
|
771
|
-
|
|
851
|
+
const paths = extractPathsFromQuery(req.query, transformer);
|
|
852
|
+
const markers = extractMarkersFromQuery(
|
|
853
|
+
req.query,
|
|
854
|
+
options,
|
|
855
|
+
transformer,
|
|
856
|
+
);
|
|
857
|
+
const overlay = await renderOverlay(
|
|
858
|
+
z,
|
|
859
|
+
x,
|
|
860
|
+
y,
|
|
861
|
+
bearing,
|
|
862
|
+
pitch,
|
|
863
|
+
w,
|
|
864
|
+
h,
|
|
865
|
+
scale,
|
|
866
|
+
paths,
|
|
867
|
+
markers,
|
|
868
|
+
req.query,
|
|
869
|
+
);
|
|
870
|
+
|
|
871
|
+
return respondImage(
|
|
872
|
+
item,
|
|
873
|
+
z,
|
|
874
|
+
x,
|
|
875
|
+
y,
|
|
876
|
+
bearing,
|
|
877
|
+
pitch,
|
|
878
|
+
w,
|
|
879
|
+
h,
|
|
880
|
+
scale,
|
|
881
|
+
format,
|
|
882
|
+
res,
|
|
883
|
+
next,
|
|
884
|
+
overlay,
|
|
885
|
+
'static',
|
|
886
|
+
);
|
|
887
|
+
},
|
|
888
|
+
);
|
|
772
889
|
|
|
773
890
|
const serveBounds = async (req, res, next) => {
|
|
774
891
|
const item = repo[req.params.id];
|
|
@@ -776,11 +893,17 @@ export const serve_rendered = {
|
|
|
776
893
|
return res.sendStatus(404);
|
|
777
894
|
}
|
|
778
895
|
const raw = req.params.raw;
|
|
779
|
-
const bbox = [
|
|
896
|
+
const bbox = [
|
|
897
|
+
+req.params.minx,
|
|
898
|
+
+req.params.miny,
|
|
899
|
+
+req.params.maxx,
|
|
900
|
+
+req.params.maxy,
|
|
901
|
+
];
|
|
780
902
|
let center = [(bbox[0] + bbox[2]) / 2, (bbox[1] + bbox[3]) / 2];
|
|
781
903
|
|
|
782
|
-
const transformer = raw
|
|
783
|
-
mercator.inverse.bind(mercator)
|
|
904
|
+
const transformer = raw
|
|
905
|
+
? mercator.inverse.bind(mercator)
|
|
906
|
+
: item.dataProjWGStoInternalWGS;
|
|
784
907
|
|
|
785
908
|
if (transformer) {
|
|
786
909
|
const minCorner = transformer(bbox.slice(0, 2));
|
|
@@ -804,14 +927,49 @@ export const serve_rendered = {
|
|
|
804
927
|
const pitch = 0;
|
|
805
928
|
|
|
806
929
|
const paths = extractPathsFromQuery(req.query, transformer);
|
|
807
|
-
const markers = extractMarkersFromQuery(
|
|
808
|
-
|
|
809
|
-
|
|
930
|
+
const markers = extractMarkersFromQuery(
|
|
931
|
+
req.query,
|
|
932
|
+
options,
|
|
933
|
+
transformer,
|
|
934
|
+
);
|
|
935
|
+
const overlay = await renderOverlay(
|
|
936
|
+
z,
|
|
937
|
+
x,
|
|
938
|
+
y,
|
|
939
|
+
bearing,
|
|
940
|
+
pitch,
|
|
941
|
+
w,
|
|
942
|
+
h,
|
|
943
|
+
scale,
|
|
944
|
+
paths,
|
|
945
|
+
markers,
|
|
946
|
+
req.query,
|
|
947
|
+
);
|
|
948
|
+
return respondImage(
|
|
949
|
+
item,
|
|
950
|
+
z,
|
|
951
|
+
x,
|
|
952
|
+
y,
|
|
953
|
+
bearing,
|
|
954
|
+
pitch,
|
|
955
|
+
w,
|
|
956
|
+
h,
|
|
957
|
+
scale,
|
|
958
|
+
format,
|
|
959
|
+
res,
|
|
960
|
+
next,
|
|
961
|
+
overlay,
|
|
962
|
+
'static',
|
|
963
|
+
);
|
|
810
964
|
};
|
|
811
965
|
|
|
812
|
-
const boundsPattern =
|
|
813
|
-
|
|
814
|
-
|
|
966
|
+
const boundsPattern = util.format(
|
|
967
|
+
':minx(%s),:miny(%s),:maxx(%s),:maxy(%s)',
|
|
968
|
+
FLOAT_PATTERN,
|
|
969
|
+
FLOAT_PATTERN,
|
|
970
|
+
FLOAT_PATTERN,
|
|
971
|
+
FLOAT_PATTERN,
|
|
972
|
+
);
|
|
815
973
|
|
|
816
974
|
app.get(util.format(staticPattern, boundsPattern), serveBounds);
|
|
817
975
|
|
|
@@ -839,66 +997,102 @@ export const serve_rendered = {
|
|
|
839
997
|
|
|
840
998
|
const autoPattern = 'auto';
|
|
841
999
|
|
|
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
|
-
}
|
|
1000
|
+
app.get(
|
|
1001
|
+
util.format(staticPattern, autoPattern),
|
|
1002
|
+
async (req, res, next) => {
|
|
1003
|
+
const item = repo[req.params.id];
|
|
1004
|
+
if (!item) {
|
|
1005
|
+
return res.sendStatus(404);
|
|
1006
|
+
}
|
|
1007
|
+
const raw = req.params.raw;
|
|
1008
|
+
const w = req.params.width | 0;
|
|
1009
|
+
const h = req.params.height | 0;
|
|
1010
|
+
const bearing = 0;
|
|
1011
|
+
const pitch = 0;
|
|
1012
|
+
const scale = getScale(req.params.scale);
|
|
1013
|
+
const format = req.params.format;
|
|
1014
|
+
|
|
1015
|
+
const transformer = raw
|
|
1016
|
+
? mercator.inverse.bind(mercator)
|
|
1017
|
+
: item.dataProjWGStoInternalWGS;
|
|
1018
|
+
|
|
1019
|
+
const paths = extractPathsFromQuery(req.query, transformer);
|
|
1020
|
+
const markers = extractMarkersFromQuery(
|
|
1021
|
+
req.query,
|
|
1022
|
+
options,
|
|
1023
|
+
transformer,
|
|
1024
|
+
);
|
|
1025
|
+
|
|
1026
|
+
// Extract coordinates from markers
|
|
1027
|
+
const markerCoordinates = [];
|
|
1028
|
+
for (const marker of markers) {
|
|
1029
|
+
markerCoordinates.push(marker.location);
|
|
1030
|
+
}
|
|
882
1031
|
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
[(bbox_[0] + bbox_[2]) / 2, (bbox_[1] + bbox_[3]) / 2]
|
|
886
|
-
);
|
|
1032
|
+
// Create array with coordinates from markers and path
|
|
1033
|
+
const coords = [].concat(paths.flat()).concat(markerCoordinates);
|
|
887
1034
|
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
z = Math.min(z, maxZoom);
|
|
893
|
-
}
|
|
1035
|
+
// Check if we have at least one coordinate to calculate a bounding box
|
|
1036
|
+
if (coords.length < 1) {
|
|
1037
|
+
return res.status(400).send('No coordinates provided');
|
|
1038
|
+
}
|
|
894
1039
|
|
|
895
|
-
|
|
896
|
-
|
|
1040
|
+
const bbox = [Infinity, Infinity, -Infinity, -Infinity];
|
|
1041
|
+
for (const pair of coords) {
|
|
1042
|
+
bbox[0] = Math.min(bbox[0], pair[0]);
|
|
1043
|
+
bbox[1] = Math.min(bbox[1], pair[1]);
|
|
1044
|
+
bbox[2] = Math.max(bbox[2], pair[0]);
|
|
1045
|
+
bbox[3] = Math.max(bbox[3], pair[1]);
|
|
1046
|
+
}
|
|
897
1047
|
|
|
898
|
-
|
|
1048
|
+
const bbox_ = mercator.convert(bbox, '900913');
|
|
1049
|
+
const center = mercator.inverse([
|
|
1050
|
+
(bbox_[0] + bbox_[2]) / 2,
|
|
1051
|
+
(bbox_[1] + bbox_[3]) / 2,
|
|
1052
|
+
]);
|
|
1053
|
+
|
|
1054
|
+
// Calculate zoom level
|
|
1055
|
+
const maxZoom = parseFloat(req.query.maxzoom);
|
|
1056
|
+
let z = calcZForBBox(bbox, w, h, req.query);
|
|
1057
|
+
if (maxZoom > 0) {
|
|
1058
|
+
z = Math.min(z, maxZoom);
|
|
1059
|
+
}
|
|
899
1060
|
|
|
900
|
-
|
|
901
|
-
|
|
1061
|
+
const x = center[0];
|
|
1062
|
+
const y = center[1];
|
|
1063
|
+
|
|
1064
|
+
const overlay = await renderOverlay(
|
|
1065
|
+
z,
|
|
1066
|
+
x,
|
|
1067
|
+
y,
|
|
1068
|
+
bearing,
|
|
1069
|
+
pitch,
|
|
1070
|
+
w,
|
|
1071
|
+
h,
|
|
1072
|
+
scale,
|
|
1073
|
+
paths,
|
|
1074
|
+
markers,
|
|
1075
|
+
req.query,
|
|
1076
|
+
);
|
|
1077
|
+
|
|
1078
|
+
return respondImage(
|
|
1079
|
+
item,
|
|
1080
|
+
z,
|
|
1081
|
+
x,
|
|
1082
|
+
y,
|
|
1083
|
+
bearing,
|
|
1084
|
+
pitch,
|
|
1085
|
+
w,
|
|
1086
|
+
h,
|
|
1087
|
+
scale,
|
|
1088
|
+
format,
|
|
1089
|
+
res,
|
|
1090
|
+
next,
|
|
1091
|
+
overlay,
|
|
1092
|
+
'static',
|
|
1093
|
+
);
|
|
1094
|
+
},
|
|
1095
|
+
);
|
|
902
1096
|
}
|
|
903
1097
|
|
|
904
1098
|
app.get('/:id.json', (req, res, next) => {
|
|
@@ -907,8 +1101,13 @@ export const serve_rendered = {
|
|
|
907
1101
|
return res.sendStatus(404);
|
|
908
1102
|
}
|
|
909
1103
|
const info = clone(item.tileJSON);
|
|
910
|
-
info.tiles = getTileUrls(
|
|
911
|
-
|
|
1104
|
+
info.tiles = getTileUrls(
|
|
1105
|
+
req,
|
|
1106
|
+
info.tiles,
|
|
1107
|
+
`styles/${req.params.id}`,
|
|
1108
|
+
info.format,
|
|
1109
|
+
item.publicUrl,
|
|
1110
|
+
);
|
|
912
1111
|
return res.send(info);
|
|
913
1112
|
});
|
|
914
1113
|
|
|
@@ -918,7 +1117,7 @@ export const serve_rendered = {
|
|
|
918
1117
|
const map = {
|
|
919
1118
|
renderers: [],
|
|
920
1119
|
renderers_static: [],
|
|
921
|
-
sources: {}
|
|
1120
|
+
sources: {},
|
|
922
1121
|
};
|
|
923
1122
|
|
|
924
1123
|
let styleJSON;
|
|
@@ -934,19 +1133,26 @@ export const serve_rendered = {
|
|
|
934
1133
|
const dir = options.paths[protocol];
|
|
935
1134
|
const file = unescape(req.url).substring(protocol.length + 3);
|
|
936
1135
|
fs.readFile(path.join(dir, file), (err, data) => {
|
|
937
|
-
callback(err, {data: data});
|
|
1136
|
+
callback(err, { data: data });
|
|
938
1137
|
});
|
|
939
1138
|
} else if (protocol === 'fonts') {
|
|
940
1139
|
const parts = req.url.split('/');
|
|
941
1140
|
const fontstack = unescape(parts[2]);
|
|
942
1141
|
const range = parts[3].split('.')[0];
|
|
943
1142
|
getFontsPbf(
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
1143
|
+
null,
|
|
1144
|
+
options.paths[protocol],
|
|
1145
|
+
fontstack,
|
|
1146
|
+
range,
|
|
1147
|
+
existingFonts,
|
|
1148
|
+
).then(
|
|
1149
|
+
(concated) => {
|
|
1150
|
+
callback(null, { data: concated });
|
|
1151
|
+
},
|
|
1152
|
+
(err) => {
|
|
1153
|
+
callback(err, { data: null });
|
|
1154
|
+
},
|
|
1155
|
+
);
|
|
950
1156
|
} else if (protocol === 'mbtiles') {
|
|
951
1157
|
const parts = req.url.split('/');
|
|
952
1158
|
const sourceId = parts[2];
|
|
@@ -958,8 +1164,13 @@ export const serve_rendered = {
|
|
|
958
1164
|
const format = parts[5].split('.')[1];
|
|
959
1165
|
source.getTile(z, x, y, (err, data, headers) => {
|
|
960
1166
|
if (err) {
|
|
961
|
-
if (options.verbose)
|
|
962
|
-
|
|
1167
|
+
if (options.verbose)
|
|
1168
|
+
console.log('MBTiles error, serving empty', err);
|
|
1169
|
+
createEmptyResponse(
|
|
1170
|
+
sourceInfo.format,
|
|
1171
|
+
sourceInfo.color,
|
|
1172
|
+
callback,
|
|
1173
|
+
);
|
|
963
1174
|
return;
|
|
964
1175
|
}
|
|
965
1176
|
|
|
@@ -972,11 +1183,23 @@ export const serve_rendered = {
|
|
|
972
1183
|
try {
|
|
973
1184
|
response.data = zlib.unzipSync(data);
|
|
974
1185
|
} catch (err) {
|
|
975
|
-
console.log(
|
|
1186
|
+
console.log(
|
|
1187
|
+
'Skipping incorrect header for tile mbtiles://%s/%s/%s/%s.pbf',
|
|
1188
|
+
id,
|
|
1189
|
+
z,
|
|
1190
|
+
x,
|
|
1191
|
+
y,
|
|
1192
|
+
);
|
|
976
1193
|
}
|
|
977
1194
|
if (options.dataDecoratorFunc) {
|
|
978
1195
|
response.data = options.dataDecoratorFunc(
|
|
979
|
-
|
|
1196
|
+
sourceId,
|
|
1197
|
+
'data',
|
|
1198
|
+
response.data,
|
|
1199
|
+
z,
|
|
1200
|
+
x,
|
|
1201
|
+
y,
|
|
1202
|
+
);
|
|
980
1203
|
}
|
|
981
1204
|
} else {
|
|
982
1205
|
response.data = data;
|
|
@@ -985,36 +1208,39 @@ export const serve_rendered = {
|
|
|
985
1208
|
callback(null, response);
|
|
986
1209
|
});
|
|
987
1210
|
} else if (protocol === 'http' || protocol === 'https') {
|
|
988
|
-
request(
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1211
|
+
request(
|
|
1212
|
+
{
|
|
1213
|
+
url: req.url,
|
|
1214
|
+
encoding: null,
|
|
1215
|
+
gzip: true,
|
|
1216
|
+
},
|
|
1217
|
+
(err, res, body) => {
|
|
1218
|
+
const parts = url.parse(req.url);
|
|
1219
|
+
const extension = path.extname(parts.pathname).toLowerCase();
|
|
1220
|
+
const format = extensionToFormat[extension] || '';
|
|
1221
|
+
if (err || res.statusCode < 200 || res.statusCode >= 300) {
|
|
1222
|
+
// console.log('HTTP error', err || res.statusCode);
|
|
1223
|
+
createEmptyResponse(format, '', callback);
|
|
1224
|
+
return;
|
|
1225
|
+
}
|
|
1001
1226
|
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1227
|
+
const response = {};
|
|
1228
|
+
if (res.headers.modified) {
|
|
1229
|
+
response.modified = new Date(res.headers.modified);
|
|
1230
|
+
}
|
|
1231
|
+
if (res.headers.expires) {
|
|
1232
|
+
response.expires = new Date(res.headers.expires);
|
|
1233
|
+
}
|
|
1234
|
+
if (res.headers.etag) {
|
|
1235
|
+
response.etag = res.headers.etag;
|
|
1236
|
+
}
|
|
1012
1237
|
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1238
|
+
response.data = body;
|
|
1239
|
+
callback(null, response);
|
|
1240
|
+
},
|
|
1241
|
+
);
|
|
1016
1242
|
}
|
|
1017
|
-
}
|
|
1243
|
+
},
|
|
1018
1244
|
});
|
|
1019
1245
|
renderer.load(styleJSON);
|
|
1020
1246
|
createCallback(null, renderer);
|
|
@@ -1025,7 +1251,7 @@ export const serve_rendered = {
|
|
|
1025
1251
|
create: createRenderer.bind(null, ratio),
|
|
1026
1252
|
destroy: (renderer) => {
|
|
1027
1253
|
renderer.release();
|
|
1028
|
-
}
|
|
1254
|
+
},
|
|
1029
1255
|
});
|
|
1030
1256
|
};
|
|
1031
1257
|
|
|
@@ -1039,16 +1265,20 @@ export const serve_rendered = {
|
|
|
1039
1265
|
}
|
|
1040
1266
|
|
|
1041
1267
|
if (styleJSON.sprite && !httpTester.test(styleJSON.sprite)) {
|
|
1042
|
-
styleJSON.sprite =
|
|
1268
|
+
styleJSON.sprite =
|
|
1269
|
+
'sprites://' +
|
|
1043
1270
|
styleJSON.sprite
|
|
1044
|
-
|
|
1045
|
-
|
|
1271
|
+
.replace('{style}', path.basename(styleFile, '.json'))
|
|
1272
|
+
.replace(
|
|
1273
|
+
'{styleJsonFolder}',
|
|
1274
|
+
path.relative(options.paths.sprites, path.dirname(styleJSONPath)),
|
|
1275
|
+
);
|
|
1046
1276
|
}
|
|
1047
1277
|
if (styleJSON.glyphs && !httpTester.test(styleJSON.glyphs)) {
|
|
1048
1278
|
styleJSON.glyphs = `fonts://${styleJSON.glyphs}`;
|
|
1049
1279
|
}
|
|
1050
1280
|
|
|
1051
|
-
for (const layer of
|
|
1281
|
+
for (const layer of styleJSON.layers || []) {
|
|
1052
1282
|
if (layer && layer.paint) {
|
|
1053
1283
|
// Remove (flatten) 3D buildings
|
|
1054
1284
|
if (layer.paint['fill-extrusion-height']) {
|
|
@@ -1061,14 +1291,14 @@ export const serve_rendered = {
|
|
|
1061
1291
|
}
|
|
1062
1292
|
|
|
1063
1293
|
const tileJSON = {
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1294
|
+
tilejson: '2.0.0',
|
|
1295
|
+
name: styleJSON.name,
|
|
1296
|
+
attribution: '',
|
|
1297
|
+
minzoom: 0,
|
|
1298
|
+
maxzoom: 20,
|
|
1299
|
+
bounds: [-180, -85.0511, 180, 85.0511],
|
|
1300
|
+
format: 'png',
|
|
1301
|
+
type: 'baselayer',
|
|
1072
1302
|
};
|
|
1073
1303
|
const attributionOverride = params.tilejson && params.tilejson.attribution;
|
|
1074
1304
|
Object.assign(tileJSON, params.tilejson || {});
|
|
@@ -1081,7 +1311,7 @@ export const serve_rendered = {
|
|
|
1081
1311
|
map,
|
|
1082
1312
|
dataProjWGStoInternalWGS: null,
|
|
1083
1313
|
lastModified: new Date().toUTCString(),
|
|
1084
|
-
watermark: params.watermark || options.watermark
|
|
1314
|
+
watermark: params.watermark || options.watermark,
|
|
1085
1315
|
};
|
|
1086
1316
|
repo[id] = repoobj;
|
|
1087
1317
|
|
|
@@ -1095,8 +1325,8 @@ export const serve_rendered = {
|
|
|
1095
1325
|
delete source.url;
|
|
1096
1326
|
|
|
1097
1327
|
let mbtilesFile = url.substring('mbtiles://'.length);
|
|
1098
|
-
const fromData =
|
|
1099
|
-
mbtilesFile[mbtilesFile.length - 1] === '}';
|
|
1328
|
+
const fromData =
|
|
1329
|
+
mbtilesFile[0] === '{' && mbtilesFile[mbtilesFile.length - 1] === '}';
|
|
1100
1330
|
|
|
1101
1331
|
if (fromData) {
|
|
1102
1332
|
mbtilesFile = mbtilesFile.substr(1, mbtilesFile.length - 2);
|
|
@@ -1111,52 +1341,58 @@ export const serve_rendered = {
|
|
|
1111
1341
|
}
|
|
1112
1342
|
}
|
|
1113
1343
|
|
|
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
|
-
|
|
1344
|
+
queue.push(
|
|
1345
|
+
new Promise((resolve, reject) => {
|
|
1346
|
+
mbtilesFile = path.resolve(options.paths.mbtiles, mbtilesFile);
|
|
1347
|
+
const mbtilesFileStats = fs.statSync(mbtilesFile);
|
|
1348
|
+
if (!mbtilesFileStats.isFile() || mbtilesFileStats.size === 0) {
|
|
1349
|
+
throw Error(`Not valid MBTiles file: ${mbtilesFile}`);
|
|
1350
|
+
}
|
|
1351
|
+
map.sources[name] = new MBTiles(mbtilesFile + '?mode=ro', (err) => {
|
|
1352
|
+
map.sources[name].getInfo((err, info) => {
|
|
1353
|
+
if (err) {
|
|
1354
|
+
console.error(err);
|
|
1355
|
+
return;
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
if (!repoobj.dataProjWGStoInternalWGS && info.proj4) {
|
|
1359
|
+
// how to do this for multiple sources with different proj4 defs?
|
|
1360
|
+
const to3857 = proj4('EPSG:3857');
|
|
1361
|
+
const toDataProj = proj4(info.proj4);
|
|
1362
|
+
repoobj.dataProjWGStoInternalWGS = (xy) =>
|
|
1363
|
+
to3857.inverse(toDataProj.forward(xy));
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
const type = source.type;
|
|
1367
|
+
Object.assign(source, info);
|
|
1368
|
+
source.type = type;
|
|
1369
|
+
source.tiles = [
|
|
1370
|
+
// meta url which will be detected when requested
|
|
1371
|
+
`mbtiles://${name}/{z}/{x}/{y}.${info.format || 'pbf'}`,
|
|
1372
|
+
];
|
|
1373
|
+
delete source.scheme;
|
|
1374
|
+
|
|
1375
|
+
if (options.dataDecoratorFunc) {
|
|
1376
|
+
source = options.dataDecoratorFunc(name, 'tilejson', source);
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
if (
|
|
1380
|
+
!attributionOverride &&
|
|
1381
|
+
source.attribution &&
|
|
1382
|
+
source.attribution.length > 0
|
|
1383
|
+
) {
|
|
1384
|
+
if (!tileJSON.attribution.includes(source.attribution)) {
|
|
1385
|
+
if (tileJSON.attribution.length > 0) {
|
|
1386
|
+
tileJSON.attribution += ' | ';
|
|
1387
|
+
}
|
|
1388
|
+
tileJSON.attribution += source.attribution;
|
|
1152
1389
|
}
|
|
1153
|
-
tileJSON.attribution += source.attribution;
|
|
1154
1390
|
}
|
|
1155
|
-
|
|
1156
|
-
|
|
1391
|
+
resolve();
|
|
1392
|
+
});
|
|
1157
1393
|
});
|
|
1158
|
-
})
|
|
1159
|
-
|
|
1394
|
+
}),
|
|
1395
|
+
);
|
|
1160
1396
|
}
|
|
1161
1397
|
}
|
|
1162
1398
|
|
|
@@ -1170,7 +1406,12 @@ export const serve_rendered = {
|
|
|
1170
1406
|
const minPoolSize = minPoolSizes[i];
|
|
1171
1407
|
const maxPoolSize = Math.max(minPoolSize, maxPoolSizes[j]);
|
|
1172
1408
|
map.renderers[s] = createPool(s, 'tile', minPoolSize, maxPoolSize);
|
|
1173
|
-
map.renderers_static[s] = createPool(
|
|
1409
|
+
map.renderers_static[s] = createPool(
|
|
1410
|
+
s,
|
|
1411
|
+
'static',
|
|
1412
|
+
minPoolSize,
|
|
1413
|
+
maxPoolSize,
|
|
1414
|
+
);
|
|
1174
1415
|
}
|
|
1175
1416
|
});
|
|
1176
1417
|
|
|
@@ -1187,5 +1428,5 @@ export const serve_rendered = {
|
|
|
1187
1428
|
});
|
|
1188
1429
|
}
|
|
1189
1430
|
delete repo[id];
|
|
1190
|
-
}
|
|
1431
|
+
},
|
|
1191
1432
|
};
|