tileserver-gl-light 5.0.0 → 5.1.0-pre.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/release.yml +69 -12
- package/CHANGELOG.md +13 -0
- package/Dockerfile +1 -1
- package/PUBLISHING.md +17 -11
- package/changelog_for_version.md +5 -0
- package/docs/config.rst +21 -3
- package/docs/endpoints.rst +8 -0
- package/package.json +5 -3
- package/public/resources/elevation-control.js +51 -0
- package/public/resources/index.css +1 -1
- package/public/templates/data.tmpl +121 -42
- package/public/templates/index.tmpl +18 -7
- package/src/main.js +6 -0
- package/src/serve_data.js +335 -140
- package/src/serve_font.js +76 -25
- package/src/serve_light.js +2 -2
- package/src/serve_rendered.js +569 -411
- package/src/serve_style.js +181 -56
- package/src/server.js +189 -92
- package/src/utils.js +251 -69
- package/test/setup.js +2 -2
- package/test/static.js +2 -2
- package/test/tiles_rendered.js +7 -7
package/src/serve_rendered.js
CHANGED
|
@@ -13,7 +13,6 @@ import '@maplibre/maplibre-gl-native';
|
|
|
13
13
|
// SECTION END
|
|
14
14
|
|
|
15
15
|
import advancedPool from 'advanced-pool';
|
|
16
|
-
import fs from 'node:fs';
|
|
17
16
|
import path from 'path';
|
|
18
17
|
import url from 'url';
|
|
19
18
|
import util from 'util';
|
|
@@ -28,29 +27,45 @@ import polyline from '@mapbox/polyline';
|
|
|
28
27
|
import proj4 from 'proj4';
|
|
29
28
|
import axios from 'axios';
|
|
30
29
|
import {
|
|
30
|
+
allowedScales,
|
|
31
|
+
allowedTileSizes,
|
|
31
32
|
getFontsPbf,
|
|
32
33
|
listFonts,
|
|
33
34
|
getTileUrls,
|
|
34
35
|
isValidHttpUrl,
|
|
35
36
|
fixTileJSONCenter,
|
|
37
|
+
fetchTileData,
|
|
38
|
+
readFile,
|
|
36
39
|
} from './utils.js';
|
|
37
|
-
import {
|
|
38
|
-
openPMtiles,
|
|
39
|
-
getPMtilesInfo,
|
|
40
|
-
getPMtilesTile,
|
|
41
|
-
} from './pmtiles_adapter.js';
|
|
40
|
+
import { openPMtiles, getPMtilesInfo } from './pmtiles_adapter.js';
|
|
42
41
|
import { renderOverlay, renderWatermark, renderAttribution } from './render.js';
|
|
43
42
|
import fsp from 'node:fs/promises';
|
|
44
43
|
import { existsP, gunzipP } from './promises.js';
|
|
45
44
|
import { openMbTilesWrapper } from './mbtiles_wrapper.js';
|
|
46
45
|
|
|
47
|
-
const FLOAT_PATTERN = '[+-]?(?:\\d+|\\d
|
|
46
|
+
const FLOAT_PATTERN = '[+-]?(?:\\d+|\\d*\\.\\d+)';
|
|
47
|
+
|
|
48
|
+
const staticTypeRegex = new RegExp(
|
|
49
|
+
`^` +
|
|
50
|
+
`(?:` +
|
|
51
|
+
// Format 1: {lon},{lat},{zoom}[@{bearing}[,{pitch}]]
|
|
52
|
+
`(?<lon>${FLOAT_PATTERN}),(?<lat>${FLOAT_PATTERN}),(?<zoom>${FLOAT_PATTERN})` +
|
|
53
|
+
`(?:@(?<bearing>${FLOAT_PATTERN})(?:,(?<pitch>${FLOAT_PATTERN}))?)?` +
|
|
54
|
+
`|` +
|
|
55
|
+
// Format 2: {minx},{miny},{maxx},{maxy}
|
|
56
|
+
`(?<minx>${FLOAT_PATTERN}),(?<miny>${FLOAT_PATTERN}),(?<maxx>${FLOAT_PATTERN}),(?<maxy>${FLOAT_PATTERN})` +
|
|
57
|
+
`|` +
|
|
58
|
+
// Format 3: auto
|
|
59
|
+
`(?<auto>auto)` +
|
|
60
|
+
`)` +
|
|
61
|
+
`$`,
|
|
62
|
+
);
|
|
63
|
+
|
|
48
64
|
const PATH_PATTERN =
|
|
49
65
|
/^((fill|stroke|width)\:[^\|]+\|)*(enc:.+|-?\d+(\.\d*)?,-?\d+(\.\d*)?(\|-?\d+(\.\d*)?,-?\d+(\.\d*)?)+)/;
|
|
50
66
|
const httpTester = /^https?:\/\//i;
|
|
51
67
|
|
|
52
68
|
const mercator = new SphericalMercator();
|
|
53
|
-
const getScale = (scale) => (scale || '@1x').slice(1, 2) | 0;
|
|
54
69
|
|
|
55
70
|
mlgl.on('message', (e) => {
|
|
56
71
|
if (e.severity === 'WARNING' || e.severity === 'ERROR') {
|
|
@@ -81,6 +96,7 @@ const cachedEmptyResponses = {
|
|
|
81
96
|
* @param {string} format The format (a sharp format or 'pbf').
|
|
82
97
|
* @param {string} color The background color (or empty string for transparent).
|
|
83
98
|
* @param {Function} callback The mlgl callback.
|
|
99
|
+
* @returns {void}
|
|
84
100
|
*/
|
|
85
101
|
function createEmptyResponse(format, color, callback) {
|
|
86
102
|
if (!format || format === 'pbf') {
|
|
@@ -103,33 +119,42 @@ function createEmptyResponse(format, color, callback) {
|
|
|
103
119
|
}
|
|
104
120
|
|
|
105
121
|
// create an "empty" response image
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
122
|
+
try {
|
|
123
|
+
color = new Color(color);
|
|
124
|
+
const array = color.array();
|
|
125
|
+
const channels = array.length === 4 && format !== 'jpeg' ? 4 : 3;
|
|
126
|
+
sharp(Buffer.from(array), {
|
|
127
|
+
raw: {
|
|
128
|
+
width: 1,
|
|
129
|
+
height: 1,
|
|
130
|
+
channels,
|
|
131
|
+
},
|
|
132
|
+
})
|
|
133
|
+
.toFormat(format)
|
|
134
|
+
.toBuffer((err, buffer, info) => {
|
|
135
|
+
if (err) {
|
|
136
|
+
console.error('Error creating image with Sharp:', err);
|
|
137
|
+
callback(err, null);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
119
140
|
cachedEmptyResponses[cacheKey] = buffer;
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
141
|
+
callback(null, { data: buffer });
|
|
142
|
+
});
|
|
143
|
+
} catch (error) {
|
|
144
|
+
console.error('Error during image processing setup:', error);
|
|
145
|
+
callback(error, null);
|
|
146
|
+
}
|
|
123
147
|
}
|
|
124
148
|
|
|
125
149
|
/**
|
|
126
150
|
* Parses coordinate pair provided to pair of floats and ensures the resulting
|
|
127
151
|
* pair is a longitude/latitude combination depending on lnglat query parameter.
|
|
128
|
-
* @param {
|
|
152
|
+
* @param {Array<string>} coordinatePair Coordinate pair.
|
|
129
153
|
* @param coordinates
|
|
130
154
|
* @param {object} query Request query parameters.
|
|
155
|
+
* @returns {Array<number>|null} Parsed coordinate pair as [longitude, latitude] or null if invalid
|
|
131
156
|
*/
|
|
132
|
-
|
|
157
|
+
function parseCoordinatePair(coordinates, query) {
|
|
133
158
|
const firstCoordinate = parseFloat(coordinates[0]);
|
|
134
159
|
const secondCoordinate = parseFloat(coordinates[1]);
|
|
135
160
|
|
|
@@ -145,15 +170,16 @@ const parseCoordinatePair = (coordinates, query) => {
|
|
|
145
170
|
}
|
|
146
171
|
|
|
147
172
|
return [firstCoordinate, secondCoordinate];
|
|
148
|
-
}
|
|
173
|
+
}
|
|
149
174
|
|
|
150
175
|
/**
|
|
151
176
|
* Parses a coordinate pair from query arguments and optionally transforms it.
|
|
152
|
-
* @param {
|
|
177
|
+
* @param {Array<string>} coordinatePair Coordinate pair.
|
|
153
178
|
* @param {object} query Request query parameters.
|
|
154
179
|
* @param {Function} transformer Optional transform function.
|
|
180
|
+
* @returns {Array<number>|null} Transformed coordinate pair or null if invalid.
|
|
155
181
|
*/
|
|
156
|
-
|
|
182
|
+
function parseCoordinates(coordinatePair, query, transformer) {
|
|
157
183
|
const parsedCoordinates = parseCoordinatePair(coordinatePair, query);
|
|
158
184
|
|
|
159
185
|
// Transform coordinates
|
|
@@ -162,14 +188,15 @@ const parseCoordinates = (coordinatePair, query, transformer) => {
|
|
|
162
188
|
}
|
|
163
189
|
|
|
164
190
|
return parsedCoordinates;
|
|
165
|
-
}
|
|
191
|
+
}
|
|
166
192
|
|
|
167
193
|
/**
|
|
168
194
|
* Parses paths provided via query into a list of path objects.
|
|
169
195
|
* @param {object} query Request query parameters.
|
|
170
196
|
* @param {Function} transformer Optional transform function.
|
|
197
|
+
* @returns {Array<Array<Array<number>>>} Array of paths.
|
|
171
198
|
*/
|
|
172
|
-
|
|
199
|
+
function extractPathsFromQuery(query, transformer) {
|
|
173
200
|
// Initiate paths array
|
|
174
201
|
const paths = [];
|
|
175
202
|
// Return an empty list if no paths have been provided
|
|
@@ -221,17 +248,17 @@ const extractPathsFromQuery = (query, transformer) => {
|
|
|
221
248
|
}
|
|
222
249
|
}
|
|
223
250
|
return paths;
|
|
224
|
-
}
|
|
225
|
-
|
|
251
|
+
}
|
|
226
252
|
/**
|
|
227
253
|
* Parses marker options provided via query and sets corresponding attributes
|
|
228
254
|
* on marker object.
|
|
229
255
|
* Options adhere to the following format
|
|
230
256
|
* [optionName]:[optionValue]
|
|
231
|
-
* @param {
|
|
257
|
+
* @param {Array<string>} optionsList List of option strings.
|
|
232
258
|
* @param {object} marker Marker object to configure.
|
|
259
|
+
* @returns {void}
|
|
233
260
|
*/
|
|
234
|
-
|
|
261
|
+
function parseMarkerOptions(optionsList, marker) {
|
|
235
262
|
for (const options of optionsList) {
|
|
236
263
|
const optionParts = options.split(':');
|
|
237
264
|
// Ensure we got an option name and value
|
|
@@ -258,15 +285,16 @@ const parseMarkerOptions = (optionsList, marker) => {
|
|
|
258
285
|
break;
|
|
259
286
|
}
|
|
260
287
|
}
|
|
261
|
-
}
|
|
288
|
+
}
|
|
262
289
|
|
|
263
290
|
/**
|
|
264
291
|
* Parses markers provided via query into a list of marker objects.
|
|
265
292
|
* @param {object} query Request query parameters.
|
|
266
293
|
* @param {object} options Configuration options.
|
|
267
294
|
* @param {Function} transformer Optional transform function.
|
|
295
|
+
* @returns {Array<object>} An array of marker objects.
|
|
268
296
|
*/
|
|
269
|
-
|
|
297
|
+
function extractMarkersFromQuery(query, options, transformer) {
|
|
270
298
|
// Return an empty list if no markers have been provided
|
|
271
299
|
if (!query.marker) {
|
|
272
300
|
return [];
|
|
@@ -342,9 +370,16 @@ const extractMarkersFromQuery = (query, options, transformer) => {
|
|
|
342
370
|
markers.push(marker);
|
|
343
371
|
}
|
|
344
372
|
return markers;
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Calculates the zoom level for a given bounding box.
|
|
376
|
+
* @param {Array<number>} bbox Bounding box as [minx, miny, maxx, maxy].
|
|
377
|
+
* @param {number} w Width of the image.
|
|
378
|
+
* @param {number} h Height of the image.
|
|
379
|
+
* @param {object} query Request query parameters.
|
|
380
|
+
* @returns {number} Calculated zoom level.
|
|
381
|
+
*/
|
|
382
|
+
function calcZForBBox(bbox, w, h, query) {
|
|
348
383
|
let z = 25;
|
|
349
384
|
|
|
350
385
|
const padding = query.padding !== undefined ? parseFloat(query.padding) : 0.1;
|
|
@@ -363,9 +398,27 @@ const calcZForBBox = (bbox, w, h, query) => {
|
|
|
363
398
|
z = Math.max(Math.log(Math.max(w, h) / 256) / Math.LN2, Math.min(25, z));
|
|
364
399
|
|
|
365
400
|
return z;
|
|
366
|
-
}
|
|
401
|
+
}
|
|
367
402
|
|
|
368
|
-
|
|
403
|
+
/**
|
|
404
|
+
* Responds with an image.
|
|
405
|
+
* @param {object} options Configuration options.
|
|
406
|
+
* @param {object} item Item object containing map and other information.
|
|
407
|
+
* @param {number} z Zoom level.
|
|
408
|
+
* @param {number} lon Longitude of the center.
|
|
409
|
+
* @param {number} lat Latitude of the center.
|
|
410
|
+
* @param {number} bearing Map bearing.
|
|
411
|
+
* @param {number} pitch Map pitch.
|
|
412
|
+
* @param {number} width Width of the image.
|
|
413
|
+
* @param {number} height Height of the image.
|
|
414
|
+
* @param {number} scale Scale factor.
|
|
415
|
+
* @param {string} format Image format.
|
|
416
|
+
* @param {object} res Express response object.
|
|
417
|
+
* @param {Buffer|null} overlay Optional overlay image.
|
|
418
|
+
* @param {string} mode Rendering mode ('tile' or 'static').
|
|
419
|
+
* @returns {Promise<void>}
|
|
420
|
+
*/
|
|
421
|
+
async function respondImage(
|
|
369
422
|
options,
|
|
370
423
|
item,
|
|
371
424
|
z,
|
|
@@ -380,7 +433,7 @@ const respondImage = (
|
|
|
380
433
|
res,
|
|
381
434
|
overlay = null,
|
|
382
435
|
mode = 'tile',
|
|
383
|
-
)
|
|
436
|
+
) {
|
|
384
437
|
if (
|
|
385
438
|
Math.abs(lon) > 180 ||
|
|
386
439
|
Math.abs(lat) > 85.06 ||
|
|
@@ -413,7 +466,8 @@ const respondImage = (
|
|
|
413
466
|
} else {
|
|
414
467
|
pool = item.map.renderersStatic[scale];
|
|
415
468
|
}
|
|
416
|
-
|
|
469
|
+
|
|
470
|
+
pool.acquire(async (err, renderer) => {
|
|
417
471
|
// For 512px tiles, use the actual maplibre-native zoom. For 256px tiles, use zoom - 1
|
|
418
472
|
let mlglZ;
|
|
419
473
|
if (width === 512) {
|
|
@@ -472,8 +526,8 @@ const respondImage = (
|
|
|
472
526
|
height: height * scale,
|
|
473
527
|
});
|
|
474
528
|
}
|
|
475
|
-
|
|
476
529
|
// HACK(Part 2) 256px tiles are a zoom level lower than maplibre-native default tiles. this hack allows tileserver-gl to support zoom 0 256px tiles, which would actually be zoom -1 in maplibre-native. Since zoom -1 isn't supported, a double sized zoom 0 tile is requested and resized here.
|
|
530
|
+
|
|
477
531
|
if (z === 0 && width === 256) {
|
|
478
532
|
image.resize(width * scale, height * scale);
|
|
479
533
|
}
|
|
@@ -527,7 +581,10 @@ const respondImage = (
|
|
|
527
581
|
dither: formatOptions.dither,
|
|
528
582
|
});
|
|
529
583
|
} else if (format === 'jpeg') {
|
|
530
|
-
image.jpeg({
|
|
584
|
+
image.jpeg({
|
|
585
|
+
quality: formatOptions.quality || formatQuality || 80,
|
|
586
|
+
progressive: formatOptions.progressive,
|
|
587
|
+
});
|
|
531
588
|
} else if (format === 'webp') {
|
|
532
589
|
image.webp({ quality: formatOptions.quality || formatQuality || 90 });
|
|
533
590
|
}
|
|
@@ -544,320 +601,410 @@ const respondImage = (
|
|
|
544
601
|
});
|
|
545
602
|
});
|
|
546
603
|
});
|
|
547
|
-
}
|
|
604
|
+
}
|
|
548
605
|
|
|
549
|
-
|
|
550
|
-
|
|
606
|
+
/**
|
|
607
|
+
* Handles requests for tile images.
|
|
608
|
+
* @param {object} options - Configuration options for the server.
|
|
609
|
+
* @param {object} repo - The repository object holding style data.
|
|
610
|
+
* @param {object} req - Express request object.
|
|
611
|
+
* @param {string} req.params.id - The id of the style.
|
|
612
|
+
* @param {string} req.params.p1 - The tile size parameter, if available.
|
|
613
|
+
* @param {string} req.params.p2 - The z parameter.
|
|
614
|
+
* @param {string} req.params.p3 - The x parameter.
|
|
615
|
+
* @param {string} req.params.p4 - The y parameter.
|
|
616
|
+
* @param {string} req.params.scale - The scale parameter.
|
|
617
|
+
* @param {string} req.params.format - The format of the image.
|
|
618
|
+
* @param {object} res - Express response object.
|
|
619
|
+
* @param {Function} next - Express next middleware function.
|
|
620
|
+
* @param {number} maxScaleFactor - The maximum scale factor allowed.
|
|
621
|
+
* @param defailtTileSize
|
|
622
|
+
* @returns {Promise<void>}
|
|
623
|
+
*/
|
|
624
|
+
async function handleTileRequest(
|
|
625
|
+
options,
|
|
626
|
+
repo,
|
|
627
|
+
req,
|
|
628
|
+
res,
|
|
629
|
+
next,
|
|
630
|
+
maxScaleFactor,
|
|
631
|
+
defailtTileSize,
|
|
632
|
+
) {
|
|
633
|
+
const {
|
|
634
|
+
id,
|
|
635
|
+
p1: tileSize,
|
|
636
|
+
p2: zParam,
|
|
637
|
+
p3: xParam,
|
|
638
|
+
p4: yParam,
|
|
639
|
+
scale: scaleParam,
|
|
640
|
+
format,
|
|
641
|
+
} = req.params;
|
|
642
|
+
const item = repo[id];
|
|
643
|
+
if (!item) {
|
|
644
|
+
return res.sendStatus(404);
|
|
645
|
+
}
|
|
551
646
|
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
647
|
+
const modifiedSince = req.get('if-modified-since');
|
|
648
|
+
const cc = req.get('cache-control');
|
|
649
|
+
if (modifiedSince && (!cc || cc.indexOf('no-cache') === -1)) {
|
|
650
|
+
if (
|
|
651
|
+
new Date(item.lastModified).getTime() ===
|
|
652
|
+
new Date(modifiedSince).getTime()
|
|
653
|
+
) {
|
|
654
|
+
return res.sendStatus(304);
|
|
558
655
|
}
|
|
559
|
-
|
|
656
|
+
}
|
|
657
|
+
const z = parseFloat(zParam) | 0;
|
|
658
|
+
const x = parseFloat(xParam) | 0;
|
|
659
|
+
const y = parseFloat(yParam) | 0;
|
|
660
|
+
const scale = allowedScales(scaleParam, maxScaleFactor);
|
|
560
661
|
|
|
561
|
-
|
|
662
|
+
let parsedTileSize = parseInt(defailtTileSize, 10);
|
|
663
|
+
if (tileSize) {
|
|
664
|
+
parsedTileSize = parseInt(allowedTileSizes(tileSize), 10);
|
|
562
665
|
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
if (!item) {
|
|
568
|
-
return res.sendStatus(404);
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
const modifiedSince = req.get('if-modified-since');
|
|
572
|
-
const cc = req.get('cache-control');
|
|
573
|
-
if (modifiedSince && (!cc || cc.indexOf('no-cache') === -1)) {
|
|
574
|
-
if (new Date(item.lastModified) <= new Date(modifiedSince)) {
|
|
575
|
-
return res.sendStatus(304);
|
|
576
|
-
}
|
|
577
|
-
}
|
|
666
|
+
if (parsedTileSize == null) {
|
|
667
|
+
return res.status(400).send('Invalid Tile Size');
|
|
668
|
+
}
|
|
669
|
+
}
|
|
578
670
|
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
z > 22 ||
|
|
591
|
-
x >= Math.pow(2, z) ||
|
|
592
|
-
y >= Math.pow(2, z)
|
|
593
|
-
) {
|
|
594
|
-
return res.status(404).send('Out of bounds');
|
|
595
|
-
}
|
|
671
|
+
if (
|
|
672
|
+
scale == null ||
|
|
673
|
+
z < 0 ||
|
|
674
|
+
x < 0 ||
|
|
675
|
+
y < 0 ||
|
|
676
|
+
z > 22 ||
|
|
677
|
+
x >= Math.pow(2, z) ||
|
|
678
|
+
y >= Math.pow(2, z)
|
|
679
|
+
) {
|
|
680
|
+
return res.status(400).send('Out of bounds');
|
|
681
|
+
}
|
|
596
682
|
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
],
|
|
602
|
-
z,
|
|
603
|
-
);
|
|
683
|
+
const tileCenter = mercator.ll(
|
|
684
|
+
[((x + 0.5) / (1 << z)) * (256 << z), ((y + 0.5) / (1 << z)) * (256 << z)],
|
|
685
|
+
z,
|
|
686
|
+
);
|
|
604
687
|
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
);
|
|
688
|
+
// prettier-ignore
|
|
689
|
+
return await respondImage(
|
|
690
|
+
options, item, z, tileCenter[0], tileCenter[1], 0, 0, parsedTileSize, parsedTileSize, scale, format, res,
|
|
691
|
+
);
|
|
692
|
+
}
|
|
611
693
|
|
|
612
|
-
|
|
613
|
-
|
|
694
|
+
/**
|
|
695
|
+
* Handles requests for static map images.
|
|
696
|
+
* @param {object} options - Configuration options for the server.
|
|
697
|
+
* @param {object} repo - The repository object holding style data.
|
|
698
|
+
* @param {object} req - Express request object.
|
|
699
|
+
* @param {object} res - Express response object.
|
|
700
|
+
* @param {string} req.params.p2 - The raw or static parameter.
|
|
701
|
+
* @param {string} req.params.p3 - The staticType parameter.
|
|
702
|
+
* @param {string} req.params.p4 - The width parameter.
|
|
703
|
+
* @param {string} req.params.p5 - The height parameter.
|
|
704
|
+
* @param {string} req.params.scale - The scale parameter.
|
|
705
|
+
* @param {string} req.params.format - The format of the image.
|
|
706
|
+
* @param {Function} next - Express next middleware function.
|
|
707
|
+
* @param {number} maxScaleFactor - The maximum scale factor allowed.
|
|
708
|
+
* @param verbose
|
|
709
|
+
* @returns {Promise<void>}
|
|
710
|
+
*/
|
|
711
|
+
async function handleStaticRequest(
|
|
712
|
+
options,
|
|
713
|
+
repo,
|
|
714
|
+
req,
|
|
715
|
+
res,
|
|
716
|
+
next,
|
|
717
|
+
maxScaleFactor,
|
|
718
|
+
) {
|
|
719
|
+
const {
|
|
720
|
+
id,
|
|
721
|
+
p2: raw,
|
|
722
|
+
p3: staticType,
|
|
723
|
+
p4: widthAndHeight,
|
|
724
|
+
scale: scaleParam,
|
|
725
|
+
format,
|
|
726
|
+
} = req.params;
|
|
727
|
+
const item = repo[id];
|
|
728
|
+
|
|
729
|
+
let parsedWidth = null;
|
|
730
|
+
let parsedHeight = null;
|
|
731
|
+
if (widthAndHeight) {
|
|
732
|
+
const sizeMatch = widthAndHeight.match(/^(\d+)x(\d+)$/);
|
|
733
|
+
if (sizeMatch) {
|
|
734
|
+
const width = parseInt(sizeMatch[1], 10);
|
|
735
|
+
const height = parseInt(sizeMatch[2], 10);
|
|
736
|
+
if (
|
|
737
|
+
isNaN(width) ||
|
|
738
|
+
isNaN(height) ||
|
|
739
|
+
width !== parseFloat(sizeMatch[1]) ||
|
|
740
|
+
height !== parseFloat(sizeMatch[2])
|
|
741
|
+
) {
|
|
742
|
+
return res
|
|
743
|
+
.status(400)
|
|
744
|
+
.send('Invalid width or height provided in size parameter');
|
|
745
|
+
}
|
|
746
|
+
parsedWidth = width;
|
|
747
|
+
parsedHeight = height;
|
|
748
|
+
} else {
|
|
749
|
+
return res
|
|
750
|
+
.status(400)
|
|
751
|
+
.send('Invalid width or height provided in size parameter');
|
|
752
|
+
}
|
|
753
|
+
} else {
|
|
754
|
+
return res
|
|
755
|
+
.status(400)
|
|
756
|
+
.send('Invalid width or height provided in size parameter');
|
|
757
|
+
}
|
|
614
758
|
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
FLOAT_PATTERN,
|
|
618
|
-
FLOAT_PATTERN,
|
|
619
|
-
FLOAT_PATTERN,
|
|
620
|
-
FLOAT_PATTERN,
|
|
621
|
-
FLOAT_PATTERN,
|
|
622
|
-
);
|
|
759
|
+
const scale = allowedScales(scaleParam, maxScaleFactor);
|
|
760
|
+
let isRaw = raw === 'raw';
|
|
623
761
|
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
const item = repo[req.params.id];
|
|
629
|
-
if (!item) {
|
|
630
|
-
return res.sendStatus(404);
|
|
631
|
-
}
|
|
632
|
-
const raw = req.params.raw;
|
|
633
|
-
const z = +req.params.z;
|
|
634
|
-
let x = +req.params.x;
|
|
635
|
-
let y = +req.params.y;
|
|
636
|
-
const bearing = +(req.params.bearing || '0');
|
|
637
|
-
const pitch = +(req.params.pitch || '0');
|
|
638
|
-
const w = req.params.width | 0;
|
|
639
|
-
const h = req.params.height | 0;
|
|
640
|
-
const scale = getScale(req.params.scale);
|
|
641
|
-
const format = req.params.format;
|
|
642
|
-
|
|
643
|
-
if (z < 0) {
|
|
644
|
-
return res.status(404).send('Invalid zoom');
|
|
645
|
-
}
|
|
762
|
+
const staticTypeMatch = staticType.match(staticTypeRegex);
|
|
763
|
+
if (!item || !format || !scale || !staticTypeMatch?.groups) {
|
|
764
|
+
return res.sendStatus(404);
|
|
765
|
+
}
|
|
646
766
|
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
767
|
+
if (staticTypeMatch.groups.lon) {
|
|
768
|
+
// Center Based Static Image
|
|
769
|
+
const z = parseFloat(staticTypeMatch.groups.zoom) || 0;
|
|
770
|
+
let x = parseFloat(staticTypeMatch.groups.lon) || 0;
|
|
771
|
+
let y = parseFloat(staticTypeMatch.groups.lat) || 0;
|
|
772
|
+
const bearing = parseFloat(staticTypeMatch.groups.bearing) || 0;
|
|
773
|
+
const pitch = parseInt(staticTypeMatch.groups.pitch) || 0;
|
|
774
|
+
if (z < 0) {
|
|
775
|
+
return res.status(404).send('Invalid zoom');
|
|
776
|
+
}
|
|
650
777
|
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
y = ll[1];
|
|
655
|
-
}
|
|
778
|
+
const transformer = isRaw
|
|
779
|
+
? mercator.inverse.bind(mercator)
|
|
780
|
+
: item.dataProjWGStoInternalWGS;
|
|
656
781
|
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
);
|
|
782
|
+
if (transformer) {
|
|
783
|
+
const ll = transformer([x, y]);
|
|
784
|
+
x = ll[0];
|
|
785
|
+
y = ll[1];
|
|
786
|
+
}
|
|
663
787
|
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
788
|
+
const paths = extractPathsFromQuery(req.query, transformer);
|
|
789
|
+
const markers = extractMarkersFromQuery(req.query, options, transformer);
|
|
790
|
+
// prettier-ignore
|
|
791
|
+
const overlay = await renderOverlay(
|
|
792
|
+
z, x, y, bearing, pitch, parsedWidth, parsedHeight, scale, paths, markers, req.query,
|
|
793
|
+
);
|
|
794
|
+
|
|
795
|
+
// prettier-ignore
|
|
796
|
+
return await respondImage(
|
|
797
|
+
options, item, z, x, y, bearing, pitch, parsedWidth, parsedHeight, scale, format, res, overlay, 'static',
|
|
798
|
+
);
|
|
799
|
+
} else if (staticTypeMatch.groups.minx) {
|
|
800
|
+
// Area Based Static Image
|
|
801
|
+
const minx = parseFloat(staticTypeMatch.groups.minx) || 0;
|
|
802
|
+
const miny = parseFloat(staticTypeMatch.groups.miny) || 0;
|
|
803
|
+
const maxx = parseFloat(staticTypeMatch.groups.maxx) || 0;
|
|
804
|
+
const maxy = parseFloat(staticTypeMatch.groups.maxy) || 0;
|
|
805
|
+
const bbox = [minx, miny, maxx, maxy];
|
|
806
|
+
let center = [(bbox[0] + bbox[2]) / 2, (bbox[1] + bbox[3]) / 2];
|
|
807
|
+
|
|
808
|
+
const transformer = isRaw
|
|
809
|
+
? mercator.inverse.bind(mercator)
|
|
810
|
+
: item.dataProjWGStoInternalWGS;
|
|
811
|
+
|
|
812
|
+
if (transformer) {
|
|
813
|
+
const minCorner = transformer(bbox.slice(0, 2));
|
|
814
|
+
const maxCorner = transformer(bbox.slice(2));
|
|
815
|
+
bbox[0] = minCorner[0];
|
|
816
|
+
bbox[1] = minCorner[1];
|
|
817
|
+
bbox[2] = maxCorner[0];
|
|
818
|
+
bbox[3] = maxCorner[1];
|
|
819
|
+
center = transformer(center);
|
|
820
|
+
}
|
|
668
821
|
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
822
|
+
const z = calcZForBBox(bbox, parsedWidth, parsedHeight, req.query);
|
|
823
|
+
const x = center[0];
|
|
824
|
+
const y = center[1];
|
|
825
|
+
const bearing = 0;
|
|
826
|
+
const pitch = 0;
|
|
827
|
+
|
|
828
|
+
const paths = extractPathsFromQuery(req.query, transformer);
|
|
829
|
+
const markers = extractMarkersFromQuery(req.query, options, transformer);
|
|
830
|
+
// prettier-ignore
|
|
831
|
+
const overlay = await renderOverlay(
|
|
832
|
+
z, x, y, bearing, pitch, parsedWidth, parsedHeight, scale, paths, markers, req.query,
|
|
677
833
|
);
|
|
678
834
|
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
const maxCorner = transformer(bbox.slice(2));
|
|
701
|
-
bbox[0] = minCorner[0];
|
|
702
|
-
bbox[1] = minCorner[1];
|
|
703
|
-
bbox[2] = maxCorner[0];
|
|
704
|
-
bbox[3] = maxCorner[1];
|
|
705
|
-
center = transformer(center);
|
|
706
|
-
}
|
|
707
|
-
|
|
708
|
-
const w = req.params.width | 0;
|
|
709
|
-
const h = req.params.height | 0;
|
|
710
|
-
const scale = getScale(req.params.scale);
|
|
711
|
-
const format = req.params.format;
|
|
835
|
+
// prettier-ignore
|
|
836
|
+
return await respondImage(
|
|
837
|
+
options, item, z, x, y, bearing, pitch, parsedWidth, parsedHeight, scale, format, res, overlay, 'static',
|
|
838
|
+
);
|
|
839
|
+
} else if (staticTypeMatch.groups.auto) {
|
|
840
|
+
// Area Static Image
|
|
841
|
+
const bearing = 0;
|
|
842
|
+
const pitch = 0;
|
|
843
|
+
|
|
844
|
+
const transformer = isRaw
|
|
845
|
+
? mercator.inverse.bind(mercator)
|
|
846
|
+
: item.dataProjWGStoInternalWGS;
|
|
847
|
+
|
|
848
|
+
const paths = extractPathsFromQuery(req.query, transformer);
|
|
849
|
+
const markers = extractMarkersFromQuery(req.query, options, transformer);
|
|
850
|
+
|
|
851
|
+
// Extract coordinates from markers
|
|
852
|
+
const markerCoordinates = [];
|
|
853
|
+
for (const marker of markers) {
|
|
854
|
+
markerCoordinates.push(marker.location);
|
|
855
|
+
}
|
|
712
856
|
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
const y = center[1];
|
|
716
|
-
const bearing = 0;
|
|
717
|
-
const pitch = 0;
|
|
857
|
+
// Create array with coordinates from markers and path
|
|
858
|
+
const coords = [].concat(paths.flat()).concat(markerCoordinates);
|
|
718
859
|
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
transformer,
|
|
724
|
-
);
|
|
725
|
-
|
|
726
|
-
// prettier-ignore
|
|
727
|
-
const overlay = await renderOverlay(
|
|
728
|
-
z, x, y, bearing, pitch, w, h, scale, paths, markers, req.query,
|
|
729
|
-
);
|
|
860
|
+
// Check if we have at least one coordinate to calculate a bounding box
|
|
861
|
+
if (coords.length < 1) {
|
|
862
|
+
return res.status(400).send('No coordinates provided');
|
|
863
|
+
}
|
|
730
864
|
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
};
|
|
865
|
+
const bbox = [Infinity, Infinity, -Infinity, -Infinity];
|
|
866
|
+
for (const pair of coords) {
|
|
867
|
+
bbox[0] = Math.min(bbox[0], pair[0]);
|
|
868
|
+
bbox[1] = Math.min(bbox[1], pair[1]);
|
|
869
|
+
bbox[2] = Math.max(bbox[2], pair[0]);
|
|
870
|
+
bbox[3] = Math.max(bbox[3], pair[1]);
|
|
871
|
+
}
|
|
739
872
|
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
873
|
+
const bbox_ = mercator.convert(bbox, '900913');
|
|
874
|
+
const center = mercator.inverse([
|
|
875
|
+
(bbox_[0] + bbox_[2]) / 2,
|
|
876
|
+
(bbox_[1] + bbox_[3]) / 2,
|
|
877
|
+
]);
|
|
878
|
+
|
|
879
|
+
// Calculate zoom level
|
|
880
|
+
const maxZoom = parseFloat(req.query.maxzoom);
|
|
881
|
+
let z = calcZForBBox(bbox, parsedWidth, parsedHeight, req.query);
|
|
882
|
+
if (maxZoom > 0) {
|
|
883
|
+
z = Math.min(z, maxZoom);
|
|
884
|
+
}
|
|
747
885
|
|
|
748
|
-
|
|
886
|
+
const x = center[0];
|
|
887
|
+
const y = center[1];
|
|
749
888
|
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
req.params.raw = true;
|
|
755
|
-
req.params.format = (req.query.format || 'image/png').split('/').pop();
|
|
756
|
-
const bbox = (req.query.bbox || '').split(',');
|
|
757
|
-
req.params.minx = bbox[0];
|
|
758
|
-
req.params.miny = bbox[1];
|
|
759
|
-
req.params.maxx = bbox[2];
|
|
760
|
-
req.params.maxy = bbox[3];
|
|
761
|
-
req.params.width = req.query.width || '256';
|
|
762
|
-
req.params.height = req.query.height || '256';
|
|
763
|
-
if (req.query.scale) {
|
|
764
|
-
req.params.width /= req.query.scale;
|
|
765
|
-
req.params.height /= req.query.scale;
|
|
766
|
-
req.params.scale = `@${req.query.scale}`;
|
|
767
|
-
}
|
|
889
|
+
// prettier-ignore
|
|
890
|
+
const overlay = await renderOverlay(
|
|
891
|
+
z, x, y, bearing, pitch, parsedWidth, parsedHeight, scale, paths, markers, req.query,
|
|
892
|
+
);
|
|
768
893
|
|
|
769
|
-
|
|
770
|
-
|
|
894
|
+
// prettier-ignore
|
|
895
|
+
return await respondImage(
|
|
896
|
+
options, item, z, x, y, bearing, pitch, parsedWidth, parsedHeight, scale, format, res, overlay, 'static',
|
|
897
|
+
);
|
|
898
|
+
} else {
|
|
899
|
+
return res.sendStatus(404);
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
const existingFonts = {};
|
|
903
|
+
let maxScaleFactor = 2;
|
|
771
904
|
|
|
772
|
-
|
|
905
|
+
export const serve_rendered = {
|
|
906
|
+
/**
|
|
907
|
+
* Initializes the serve_rendered module.
|
|
908
|
+
* @param {object} options Configuration options.
|
|
909
|
+
* @param {object} repo Repository object.
|
|
910
|
+
* @param {object} programOpts - An object containing the program options.
|
|
911
|
+
* @returns {Promise<express.Application>} A promise that resolves to the Express app.
|
|
912
|
+
*/
|
|
913
|
+
init: async function (options, repo, programOpts) {
|
|
914
|
+
const { verbose, tileSize: defailtTileSize = 256 } = programOpts;
|
|
915
|
+
maxScaleFactor = Math.min(Math.floor(options.maxScaleFactor || 3), 9);
|
|
916
|
+
const app = express().disable('x-powered-by');
|
|
773
917
|
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
918
|
+
/**
|
|
919
|
+
* Handles requests for tile images.
|
|
920
|
+
* @param {object} req - Express request object.
|
|
921
|
+
* @param {object} res - Express response object.
|
|
922
|
+
* @param {string} req.params.id - The id of the style.
|
|
923
|
+
* @param {string} [req.params.p1] - The tile size or static parameter, if available.
|
|
924
|
+
* @param {string} req.params.p2 - The z, static, or raw parameter.
|
|
925
|
+
* @param {string} req.params.p3 - The x or staticType parameter.
|
|
926
|
+
* @param {string} req.params.p4 - The y or width parameter.
|
|
927
|
+
* @param {string} req.params.scale - The scale parameter.
|
|
928
|
+
* @param {string} req.params.format - The format of the image.
|
|
929
|
+
* @returns {Promise<void>}
|
|
930
|
+
*/
|
|
931
|
+
app.get(
|
|
932
|
+
`/:id{/:p1}/:p2/:p3/:p4{@:scale}{.:format}`,
|
|
933
|
+
async (req, res, next) => {
|
|
934
|
+
try {
|
|
935
|
+
const { p1, p2, id, p3, p4, scale, format } = req.params;
|
|
936
|
+
const requestType =
|
|
937
|
+
(!p1 && p2 === 'static') || (p1 === 'static' && p2 === 'raw')
|
|
938
|
+
? 'static'
|
|
939
|
+
: 'tile';
|
|
940
|
+
if (verbose) {
|
|
941
|
+
console.log(
|
|
942
|
+
`Handling rendered %s request for: /styles/%s%s/%s/%s/%s%s.%s`,
|
|
943
|
+
requestType,
|
|
944
|
+
String(id).replace(/\n|\r/g, ''),
|
|
945
|
+
p1 ? '/' + String(p1).replace(/\n|\r/g, '') : '',
|
|
946
|
+
String(p2).replace(/\n|\r/g, ''),
|
|
947
|
+
String(p3).replace(/\n|\r/g, ''),
|
|
948
|
+
String(p4).replace(/\n|\r/g, ''),
|
|
949
|
+
scale ? '@' + String(scale).replace(/\n|\r/g, '') : '',
|
|
950
|
+
String(format).replace(/\n|\r/g, ''),
|
|
799
951
|
);
|
|
952
|
+
}
|
|
800
953
|
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
return res.status(400).send('No coordinates provided');
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
const bbox = [Infinity, Infinity, -Infinity, -Infinity];
|
|
816
|
-
for (const pair of coords) {
|
|
817
|
-
bbox[0] = Math.min(bbox[0], pair[0]);
|
|
818
|
-
bbox[1] = Math.min(bbox[1], pair[1]);
|
|
819
|
-
bbox[2] = Math.max(bbox[2], pair[0]);
|
|
820
|
-
bbox[3] = Math.max(bbox[3], pair[1]);
|
|
821
|
-
}
|
|
822
|
-
|
|
823
|
-
const bbox_ = mercator.convert(bbox, '900913');
|
|
824
|
-
const center = mercator.inverse([
|
|
825
|
-
(bbox_[0] + bbox_[2]) / 2,
|
|
826
|
-
(bbox_[1] + bbox_[3]) / 2,
|
|
827
|
-
]);
|
|
828
|
-
|
|
829
|
-
// Calculate zoom level
|
|
830
|
-
const maxZoom = parseFloat(req.query.maxzoom);
|
|
831
|
-
let z = calcZForBBox(bbox, w, h, req.query);
|
|
832
|
-
if (maxZoom > 0) {
|
|
833
|
-
z = Math.min(z, maxZoom);
|
|
954
|
+
if (requestType === 'static') {
|
|
955
|
+
// Route to static if p2 is static
|
|
956
|
+
if (options.serveStaticMaps !== false) {
|
|
957
|
+
return handleStaticRequest(
|
|
958
|
+
options,
|
|
959
|
+
repo,
|
|
960
|
+
req,
|
|
961
|
+
res,
|
|
962
|
+
next,
|
|
963
|
+
maxScaleFactor,
|
|
964
|
+
);
|
|
834
965
|
}
|
|
835
|
-
|
|
836
|
-
const x = center[0];
|
|
837
|
-
const y = center[1];
|
|
838
|
-
|
|
839
|
-
// prettier-ignore
|
|
840
|
-
const overlay = await renderOverlay(
|
|
841
|
-
z, x, y, bearing, pitch, w, h, scale, paths, markers, req.query,
|
|
842
|
-
);
|
|
843
|
-
|
|
844
|
-
// prettier-ignore
|
|
845
|
-
return respondImage(
|
|
846
|
-
options, item, z, x, y, bearing, pitch, w, h, scale, format, res, overlay, 'static',
|
|
847
|
-
);
|
|
848
|
-
} catch (e) {
|
|
849
|
-
next(e);
|
|
966
|
+
return res.sendStatus(404);
|
|
850
967
|
}
|
|
851
|
-
},
|
|
852
|
-
);
|
|
853
|
-
}
|
|
854
968
|
|
|
855
|
-
|
|
969
|
+
return handleTileRequest(
|
|
970
|
+
options,
|
|
971
|
+
repo,
|
|
972
|
+
req,
|
|
973
|
+
res,
|
|
974
|
+
next,
|
|
975
|
+
maxScaleFactor,
|
|
976
|
+
defailtTileSize,
|
|
977
|
+
);
|
|
978
|
+
} catch (e) {
|
|
979
|
+
console.log(e);
|
|
980
|
+
return next(e);
|
|
981
|
+
}
|
|
982
|
+
},
|
|
983
|
+
);
|
|
984
|
+
|
|
985
|
+
/**
|
|
986
|
+
* Handles requests for rendered tilejson endpoint.
|
|
987
|
+
* @param {object} req - Express request object.
|
|
988
|
+
* @param {object} res - Express response object.
|
|
989
|
+
* @param {string} req.params.id - The id of the tilejson
|
|
990
|
+
* @param {string} [req.params.tileSize] - The size of the tile, if specified.
|
|
991
|
+
* @returns {void}
|
|
992
|
+
*/
|
|
993
|
+
app.get('{/:tileSize}/:id.json', (req, res, next) => {
|
|
856
994
|
const item = repo[req.params.id];
|
|
857
995
|
if (!item) {
|
|
858
996
|
return res.sendStatus(404);
|
|
859
997
|
}
|
|
860
998
|
const tileSize = parseInt(req.params.tileSize, 10) || undefined;
|
|
999
|
+
if (verbose) {
|
|
1000
|
+
console.log(
|
|
1001
|
+
`Handling rendered tilejson request for: /styles/%s%s.json`,
|
|
1002
|
+
req.params.tileSize
|
|
1003
|
+
? String(req.params.tileSize).replace(/\n|\r/g, '') + '/'
|
|
1004
|
+
: '',
|
|
1005
|
+
String(req.params.id).replace(/\n|\r/g, ''),
|
|
1006
|
+
);
|
|
1007
|
+
}
|
|
861
1008
|
const info = clone(item.tileJSON);
|
|
862
1009
|
info.tiles = getTileUrls(
|
|
863
1010
|
req,
|
|
@@ -874,7 +1021,17 @@ export const serve_rendered = {
|
|
|
874
1021
|
Object.assign(existingFonts, fonts);
|
|
875
1022
|
return app;
|
|
876
1023
|
},
|
|
877
|
-
|
|
1024
|
+
/**
|
|
1025
|
+
* Adds a new item to the repository.
|
|
1026
|
+
* @param {object} options Configuration options.
|
|
1027
|
+
* @param {object} repo Repository object.
|
|
1028
|
+
* @param {object} params Parameters object.
|
|
1029
|
+
* @param {string} id ID of the item.
|
|
1030
|
+
* @param {object} programOpts - An object containing the program options
|
|
1031
|
+
* @param {Function} dataResolver Function to resolve data.
|
|
1032
|
+
* @returns {Promise<void>}
|
|
1033
|
+
*/
|
|
1034
|
+
add: async function (options, repo, params, id, programOpts, dataResolver) {
|
|
878
1035
|
const map = {
|
|
879
1036
|
renderers: [],
|
|
880
1037
|
renderersStatic: [],
|
|
@@ -882,23 +1039,45 @@ export const serve_rendered = {
|
|
|
882
1039
|
sourceTypes: {},
|
|
883
1040
|
};
|
|
884
1041
|
|
|
1042
|
+
const { publicUrl, verbose } = programOpts;
|
|
1043
|
+
|
|
885
1044
|
let styleJSON;
|
|
1045
|
+
/**
|
|
1046
|
+
* Creates a pool of renderers.
|
|
1047
|
+
* @param {number} ratio Pixel ratio
|
|
1048
|
+
* @param {string} mode Rendering mode ('tile' or 'static').
|
|
1049
|
+
* @param {number} min Minimum pool size.
|
|
1050
|
+
* @param {number} max Maximum pool size.
|
|
1051
|
+
* @returns {object} The created pool
|
|
1052
|
+
*/
|
|
886
1053
|
const createPool = (ratio, mode, min, max) => {
|
|
1054
|
+
/**
|
|
1055
|
+
* Creates a renderer
|
|
1056
|
+
* @param {number} ratio Pixel ratio
|
|
1057
|
+
* @param {Function} createCallback Function that returns the renderer when created
|
|
1058
|
+
* @returns {void}
|
|
1059
|
+
*/
|
|
887
1060
|
const createRenderer = (ratio, createCallback) => {
|
|
888
1061
|
const renderer = new mlgl.Map({
|
|
889
1062
|
mode,
|
|
890
1063
|
ratio,
|
|
891
1064
|
request: async (req, callback) => {
|
|
892
1065
|
const protocol = req.url.split(':')[0];
|
|
893
|
-
|
|
1066
|
+
if (verbose) {
|
|
1067
|
+
console.log('Handling request:', req);
|
|
1068
|
+
}
|
|
894
1069
|
if (protocol === 'sprites') {
|
|
895
1070
|
const dir = options.paths[protocol];
|
|
896
1071
|
const file = decodeURIComponent(req.url).substring(
|
|
897
1072
|
protocol.length + 3,
|
|
898
1073
|
);
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
1074
|
+
readFile(path.join(dir, file))
|
|
1075
|
+
.then((data) => {
|
|
1076
|
+
callback(null, { data: data });
|
|
1077
|
+
})
|
|
1078
|
+
.catch((err) => {
|
|
1079
|
+
callback(err, null);
|
|
1080
|
+
});
|
|
902
1081
|
} else if (protocol === 'fonts') {
|
|
903
1082
|
const parts = req.url.split('/');
|
|
904
1083
|
const fontstack = decodeURIComponent(parts[2]);
|
|
@@ -928,88 +1107,57 @@ export const serve_rendered = {
|
|
|
928
1107
|
const y = parts[5].split('.')[0] | 0;
|
|
929
1108
|
const format = parts[5].split('.')[1];
|
|
930
1109
|
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
1110
|
+
const fetchTile = await fetchTileData(
|
|
1111
|
+
source,
|
|
1112
|
+
sourceType,
|
|
1113
|
+
z,
|
|
1114
|
+
x,
|
|
1115
|
+
y,
|
|
1116
|
+
);
|
|
1117
|
+
if (fetchTile == null) {
|
|
1118
|
+
if (verbose) {
|
|
1119
|
+
console.log(
|
|
1120
|
+
'fetchTile error on %s, serving empty response',
|
|
1121
|
+
req.url,
|
|
942
1122
|
);
|
|
943
|
-
return;
|
|
944
|
-
} else {
|
|
945
|
-
const response = {};
|
|
946
|
-
response.data = data;
|
|
947
|
-
if (headers['Last-Modified']) {
|
|
948
|
-
response.modified = new Date(headers['Last-Modified']);
|
|
949
|
-
}
|
|
950
|
-
|
|
951
|
-
if (format === 'pbf') {
|
|
952
|
-
if (options.dataDecoratorFunc) {
|
|
953
|
-
response.data = options.dataDecoratorFunc(
|
|
954
|
-
sourceId,
|
|
955
|
-
'data',
|
|
956
|
-
response.data,
|
|
957
|
-
z,
|
|
958
|
-
x,
|
|
959
|
-
y,
|
|
960
|
-
);
|
|
961
|
-
}
|
|
962
|
-
}
|
|
963
|
-
|
|
964
|
-
callback(null, response);
|
|
965
1123
|
}
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
sourceInfo.format,
|
|
973
|
-
sourceInfo.color,
|
|
974
|
-
callback,
|
|
975
|
-
);
|
|
976
|
-
return;
|
|
977
|
-
}
|
|
978
|
-
|
|
979
|
-
const response = {};
|
|
980
|
-
if (headers['Last-Modified']) {
|
|
981
|
-
response.modified = new Date(headers['Last-Modified']);
|
|
982
|
-
}
|
|
983
|
-
|
|
984
|
-
if (format === 'pbf') {
|
|
985
|
-
try {
|
|
986
|
-
response.data = await gunzipP(data);
|
|
987
|
-
} catch (err) {
|
|
988
|
-
console.log(
|
|
989
|
-
'Skipping incorrect header for tile mbtiles://%s/%s/%s/%s.pbf',
|
|
990
|
-
id,
|
|
991
|
-
z,
|
|
992
|
-
x,
|
|
993
|
-
y,
|
|
994
|
-
);
|
|
995
|
-
}
|
|
996
|
-
if (options.dataDecoratorFunc) {
|
|
997
|
-
response.data = options.dataDecoratorFunc(
|
|
998
|
-
sourceId,
|
|
999
|
-
'data',
|
|
1000
|
-
response.data,
|
|
1001
|
-
z,
|
|
1002
|
-
x,
|
|
1003
|
-
y,
|
|
1004
|
-
);
|
|
1005
|
-
}
|
|
1006
|
-
} else {
|
|
1007
|
-
response.data = data;
|
|
1008
|
-
}
|
|
1009
|
-
|
|
1010
|
-
callback(null, response);
|
|
1011
|
-
});
|
|
1124
|
+
createEmptyResponse(
|
|
1125
|
+
sourceInfo.format,
|
|
1126
|
+
sourceInfo.color,
|
|
1127
|
+
callback,
|
|
1128
|
+
);
|
|
1129
|
+
return;
|
|
1012
1130
|
}
|
|
1131
|
+
|
|
1132
|
+
const response = {};
|
|
1133
|
+
response.data = fetchTile.data;
|
|
1134
|
+
let headers = fetchTile.headers;
|
|
1135
|
+
|
|
1136
|
+
if (headers['Last-Modified']) {
|
|
1137
|
+
response.modified = new Date(headers['Last-Modified']);
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
if (format === 'pbf') {
|
|
1141
|
+
let isGzipped =
|
|
1142
|
+
response.data
|
|
1143
|
+
.slice(0, 2)
|
|
1144
|
+
.indexOf(Buffer.from([0x1f, 0x8b])) === 0;
|
|
1145
|
+
if (isGzipped) {
|
|
1146
|
+
response.data = await gunzipP(response.data);
|
|
1147
|
+
}
|
|
1148
|
+
if (options.dataDecoratorFunc) {
|
|
1149
|
+
response.data = options.dataDecoratorFunc(
|
|
1150
|
+
sourceId,
|
|
1151
|
+
'data',
|
|
1152
|
+
response.data,
|
|
1153
|
+
z,
|
|
1154
|
+
x,
|
|
1155
|
+
y,
|
|
1156
|
+
);
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
callback(null, response);
|
|
1013
1161
|
} else if (protocol === 'http' || protocol === 'https') {
|
|
1014
1162
|
try {
|
|
1015
1163
|
const response = await axios.get(req.url, {
|
|
@@ -1052,9 +1200,13 @@ export const serve_rendered = {
|
|
|
1052
1200
|
);
|
|
1053
1201
|
}
|
|
1054
1202
|
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1203
|
+
readFile(file)
|
|
1204
|
+
.then((data) => {
|
|
1205
|
+
callback(null, { data: data });
|
|
1206
|
+
})
|
|
1207
|
+
.catch((err) => {
|
|
1208
|
+
callback(err, null);
|
|
1209
|
+
});
|
|
1058
1210
|
} else {
|
|
1059
1211
|
throw Error(
|
|
1060
1212
|
`File does not exist: "${req.url}" - resolved to "${file}"`,
|
|
@@ -1288,7 +1440,13 @@ export const serve_rendered = {
|
|
|
1288
1440
|
);
|
|
1289
1441
|
}
|
|
1290
1442
|
},
|
|
1291
|
-
|
|
1443
|
+
/**
|
|
1444
|
+
* Removes an item from the repository.
|
|
1445
|
+
* @param {object} repo Repository object.
|
|
1446
|
+
* @param {string} id ID of the item to remove.
|
|
1447
|
+
* @returns {void}
|
|
1448
|
+
*/
|
|
1449
|
+
remove: function (repo, id) {
|
|
1292
1450
|
const item = repo[id];
|
|
1293
1451
|
if (item) {
|
|
1294
1452
|
item.map.renderers.forEach((pool) => {
|