tileserver-gl-light 4.5.2 → 4.6.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/LICENSE.md +32 -0
- package/docs/config.rst +52 -10
- package/package.json +3 -1
- package/public/templates/index.tmpl +3 -2
- package/src/main.js +166 -89
- package/src/pmtiles_adapter.js +151 -0
- package/src/serve_data.js +188 -88
- package/src/serve_rendered.js +203 -98
- package/src/serve_style.js +17 -13
- package/src/server.js +114 -62
- package/src/utils.js +12 -1
package/src/serve_rendered.js
CHANGED
|
@@ -6,7 +6,7 @@ import path from 'path';
|
|
|
6
6
|
import url from 'url';
|
|
7
7
|
import util from 'util';
|
|
8
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
|
|
9
|
+
import sharp from 'sharp'; // sharp has to be required before node-canvas on linux but after it on windows. see https://github.com/lovell/sharp/issues/371
|
|
10
10
|
import { createCanvas, Image } from 'canvas';
|
|
11
11
|
import clone from 'clone';
|
|
12
12
|
import Color from 'color';
|
|
@@ -18,7 +18,17 @@ import MBTiles from '@mapbox/mbtiles';
|
|
|
18
18
|
import polyline from '@mapbox/polyline';
|
|
19
19
|
import proj4 from 'proj4';
|
|
20
20
|
import request from 'request';
|
|
21
|
-
import {
|
|
21
|
+
import {
|
|
22
|
+
getFontsPbf,
|
|
23
|
+
getTileUrls,
|
|
24
|
+
isValidHttpUrl,
|
|
25
|
+
fixTileJSONCenter,
|
|
26
|
+
} from './utils.js';
|
|
27
|
+
import {
|
|
28
|
+
PMtilesOpen,
|
|
29
|
+
GetPMtilesInfo,
|
|
30
|
+
GetPMtilesTile,
|
|
31
|
+
} from './pmtiles_adapter.js';
|
|
22
32
|
|
|
23
33
|
const FLOAT_PATTERN = '[+-]?(?:\\d+|\\d+.?\\d+)';
|
|
24
34
|
const PATH_PATTERN =
|
|
@@ -1207,11 +1217,12 @@ export const serve_rendered = {
|
|
|
1207
1217
|
|
|
1208
1218
|
return Promise.all([fontListingPromise]).then(() => app);
|
|
1209
1219
|
},
|
|
1210
|
-
add: (options, repo, params, id, publicUrl, dataResolver) => {
|
|
1220
|
+
add: async (options, repo, params, id, publicUrl, dataResolver) => {
|
|
1211
1221
|
const map = {
|
|
1212
1222
|
renderers: [],
|
|
1213
1223
|
renderers_static: [],
|
|
1214
1224
|
sources: {},
|
|
1225
|
+
source_types: {},
|
|
1215
1226
|
};
|
|
1216
1227
|
|
|
1217
1228
|
let styleJSON;
|
|
@@ -1220,7 +1231,7 @@ export const serve_rendered = {
|
|
|
1220
1231
|
const renderer = new mlgl.Map({
|
|
1221
1232
|
mode: mode,
|
|
1222
1233
|
ratio: ratio,
|
|
1223
|
-
request: (req, callback) => {
|
|
1234
|
+
request: async (req, callback) => {
|
|
1224
1235
|
const protocol = req.url.split(':')[0];
|
|
1225
1236
|
// console.log('Handling request:', req);
|
|
1226
1237
|
if (protocol === 'sprites') {
|
|
@@ -1247,17 +1258,23 @@ export const serve_rendered = {
|
|
|
1247
1258
|
callback(err, { data: null });
|
|
1248
1259
|
},
|
|
1249
1260
|
);
|
|
1250
|
-
} else if (protocol === 'mbtiles') {
|
|
1261
|
+
} else if (protocol === 'mbtiles' || protocol === 'pmtiles') {
|
|
1251
1262
|
const parts = req.url.split('/');
|
|
1252
1263
|
const sourceId = parts[2];
|
|
1253
1264
|
const source = map.sources[sourceId];
|
|
1265
|
+
const source_type = map.source_types[sourceId];
|
|
1254
1266
|
const sourceInfo = styleJSON.sources[sourceId];
|
|
1267
|
+
|
|
1255
1268
|
const z = parts[3] | 0;
|
|
1256
1269
|
const x = parts[4] | 0;
|
|
1257
1270
|
const y = parts[5].split('.')[0] | 0;
|
|
1258
1271
|
const format = parts[5].split('.')[1];
|
|
1259
|
-
|
|
1260
|
-
|
|
1272
|
+
|
|
1273
|
+
if (source_type === 'pmtiles') {
|
|
1274
|
+
let tileinfo = await GetPMtilesTile(source, z, x, y);
|
|
1275
|
+
let data = tileinfo.data;
|
|
1276
|
+
let headers = tileinfo.header;
|
|
1277
|
+
if (data == undefined) {
|
|
1261
1278
|
if (options.verbose)
|
|
1262
1279
|
console.log('MBTiles error, serving empty', err);
|
|
1263
1280
|
createEmptyResponse(
|
|
@@ -1266,41 +1283,75 @@ export const serve_rendered = {
|
|
|
1266
1283
|
callback,
|
|
1267
1284
|
);
|
|
1268
1285
|
return;
|
|
1269
|
-
}
|
|
1286
|
+
} else {
|
|
1287
|
+
const response = {};
|
|
1288
|
+
response.data = data;
|
|
1289
|
+
if (headers['Last-Modified']) {
|
|
1290
|
+
response.modified = new Date(headers['Last-Modified']);
|
|
1291
|
+
}
|
|
1270
1292
|
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1293
|
+
if (format === 'pbf') {
|
|
1294
|
+
if (options.dataDecoratorFunc) {
|
|
1295
|
+
response.data = options.dataDecoratorFunc(
|
|
1296
|
+
sourceId,
|
|
1297
|
+
'data',
|
|
1298
|
+
response.data,
|
|
1299
|
+
z,
|
|
1300
|
+
x,
|
|
1301
|
+
y,
|
|
1302
|
+
);
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1275
1305
|
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1306
|
+
callback(null, response);
|
|
1307
|
+
}
|
|
1308
|
+
} else if (source_type === 'mbtiles') {
|
|
1309
|
+
source.getTile(z, x, y, (err, data, headers) => {
|
|
1310
|
+
if (err) {
|
|
1311
|
+
if (options.verbose)
|
|
1312
|
+
console.log('MBTiles error, serving empty', err);
|
|
1313
|
+
createEmptyResponse(
|
|
1314
|
+
sourceInfo.format,
|
|
1315
|
+
sourceInfo.color,
|
|
1316
|
+
callback,
|
|
1286
1317
|
);
|
|
1318
|
+
return;
|
|
1287
1319
|
}
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
response.data,
|
|
1293
|
-
z,
|
|
1294
|
-
x,
|
|
1295
|
-
y,
|
|
1296
|
-
);
|
|
1320
|
+
|
|
1321
|
+
const response = {};
|
|
1322
|
+
if (headers['Last-Modified']) {
|
|
1323
|
+
response.modified = new Date(headers['Last-Modified']);
|
|
1297
1324
|
}
|
|
1298
|
-
} else {
|
|
1299
|
-
response.data = data;
|
|
1300
|
-
}
|
|
1301
1325
|
|
|
1302
|
-
|
|
1303
|
-
|
|
1326
|
+
if (format === 'pbf') {
|
|
1327
|
+
try {
|
|
1328
|
+
response.data = zlib.unzipSync(data);
|
|
1329
|
+
} catch (err) {
|
|
1330
|
+
console.log(
|
|
1331
|
+
'Skipping incorrect header for tile mbtiles://%s/%s/%s/%s.pbf',
|
|
1332
|
+
id,
|
|
1333
|
+
z,
|
|
1334
|
+
x,
|
|
1335
|
+
y,
|
|
1336
|
+
);
|
|
1337
|
+
}
|
|
1338
|
+
if (options.dataDecoratorFunc) {
|
|
1339
|
+
response.data = options.dataDecoratorFunc(
|
|
1340
|
+
sourceId,
|
|
1341
|
+
'data',
|
|
1342
|
+
response.data,
|
|
1343
|
+
z,
|
|
1344
|
+
x,
|
|
1345
|
+
y,
|
|
1346
|
+
);
|
|
1347
|
+
}
|
|
1348
|
+
} else {
|
|
1349
|
+
response.data = data;
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
callback(null, response);
|
|
1353
|
+
});
|
|
1354
|
+
}
|
|
1304
1355
|
} else if (protocol === 'http' || protocol === 'https') {
|
|
1305
1356
|
request(
|
|
1306
1357
|
{
|
|
@@ -1416,82 +1467,136 @@ export const serve_rendered = {
|
|
|
1416
1467
|
|
|
1417
1468
|
const queue = [];
|
|
1418
1469
|
for (const name of Object.keys(styleJSON.sources)) {
|
|
1470
|
+
let source_type;
|
|
1419
1471
|
let source = styleJSON.sources[name];
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1472
|
+
let url = source.url;
|
|
1473
|
+
if (
|
|
1474
|
+
url &&
|
|
1475
|
+
(url.startsWith('pmtiles://') || url.startsWith('mbtiles://'))
|
|
1476
|
+
) {
|
|
1477
|
+
// found pmtiles or mbtiles source, replace with info from local file
|
|
1424
1478
|
delete source.url;
|
|
1425
1479
|
|
|
1426
|
-
let
|
|
1427
|
-
|
|
1428
|
-
|
|
1480
|
+
let dataId = url.replace('pmtiles://', '').replace('mbtiles://', '');
|
|
1481
|
+
if (dataId.startsWith('{') && dataId.endsWith('}')) {
|
|
1482
|
+
dataId = dataId.slice(1, -1);
|
|
1483
|
+
}
|
|
1429
1484
|
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1485
|
+
const mapsTo = (params.mapping || {})[dataId];
|
|
1486
|
+
if (mapsTo) {
|
|
1487
|
+
dataId = mapsTo;
|
|
1488
|
+
}
|
|
1489
|
+
|
|
1490
|
+
let inputFile;
|
|
1491
|
+
const DataInfo = dataResolver(dataId);
|
|
1492
|
+
if (DataInfo.inputfile) {
|
|
1493
|
+
inputFile = DataInfo.inputfile;
|
|
1494
|
+
source_type = DataInfo.filetype;
|
|
1495
|
+
} else {
|
|
1496
|
+
console.error(`ERROR: data "${inputFile}" not found!`);
|
|
1497
|
+
process.exit(1);
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
if (!isValidHttpUrl(inputFile)) {
|
|
1501
|
+
const inputFileStats = fs.statSync(inputFile);
|
|
1502
|
+
if (!inputFileStats.isFile() || inputFileStats.size === 0) {
|
|
1503
|
+
throw Error(`Not valid PMTiles file: "${inputFile}"`);
|
|
1440
1504
|
}
|
|
1441
1505
|
}
|
|
1442
1506
|
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1507
|
+
if (source_type === 'pmtiles') {
|
|
1508
|
+
map.sources[name] = PMtilesOpen(inputFile);
|
|
1509
|
+
map.source_types[name] = 'pmtiles';
|
|
1510
|
+
const metadata = await GetPMtilesInfo(map.sources[name]);
|
|
1511
|
+
|
|
1512
|
+
if (!repoobj.dataProjWGStoInternalWGS && metadata.proj4) {
|
|
1513
|
+
// how to do this for multiple sources with different proj4 defs?
|
|
1514
|
+
const to3857 = proj4('EPSG:3857');
|
|
1515
|
+
const toDataProj = proj4(metadata.proj4);
|
|
1516
|
+
repoobj.dataProjWGStoInternalWGS = (xy) =>
|
|
1517
|
+
to3857.inverse(toDataProj.forward(xy));
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1520
|
+
const type = source.type;
|
|
1521
|
+
Object.assign(source, metadata);
|
|
1522
|
+
source.type = type;
|
|
1523
|
+
source.tiles = [
|
|
1524
|
+
// meta url which will be detected when requested
|
|
1525
|
+
`pmtiles://${name}/{z}/{x}/{y}.${metadata.format || 'pbf'}`,
|
|
1526
|
+
];
|
|
1527
|
+
delete source.scheme;
|
|
1528
|
+
|
|
1529
|
+
if (
|
|
1530
|
+
!attributionOverride &&
|
|
1531
|
+
source.attribution &&
|
|
1532
|
+
source.attribution.length > 0
|
|
1533
|
+
) {
|
|
1534
|
+
if (!tileJSON.attribution.includes(source.attribution)) {
|
|
1535
|
+
if (tileJSON.attribution.length > 0) {
|
|
1536
|
+
tileJSON.attribution += ' | ';
|
|
1537
|
+
}
|
|
1538
|
+
tileJSON.attribution += source.attribution;
|
|
1449
1539
|
}
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1540
|
+
}
|
|
1541
|
+
} else {
|
|
1542
|
+
queue.push(
|
|
1543
|
+
new Promise((resolve, reject) => {
|
|
1544
|
+
inputFile = path.resolve(options.paths.mbtiles, inputFile);
|
|
1545
|
+
const inputFileStats = fs.statSync(inputFile);
|
|
1546
|
+
if (!inputFileStats.isFile() || inputFileStats.size === 0) {
|
|
1547
|
+
throw Error(`Not valid MBTiles file: "${inputFile}"`);
|
|
1548
|
+
}
|
|
1549
|
+
map.sources[name] = new MBTiles(inputFile + '?mode=ro', (err) => {
|
|
1550
|
+
map.sources[name].getInfo((err, info) => {
|
|
1551
|
+
if (err) {
|
|
1552
|
+
console.error(err);
|
|
1553
|
+
return;
|
|
1554
|
+
}
|
|
1555
|
+
map.source_types[name] = 'mbtiles';
|
|
1556
|
+
|
|
1557
|
+
if (!repoobj.dataProjWGStoInternalWGS && info.proj4) {
|
|
1558
|
+
// how to do this for multiple sources with different proj4 defs?
|
|
1559
|
+
const to3857 = proj4('EPSG:3857');
|
|
1560
|
+
const toDataProj = proj4(info.proj4);
|
|
1561
|
+
repoobj.dataProjWGStoInternalWGS = (xy) =>
|
|
1562
|
+
to3857.inverse(toDataProj.forward(xy));
|
|
1563
|
+
}
|
|
1456
1564
|
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1565
|
+
const type = source.type;
|
|
1566
|
+
Object.assign(source, info);
|
|
1567
|
+
source.type = type;
|
|
1568
|
+
source.tiles = [
|
|
1569
|
+
// meta url which will be detected when requested
|
|
1570
|
+
`mbtiles://${name}/{z}/{x}/{y}.${info.format || 'pbf'}`,
|
|
1571
|
+
];
|
|
1572
|
+
delete source.scheme;
|
|
1464
1573
|
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
delete source.scheme;
|
|
1473
|
-
|
|
1474
|
-
if (options.dataDecoratorFunc) {
|
|
1475
|
-
source = options.dataDecoratorFunc(name, 'tilejson', source);
|
|
1476
|
-
}
|
|
1574
|
+
if (options.dataDecoratorFunc) {
|
|
1575
|
+
source = options.dataDecoratorFunc(
|
|
1576
|
+
name,
|
|
1577
|
+
'tilejson',
|
|
1578
|
+
source,
|
|
1579
|
+
);
|
|
1580
|
+
}
|
|
1477
1581
|
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1582
|
+
if (
|
|
1583
|
+
!attributionOverride &&
|
|
1584
|
+
source.attribution &&
|
|
1585
|
+
source.attribution.length > 0
|
|
1586
|
+
) {
|
|
1587
|
+
if (!tileJSON.attribution.includes(source.attribution)) {
|
|
1588
|
+
if (tileJSON.attribution.length > 0) {
|
|
1589
|
+
tileJSON.attribution += ' | ';
|
|
1590
|
+
}
|
|
1591
|
+
tileJSON.attribution += source.attribution;
|
|
1486
1592
|
}
|
|
1487
|
-
tileJSON.attribution += source.attribution;
|
|
1488
1593
|
}
|
|
1489
|
-
|
|
1490
|
-
|
|
1594
|
+
resolve();
|
|
1595
|
+
});
|
|
1491
1596
|
});
|
|
1492
|
-
})
|
|
1493
|
-
|
|
1494
|
-
|
|
1597
|
+
}),
|
|
1598
|
+
);
|
|
1599
|
+
}
|
|
1495
1600
|
}
|
|
1496
1601
|
}
|
|
1497
1602
|
|
package/src/serve_style.js
CHANGED
|
@@ -110,20 +110,24 @@ export const serve_style = {
|
|
|
110
110
|
|
|
111
111
|
for (const name of Object.keys(styleJSON.sources)) {
|
|
112
112
|
const source = styleJSON.sources[name];
|
|
113
|
-
|
|
114
|
-
if (
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
mbtilesFile = mapsTo;
|
|
124
|
-
}
|
|
113
|
+
let url = source.url;
|
|
114
|
+
if (
|
|
115
|
+
url &&
|
|
116
|
+
(url.startsWith('pmtiles://') || url.startsWith('mbtiles://'))
|
|
117
|
+
) {
|
|
118
|
+
const protocol = url.split(':')[0];
|
|
119
|
+
|
|
120
|
+
let dataId = url.replace('pmtiles://', '').replace('mbtiles://', '');
|
|
121
|
+
if (dataId.startsWith('{') && dataId.endsWith('}')) {
|
|
122
|
+
dataId = dataId.slice(1, -1);
|
|
125
123
|
}
|
|
126
|
-
|
|
124
|
+
|
|
125
|
+
const mapsTo = (params.mapping || {})[dataId];
|
|
126
|
+
if (mapsTo) {
|
|
127
|
+
dataId = mapsTo;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const identifier = reportTiles(dataId, protocol);
|
|
127
131
|
if (!identifier) {
|
|
128
132
|
return false;
|
|
129
133
|
}
|