tileserver-gl-light 4.0.0 → 4.1.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/PUBLISHING.md +8 -1
- package/docs/deployment.rst +2 -2
- package/package.json +5 -5
- package/public/resources/maplibre-gl-inspect.css +2 -1
- package/public/templates/index.tmpl +1 -1
- package/publish.js +15 -9
- package/src/main.js +31 -30
- package/src/serve_data.js +22 -22
- package/src/serve_font.js +12 -12
- package/src/serve_light.js +10 -0
- package/src/serve_rendered.js +121 -120
- package/src/serve_style.js +15 -15
- package/src/server.js +122 -121
- package/src/utils.js +13 -15
- package/test/metadata.js +30 -30
- package/test/setup.js +9 -5
- package/test/static.js +4 -4
- package/test/style.js +18 -18
- package/test/tiles_data.js +4 -4
- package/test/tiles_rendered.js +5 -5
package/src/serve_rendered.js
CHANGED
|
@@ -1,37 +1,33 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const mbgl = require('@maplibre/maplibre-gl-native');
|
|
21
|
-
const MBTiles = require('@mapbox/mbtiles');
|
|
22
|
-
const proj4 = require('proj4');
|
|
23
|
-
const request = require('request');
|
|
24
|
-
|
|
25
|
-
const utils = require('./utils');
|
|
3
|
+
import advancedPool from 'advanced-pool';
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import url from 'url';
|
|
7
|
+
import util from 'util';
|
|
8
|
+
import zlib from 'zlib';
|
|
9
|
+
import sharp from 'sharp'; // sharp has to be required before node-canvas. see https://github.com/lovell/sharp/issues/371
|
|
10
|
+
import pkg from 'canvas';
|
|
11
|
+
import clone from 'clone';
|
|
12
|
+
import Color from 'color';
|
|
13
|
+
import express from 'express';
|
|
14
|
+
import SphericalMercator from '@mapbox/sphericalmercator';
|
|
15
|
+
import mlgl from '@maplibre/maplibre-gl-native';
|
|
16
|
+
import MBTiles from '@mapbox/mbtiles';
|
|
17
|
+
import proj4 from 'proj4';
|
|
18
|
+
import request from 'request';
|
|
19
|
+
import {getFontsPbf, getTileUrls, fixTileJSONCenter} from './utils.js';
|
|
26
20
|
|
|
27
21
|
const FLOAT_PATTERN = '[+-]?(?:\\d+|\\d+\.?\\d+)';
|
|
28
22
|
const httpTester = /^(http(s)?:)?\/\//;
|
|
29
23
|
|
|
30
|
-
const
|
|
24
|
+
const {createCanvas} = pkg;
|
|
25
|
+
const mercator = new SphericalMercator();
|
|
26
|
+
const getScale = (scale) => (scale || '@1x').slice(1, 2) | 0;
|
|
31
27
|
|
|
32
|
-
|
|
28
|
+
mlgl.on('message', (e) => {
|
|
33
29
|
if (e.severity === 'WARNING' || e.severity === 'ERROR') {
|
|
34
|
-
console.log('
|
|
30
|
+
console.log('mlgl:', e);
|
|
35
31
|
}
|
|
36
32
|
});
|
|
37
33
|
|
|
@@ -54,14 +50,14 @@ const cachedEmptyResponses = {
|
|
|
54
50
|
};
|
|
55
51
|
|
|
56
52
|
/**
|
|
57
|
-
* Create an appropriate
|
|
53
|
+
* Create an appropriate mlgl response for http errors.
|
|
58
54
|
* @param {string} format The format (a sharp format or 'pbf').
|
|
59
55
|
* @param {string} color The background color (or empty string for transparent).
|
|
60
|
-
* @param {Function} callback The
|
|
56
|
+
* @param {Function} callback The mlgl callback.
|
|
61
57
|
*/
|
|
62
58
|
function createEmptyResponse(format, color, callback) {
|
|
63
59
|
if (!format || format === 'pbf') {
|
|
64
|
-
callback(null, {
|
|
60
|
+
callback(null, {data: cachedEmptyResponses['']});
|
|
65
61
|
return;
|
|
66
62
|
}
|
|
67
63
|
|
|
@@ -75,7 +71,7 @@ function createEmptyResponse(format, color, callback) {
|
|
|
75
71
|
const cacheKey = `${format},${color}`;
|
|
76
72
|
const data = cachedEmptyResponses[cacheKey];
|
|
77
73
|
if (data) {
|
|
78
|
-
callback(null, {
|
|
74
|
+
callback(null, {data: data});
|
|
79
75
|
return;
|
|
80
76
|
}
|
|
81
77
|
|
|
@@ -93,7 +89,7 @@ function createEmptyResponse(format, color, callback) {
|
|
|
93
89
|
if (!err) {
|
|
94
90
|
cachedEmptyResponses[cacheKey] = buffer;
|
|
95
91
|
}
|
|
96
|
-
callback(null, {
|
|
92
|
+
callback(null, {data: buffer});
|
|
97
93
|
});
|
|
98
94
|
}
|
|
99
95
|
|
|
@@ -119,7 +115,7 @@ const extractPathFromQuery = (query, transformer) => {
|
|
|
119
115
|
};
|
|
120
116
|
|
|
121
117
|
const renderOverlay = (z, x, y, bearing, pitch, w, h, scale,
|
|
122
|
-
|
|
118
|
+
path, query) => {
|
|
123
119
|
if (!path || path.length < 2) {
|
|
124
120
|
return null;
|
|
125
121
|
}
|
|
@@ -179,14 +175,14 @@ const calcZForBBox = (bbox, w, h, query) => {
|
|
|
179
175
|
const padding = query.padding !== undefined ?
|
|
180
176
|
parseFloat(query.padding) : 0.1;
|
|
181
177
|
|
|
182
|
-
const minCorner = mercator.px([bbox[0], bbox[3]], z)
|
|
183
|
-
|
|
178
|
+
const minCorner = mercator.px([bbox[0], bbox[3]], z);
|
|
179
|
+
const maxCorner = mercator.px([bbox[2], bbox[1]], z);
|
|
184
180
|
const w_ = w / (1 + 2 * padding);
|
|
185
181
|
const h_ = h / (1 + 2 * padding);
|
|
186
182
|
|
|
187
183
|
z -= Math.max(
|
|
188
|
-
|
|
189
|
-
|
|
184
|
+
Math.log((maxCorner[0] - minCorner[0]) / w_),
|
|
185
|
+
Math.log((maxCorner[1] - minCorner[1]) / h_)
|
|
190
186
|
) / Math.LN2;
|
|
191
187
|
|
|
192
188
|
z = Math.max(Math.log(Math.max(w, h) / 256) / Math.LN2, Math.min(25, z));
|
|
@@ -197,7 +193,7 @@ const calcZForBBox = (bbox, w, h, query) => {
|
|
|
197
193
|
const existingFonts = {};
|
|
198
194
|
let maxScaleFactor = 2;
|
|
199
195
|
|
|
200
|
-
|
|
196
|
+
export const serve_rendered = {
|
|
201
197
|
init: (options, repo) => {
|
|
202
198
|
const fontListingPromise = new Promise((resolve, reject) => {
|
|
203
199
|
fs.readdir(options.paths.fonts, (err, files) => {
|
|
@@ -253,9 +249,9 @@ module.exports = {
|
|
|
253
249
|
pool = item.map.renderers_static[scale];
|
|
254
250
|
}
|
|
255
251
|
pool.acquire((err, renderer) => {
|
|
256
|
-
const
|
|
252
|
+
const mlglZ = Math.max(0, z - 1);
|
|
257
253
|
const params = {
|
|
258
|
-
zoom:
|
|
254
|
+
zoom: mlglZ,
|
|
259
255
|
center: [lon, lat],
|
|
260
256
|
bearing: bearing,
|
|
261
257
|
pitch: pitch,
|
|
@@ -282,9 +278,9 @@ module.exports = {
|
|
|
282
278
|
|
|
283
279
|
// Fix semi-transparent outlines on raw, premultiplied input
|
|
284
280
|
// https://github.com/maptiler/tileserver-gl/issues/350#issuecomment-477857040
|
|
285
|
-
for (
|
|
286
|
-
|
|
287
|
-
|
|
281
|
+
for (let i = 0; i < data.length; i += 4) {
|
|
282
|
+
const alpha = data[i + 3];
|
|
283
|
+
const norm = alpha / 255;
|
|
288
284
|
if (alpha === 0) {
|
|
289
285
|
data[i] = 0;
|
|
290
286
|
data[i + 1] = 0;
|
|
@@ -305,9 +301,11 @@ module.exports = {
|
|
|
305
301
|
});
|
|
306
302
|
|
|
307
303
|
if (z > 2 && tileMargin > 0) {
|
|
304
|
+
const [_, y] = mercator.px(params.center, z);
|
|
305
|
+
let yoffset = Math.max(Math.min(0, y - 128 - tileMargin), y + 128 + tileMargin - Math.pow(2, z + 8));
|
|
308
306
|
image.extract({
|
|
309
307
|
left: tileMargin * scale,
|
|
310
|
-
top: tileMargin * scale,
|
|
308
|
+
top: (tileMargin + yoffset) * scale,
|
|
311
309
|
width: width * scale,
|
|
312
310
|
height: height * scale
|
|
313
311
|
});
|
|
@@ -319,7 +317,7 @@ module.exports = {
|
|
|
319
317
|
}
|
|
320
318
|
|
|
321
319
|
if (opt_overlay) {
|
|
322
|
-
image.composite([{
|
|
320
|
+
image.composite([{input: opt_overlay}]);
|
|
323
321
|
}
|
|
324
322
|
if (item.watermark) {
|
|
325
323
|
const canvas = createCanvas(scale * width, scale * height);
|
|
@@ -332,17 +330,17 @@ module.exports = {
|
|
|
332
330
|
ctx.fillStyle = 'rgba(0,0,0,.4)';
|
|
333
331
|
ctx.fillText(item.watermark, 5, height - 5);
|
|
334
332
|
|
|
335
|
-
image.composite([{
|
|
333
|
+
image.composite([{input: canvas.toBuffer()}]);
|
|
336
334
|
}
|
|
337
335
|
|
|
338
336
|
const formatQuality = (options.formatQuality || {})[format];
|
|
339
337
|
|
|
340
338
|
if (format === 'png') {
|
|
341
|
-
image.png({
|
|
339
|
+
image.png({adaptiveFiltering: false});
|
|
342
340
|
} else if (format === 'jpeg') {
|
|
343
|
-
image.jpeg({
|
|
341
|
+
image.jpeg({quality: formatQuality || 80});
|
|
344
342
|
} else if (format === 'webp') {
|
|
345
|
-
image.webp({
|
|
343
|
+
image.webp({quality: formatQuality || 90});
|
|
346
344
|
}
|
|
347
345
|
image.toBuffer((err, buffer, info) => {
|
|
348
346
|
if (!buffer) {
|
|
@@ -365,18 +363,18 @@ module.exports = {
|
|
|
365
363
|
return res.sendStatus(404);
|
|
366
364
|
}
|
|
367
365
|
|
|
368
|
-
const modifiedSince = req.get('if-modified-since')
|
|
366
|
+
const modifiedSince = req.get('if-modified-since'); const cc = req.get('cache-control');
|
|
369
367
|
if (modifiedSince && (!cc || cc.indexOf('no-cache') === -1)) {
|
|
370
368
|
if (new Date(item.lastModified) <= new Date(modifiedSince)) {
|
|
371
369
|
return res.sendStatus(304);
|
|
372
370
|
}
|
|
373
371
|
}
|
|
374
372
|
|
|
375
|
-
const z = req.params.z | 0
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
373
|
+
const z = req.params.z | 0;
|
|
374
|
+
const x = req.params.x | 0;
|
|
375
|
+
const y = req.params.y | 0;
|
|
376
|
+
const scale = getScale(req.params.scale);
|
|
377
|
+
const format = req.params.format;
|
|
380
378
|
if (z < 0 || x < 0 || y < 0 ||
|
|
381
379
|
z > 22 || x >= Math.pow(2, z) || y >= Math.pow(2, z)) {
|
|
382
380
|
return res.status(404).send('Out of bounds');
|
|
@@ -395,8 +393,8 @@ module.exports = {
|
|
|
395
393
|
|
|
396
394
|
const centerPattern =
|
|
397
395
|
util.format(':x(%s),:y(%s),:z(%s)(@:bearing(%s)(,:pitch(%s))?)?',
|
|
398
|
-
|
|
399
|
-
|
|
396
|
+
FLOAT_PATTERN, FLOAT_PATTERN, FLOAT_PATTERN,
|
|
397
|
+
FLOAT_PATTERN, FLOAT_PATTERN);
|
|
400
398
|
|
|
401
399
|
app.get(util.format(staticPattern, centerPattern), (req, res, next) => {
|
|
402
400
|
const item = repo[req.params.id];
|
|
@@ -404,15 +402,15 @@ module.exports = {
|
|
|
404
402
|
return res.sendStatus(404);
|
|
405
403
|
}
|
|
406
404
|
const raw = req.params.raw;
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
405
|
+
const z = +req.params.z;
|
|
406
|
+
let x = +req.params.x;
|
|
407
|
+
let y = +req.params.y;
|
|
408
|
+
const bearing = +(req.params.bearing || '0');
|
|
409
|
+
const pitch = +(req.params.pitch || '0');
|
|
410
|
+
const w = req.params.width | 0;
|
|
411
|
+
const h = req.params.height | 0;
|
|
412
|
+
const scale = getScale(req.params.scale);
|
|
413
|
+
const format = req.params.format;
|
|
416
414
|
|
|
417
415
|
if (z < 0) {
|
|
418
416
|
return res.status(404).send('Invalid zoom');
|
|
@@ -439,8 +437,7 @@ module.exports = {
|
|
|
439
437
|
return res.sendStatus(404);
|
|
440
438
|
}
|
|
441
439
|
const raw = req.params.raw;
|
|
442
|
-
const bbox = [+req.params.minx, +req.params.miny,
|
|
443
|
-
+req.params.maxx, +req.params.maxy];
|
|
440
|
+
const bbox = [+req.params.minx, +req.params.miny, +req.params.maxx, +req.params.maxy];
|
|
444
441
|
let center = [(bbox[0] + bbox[2]) / 2, (bbox[1] + bbox[3]) / 2];
|
|
445
442
|
|
|
446
443
|
const transformer = raw ?
|
|
@@ -456,30 +453,31 @@ module.exports = {
|
|
|
456
453
|
center = transformer(center);
|
|
457
454
|
}
|
|
458
455
|
|
|
459
|
-
const w = req.params.width | 0
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
456
|
+
const w = req.params.width | 0;
|
|
457
|
+
const h = req.params.height | 0;
|
|
458
|
+
const scale = getScale(req.params.scale);
|
|
459
|
+
const format = req.params.format;
|
|
463
460
|
|
|
464
|
-
const z = calcZForBBox(bbox, w, h, req.query)
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
461
|
+
const z = calcZForBBox(bbox, w, h, req.query);
|
|
462
|
+
const x = center[0];
|
|
463
|
+
const y = center[1];
|
|
464
|
+
const bearing = 0;
|
|
465
|
+
const pitch = 0;
|
|
469
466
|
|
|
470
467
|
const path = extractPathFromQuery(req.query, transformer);
|
|
471
468
|
const overlay = renderOverlay(z, x, y, bearing, pitch, w, h, scale, path, req.query);
|
|
469
|
+
|
|
472
470
|
return respondImage(item, z, x, y, bearing, pitch, w, h, scale, format, res, next, overlay, 'static');
|
|
473
471
|
};
|
|
474
472
|
|
|
475
473
|
const boundsPattern =
|
|
476
474
|
util.format(':minx(%s),:miny(%s),:maxx(%s),:maxy(%s)',
|
|
477
|
-
|
|
475
|
+
FLOAT_PATTERN, FLOAT_PATTERN, FLOAT_PATTERN, FLOAT_PATTERN);
|
|
478
476
|
|
|
479
477
|
app.get(util.format(staticPattern, boundsPattern), serveBounds);
|
|
480
478
|
|
|
481
479
|
app.get('/:id/static/', (req, res, next) => {
|
|
482
|
-
for (
|
|
480
|
+
for (const key in req.query) {
|
|
483
481
|
req.query[key.toLowerCase()] = req.query[key];
|
|
484
482
|
}
|
|
485
483
|
req.params.raw = true;
|
|
@@ -508,12 +506,12 @@ module.exports = {
|
|
|
508
506
|
return res.sendStatus(404);
|
|
509
507
|
}
|
|
510
508
|
const raw = req.params.raw;
|
|
511
|
-
const w = req.params.width | 0
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
509
|
+
const w = req.params.width | 0;
|
|
510
|
+
const h = req.params.height | 0;
|
|
511
|
+
const bearing = 0;
|
|
512
|
+
const pitch = 0;
|
|
513
|
+
const scale = getScale(req.params.scale);
|
|
514
|
+
const format = req.params.format;
|
|
517
515
|
|
|
518
516
|
const transformer = raw ?
|
|
519
517
|
mercator.inverse.bind(mercator) : item.dataProjWGStoInternalWGS;
|
|
@@ -533,12 +531,12 @@ module.exports = {
|
|
|
533
531
|
|
|
534
532
|
const bbox_ = mercator.convert(bbox, '900913');
|
|
535
533
|
const center = mercator.inverse(
|
|
536
|
-
|
|
534
|
+
[(bbox_[0] + bbox_[2]) / 2, (bbox_[1] + bbox_[3]) / 2]
|
|
537
535
|
);
|
|
538
536
|
|
|
539
|
-
const z = calcZForBBox(bbox, w, h, req.query)
|
|
540
|
-
|
|
541
|
-
|
|
537
|
+
const z = calcZForBBox(bbox, w, h, req.query);
|
|
538
|
+
const x = center[0];
|
|
539
|
+
const y = center[1];
|
|
542
540
|
|
|
543
541
|
const overlay = renderOverlay(z, x, y, bearing, pitch, w, h, scale, path, req.query);
|
|
544
542
|
|
|
@@ -552,8 +550,8 @@ module.exports = {
|
|
|
552
550
|
return res.sendStatus(404);
|
|
553
551
|
}
|
|
554
552
|
const info = clone(item.tileJSON);
|
|
555
|
-
info.tiles =
|
|
556
|
-
|
|
553
|
+
info.tiles = getTileUrls(req, info.tiles,
|
|
554
|
+
`styles/${req.params.id}`, info.format, item.publicUrl);
|
|
557
555
|
return res.send(info);
|
|
558
556
|
});
|
|
559
557
|
|
|
@@ -569,38 +567,38 @@ module.exports = {
|
|
|
569
567
|
let styleJSON;
|
|
570
568
|
const createPool = (ratio, mode, min, max) => {
|
|
571
569
|
const createRenderer = (ratio, createCallback) => {
|
|
572
|
-
const renderer = new
|
|
570
|
+
const renderer = new mlgl.Map({
|
|
573
571
|
mode: mode,
|
|
574
572
|
ratio: ratio,
|
|
575
573
|
request: (req, callback) => {
|
|
576
574
|
const protocol = req.url.split(':')[0];
|
|
577
|
-
//console.log('Handling request:', req);
|
|
575
|
+
// console.log('Handling request:', req);
|
|
578
576
|
if (protocol === 'sprites') {
|
|
579
577
|
const dir = options.paths[protocol];
|
|
580
578
|
const file = unescape(req.url).substring(protocol.length + 3);
|
|
581
579
|
fs.readFile(path.join(dir, file), (err, data) => {
|
|
582
|
-
callback(err, {
|
|
580
|
+
callback(err, {data: data});
|
|
583
581
|
});
|
|
584
582
|
} else if (protocol === 'fonts') {
|
|
585
583
|
const parts = req.url.split('/');
|
|
586
584
|
const fontstack = unescape(parts[2]);
|
|
587
585
|
const range = parts[3].split('.')[0];
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
).then(concated => {
|
|
591
|
-
callback(null, {
|
|
592
|
-
}, err => {
|
|
593
|
-
callback(err, {
|
|
586
|
+
getFontsPbf(
|
|
587
|
+
null, options.paths[protocol], fontstack, range, existingFonts
|
|
588
|
+
).then((concated) => {
|
|
589
|
+
callback(null, {data: concated});
|
|
590
|
+
}, (err) => {
|
|
591
|
+
callback(err, {data: null});
|
|
594
592
|
});
|
|
595
593
|
} else if (protocol === 'mbtiles') {
|
|
596
594
|
const parts = req.url.split('/');
|
|
597
595
|
const sourceId = parts[2];
|
|
598
596
|
const source = map.sources[sourceId];
|
|
599
597
|
const sourceInfo = styleJSON.sources[sourceId];
|
|
600
|
-
const z = parts[3] | 0
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
598
|
+
const z = parts[3] | 0;
|
|
599
|
+
const x = parts[4] | 0;
|
|
600
|
+
const y = parts[5].split('.')[0] | 0;
|
|
601
|
+
const format = parts[5].split('.')[1];
|
|
604
602
|
source.getTile(z, x, y, (err, data, headers) => {
|
|
605
603
|
if (err) {
|
|
606
604
|
if (options.verbose) console.log('MBTiles error, serving empty', err);
|
|
@@ -617,11 +615,11 @@ module.exports = {
|
|
|
617
615
|
try {
|
|
618
616
|
response.data = zlib.unzipSync(data);
|
|
619
617
|
} catch (err) {
|
|
620
|
-
console.log(
|
|
618
|
+
console.log('Skipping incorrect header for tile mbtiles://%s/%s/%s/%s.pbf', id, z, x, y);
|
|
621
619
|
}
|
|
622
620
|
if (options.dataDecoratorFunc) {
|
|
623
621
|
response.data = options.dataDecoratorFunc(
|
|
624
|
-
|
|
622
|
+
sourceId, 'data', response.data, z, x, y);
|
|
625
623
|
}
|
|
626
624
|
} else {
|
|
627
625
|
response.data = data;
|
|
@@ -668,7 +666,7 @@ module.exports = {
|
|
|
668
666
|
min: min,
|
|
669
667
|
max: max,
|
|
670
668
|
create: createRenderer.bind(null, ratio),
|
|
671
|
-
destroy: renderer => {
|
|
669
|
+
destroy: (renderer) => {
|
|
672
670
|
renderer.release();
|
|
673
671
|
}
|
|
674
672
|
});
|
|
@@ -686,8 +684,8 @@ module.exports = {
|
|
|
686
684
|
if (styleJSON.sprite && !httpTester.test(styleJSON.sprite)) {
|
|
687
685
|
styleJSON.sprite = 'sprites://' +
|
|
688
686
|
styleJSON.sprite
|
|
689
|
-
|
|
690
|
-
|
|
687
|
+
.replace('{style}', path.basename(styleFile, '.json'))
|
|
688
|
+
.replace('{styleJsonFolder}', path.relative(options.paths.sprites, path.dirname(styleJSONPath)));
|
|
691
689
|
}
|
|
692
690
|
if (styleJSON.glyphs && !httpTester.test(styleJSON.glyphs)) {
|
|
693
691
|
styleJSON.glyphs = `fonts://${styleJSON.glyphs}`;
|
|
@@ -718,9 +716,9 @@ module.exports = {
|
|
|
718
716
|
const attributionOverride = params.tilejson && params.tilejson.attribution;
|
|
719
717
|
Object.assign(tileJSON, params.tilejson || {});
|
|
720
718
|
tileJSON.tiles = params.domains || options.domains;
|
|
721
|
-
|
|
719
|
+
fixTileJSONCenter(tileJSON);
|
|
722
720
|
|
|
723
|
-
|
|
721
|
+
const repoobj = {
|
|
724
722
|
tileJSON,
|
|
725
723
|
publicUrl,
|
|
726
724
|
map,
|
|
@@ -728,6 +726,7 @@ module.exports = {
|
|
|
728
726
|
lastModified: new Date().toUTCString(),
|
|
729
727
|
watermark: params.watermark || options.watermark
|
|
730
728
|
};
|
|
729
|
+
repo[id] = repoobj;
|
|
731
730
|
|
|
732
731
|
const queue = [];
|
|
733
732
|
for (const name of Object.keys(styleJSON.sources)) {
|
|
@@ -761,18 +760,18 @@ module.exports = {
|
|
|
761
760
|
if (!mbtilesFileStats.isFile() || mbtilesFileStats.size === 0) {
|
|
762
761
|
throw Error(`Not valid MBTiles file: ${mbtilesFile}`);
|
|
763
762
|
}
|
|
764
|
-
map.sources[name] = new MBTiles(mbtilesFile, err => {
|
|
763
|
+
map.sources[name] = new MBTiles(mbtilesFile + '?mode=ro', err => {
|
|
765
764
|
map.sources[name].getInfo((err, info) => {
|
|
766
765
|
if (err) {
|
|
767
766
|
console.error(err);
|
|
768
767
|
return;
|
|
769
768
|
}
|
|
770
769
|
|
|
771
|
-
if (!
|
|
770
|
+
if (!repoobj.dataProjWGStoInternalWGS && info.proj4) {
|
|
772
771
|
// how to do this for multiple sources with different proj4 defs?
|
|
773
772
|
const to3857 = proj4('EPSG:3857');
|
|
774
773
|
const toDataProj = proj4(info.proj4);
|
|
775
|
-
|
|
774
|
+
repoobj.dataProjWGStoInternalWGS = (xy) => to3857.inverse(toDataProj.forward(xy));
|
|
776
775
|
}
|
|
777
776
|
|
|
778
777
|
const type = source.type;
|
|
@@ -790,10 +789,12 @@ module.exports = {
|
|
|
790
789
|
|
|
791
790
|
if (!attributionOverride &&
|
|
792
791
|
source.attribution && source.attribution.length > 0) {
|
|
793
|
-
if (tileJSON.attribution.
|
|
794
|
-
tileJSON.attribution
|
|
792
|
+
if (!tileJSON.attribution.includes(source.attribution)) {
|
|
793
|
+
if (tileJSON.attribution.length > 0) {
|
|
794
|
+
tileJSON.attribution += ' | ';
|
|
795
|
+
}
|
|
796
|
+
tileJSON.attribution += source.attribution;
|
|
795
797
|
}
|
|
796
|
-
tileJSON.attribution += source.attribution;
|
|
797
798
|
}
|
|
798
799
|
resolve();
|
|
799
800
|
});
|
|
@@ -819,15 +820,15 @@ module.exports = {
|
|
|
819
820
|
return Promise.all([renderersReadyPromise]);
|
|
820
821
|
},
|
|
821
822
|
remove: (repo, id) => {
|
|
822
|
-
|
|
823
|
+
const item = repo[id];
|
|
823
824
|
if (item) {
|
|
824
|
-
item.map.renderers.forEach(pool => {
|
|
825
|
+
item.map.renderers.forEach((pool) => {
|
|
825
826
|
pool.close();
|
|
826
827
|
});
|
|
827
|
-
item.map.renderers_static.forEach(pool => {
|
|
828
|
+
item.map.renderers_static.forEach((pool) => {
|
|
828
829
|
pool.close();
|
|
829
830
|
});
|
|
830
831
|
}
|
|
831
832
|
delete repo[id];
|
|
832
|
-
}
|
|
833
|
+
}
|
|
833
834
|
};
|
package/src/serve_style.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import fs from 'node:fs';
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
import clone from 'clone';
|
|
7
|
+
import express from 'express';
|
|
8
8
|
import {validate} from '@maplibre/maplibre-gl-style-spec';
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
import {getPublicUrl} from './utils.js';
|
|
11
11
|
|
|
12
12
|
const httpTester = /^(http(s)?:)?\/\//;
|
|
13
13
|
|
|
@@ -24,10 +24,10 @@ const fixUrl = (req, url, publicUrl, opt_nokey) => {
|
|
|
24
24
|
query = `?${queryParams.join('&')}`;
|
|
25
25
|
}
|
|
26
26
|
return url.replace(
|
|
27
|
-
|
|
27
|
+
'local://', getPublicUrl(publicUrl, req)) + query;
|
|
28
28
|
};
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
export const serve_style = {
|
|
31
31
|
init: (options, repo) => {
|
|
32
32
|
const app = express().disable('x-powered-by');
|
|
33
33
|
|
|
@@ -56,8 +56,8 @@ module.exports = {
|
|
|
56
56
|
if (!item || !item.spritePath) {
|
|
57
57
|
return res.sendStatus(404);
|
|
58
58
|
}
|
|
59
|
-
const scale = req.params.scale
|
|
60
|
-
|
|
59
|
+
const scale = req.params.scale;
|
|
60
|
+
const format = req.params.format;
|
|
61
61
|
const filename = `${item.spritePath + (scale || '')}.${format}`;
|
|
62
62
|
return fs.readFile(filename, (err, data) => {
|
|
63
63
|
if (err) {
|
|
@@ -87,7 +87,7 @@ module.exports = {
|
|
|
87
87
|
return false;
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
|
|
90
|
+
const validationErrors = validate(styleFileData);
|
|
91
91
|
if (validationErrors.length > 0) {
|
|
92
92
|
console.log(`The file "${params.style}" is not valid a valid style file:`);
|
|
93
93
|
for (const err of validationErrors) {
|
|
@@ -95,7 +95,7 @@ module.exports = {
|
|
|
95
95
|
}
|
|
96
96
|
return false;
|
|
97
97
|
}
|
|
98
|
-
|
|
98
|
+
const styleJSON = JSON.parse(styleFileData);
|
|
99
99
|
|
|
100
100
|
for (const name of Object.keys(styleJSON.sources)) {
|
|
101
101
|
const source = styleJSON.sources[name];
|
|
@@ -120,7 +120,7 @@ module.exports = {
|
|
|
120
120
|
}
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
-
for (
|
|
123
|
+
for (const obj of styleJSON.layers) {
|
|
124
124
|
if (obj['type'] === 'symbol') {
|
|
125
125
|
const fonts = (obj['layout'] || {})['text-font'];
|
|
126
126
|
if (fonts && fonts.length) {
|
|
@@ -136,9 +136,9 @@ module.exports = {
|
|
|
136
136
|
|
|
137
137
|
if (styleJSON.sprite && !httpTester.test(styleJSON.sprite)) {
|
|
138
138
|
spritePath = path.join(options.paths.sprites,
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
139
|
+
styleJSON.sprite
|
|
140
|
+
.replace('{style}', path.basename(styleFile, '.json'))
|
|
141
|
+
.replace('{styleJsonFolder}', path.relative(options.paths.sprites, path.dirname(styleFile)))
|
|
142
142
|
);
|
|
143
143
|
styleJSON.sprite = `local://styles/${id}/sprite`;
|
|
144
144
|
}
|