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.
@@ -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 { getFontsPbf, getTileUrls, fixTileJSONCenter } from './utils.js';
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
- source.getTile(z, x, y, (err, data, headers) => {
1260
- if (err) {
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
- const response = {};
1272
- if (headers['Last-Modified']) {
1273
- response.modified = new Date(headers['Last-Modified']);
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
- if (format === 'pbf') {
1277
- try {
1278
- response.data = zlib.unzipSync(data);
1279
- } catch (err) {
1280
- console.log(
1281
- 'Skipping incorrect header for tile mbtiles://%s/%s/%s/%s.pbf',
1282
- id,
1283
- z,
1284
- x,
1285
- y,
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
- if (options.dataDecoratorFunc) {
1289
- response.data = options.dataDecoratorFunc(
1290
- sourceId,
1291
- 'data',
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
- callback(null, response);
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
- const url = source.url;
1421
-
1422
- if (url && url.lastIndexOf('mbtiles:', 0) === 0) {
1423
- // found mbtiles source, replace with info from local file
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 mbtilesFile = url.substring('mbtiles://'.length);
1427
- const fromData =
1428
- mbtilesFile[0] === '{' && mbtilesFile[mbtilesFile.length - 1] === '}';
1480
+ let dataId = url.replace('pmtiles://', '').replace('mbtiles://', '');
1481
+ if (dataId.startsWith('{') && dataId.endsWith('}')) {
1482
+ dataId = dataId.slice(1, -1);
1483
+ }
1429
1484
 
1430
- if (fromData) {
1431
- mbtilesFile = mbtilesFile.substr(1, mbtilesFile.length - 2);
1432
- const mapsTo = (params.mapping || {})[mbtilesFile];
1433
- if (mapsTo) {
1434
- mbtilesFile = mapsTo;
1435
- }
1436
- mbtilesFile = dataResolver(mbtilesFile);
1437
- if (!mbtilesFile) {
1438
- console.error(`ERROR: data "${mbtilesFile}" not found!`);
1439
- process.exit(1);
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
- queue.push(
1444
- new Promise((resolve, reject) => {
1445
- mbtilesFile = path.resolve(options.paths.mbtiles, mbtilesFile);
1446
- const mbtilesFileStats = fs.statSync(mbtilesFile);
1447
- if (!mbtilesFileStats.isFile() || mbtilesFileStats.size === 0) {
1448
- throw Error(`Not valid MBTiles file: ${mbtilesFile}`);
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
- map.sources[name] = new MBTiles(mbtilesFile + '?mode=ro', (err) => {
1451
- map.sources[name].getInfo((err, info) => {
1452
- if (err) {
1453
- console.error(err);
1454
- return;
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
- if (!repoobj.dataProjWGStoInternalWGS && info.proj4) {
1458
- // how to do this for multiple sources with different proj4 defs?
1459
- const to3857 = proj4('EPSG:3857');
1460
- const toDataProj = proj4(info.proj4);
1461
- repoobj.dataProjWGStoInternalWGS = (xy) =>
1462
- to3857.inverse(toDataProj.forward(xy));
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
- const type = source.type;
1466
- Object.assign(source, info);
1467
- source.type = type;
1468
- source.tiles = [
1469
- // meta url which will be detected when requested
1470
- `mbtiles://${name}/{z}/{x}/{y}.${info.format || 'pbf'}`,
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
- if (
1479
- !attributionOverride &&
1480
- source.attribution &&
1481
- source.attribution.length > 0
1482
- ) {
1483
- if (!tileJSON.attribution.includes(source.attribution)) {
1484
- if (tileJSON.attribution.length > 0) {
1485
- tileJSON.attribution += ' | ';
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
- resolve();
1594
+ resolve();
1595
+ });
1491
1596
  });
1492
- });
1493
- }),
1494
- );
1597
+ }),
1598
+ );
1599
+ }
1495
1600
  }
1496
1601
  }
1497
1602
 
@@ -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
- const url = source.url;
114
- if (url && url.lastIndexOf('mbtiles:', 0) === 0) {
115
- let mbtilesFile = url.substring('mbtiles://'.length);
116
- const fromData =
117
- mbtilesFile[0] === '{' && mbtilesFile[mbtilesFile.length - 1] === '}';
118
-
119
- if (fromData) {
120
- mbtilesFile = mbtilesFile.substr(1, mbtilesFile.length - 2);
121
- const mapsTo = (params.mapping || {})[mbtilesFile];
122
- if (mapsTo) {
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
- const identifier = reportTiles(mbtilesFile, fromData);
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
  }