tileserver-gl-light 5.3.1 → 5.4.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/CHANGELOG.md +8 -3
- package/docs/config.rst +30 -7
- package/package.json +9 -9
- package/public/resources/maplibre-gl.js +4 -4
- package/public/resources/maplibre-gl.js.map +1 -1
- package/src/serve_data.js +20 -32
- package/src/serve_rendered.js +67 -1
- package/src/server.js +69 -12
- package/src/utils.js +9 -2
- package/test/style.js +1 -1
package/src/serve_data.js
CHANGED
|
@@ -108,7 +108,11 @@ export const serve_data = {
|
|
|
108
108
|
x,
|
|
109
109
|
y,
|
|
110
110
|
);
|
|
111
|
-
if (fetchTile == null
|
|
111
|
+
if (fetchTile == null && item.tileJSON.sparse) {
|
|
112
|
+
return res.status(410).send();
|
|
113
|
+
} else if (fetchTile == null) {
|
|
114
|
+
return res.status(204).send();
|
|
115
|
+
}
|
|
112
116
|
|
|
113
117
|
let data = fetchTile.data;
|
|
114
118
|
let headers = fetchTile.headers;
|
|
@@ -366,51 +370,35 @@ export const serve_data = {
|
|
|
366
370
|
|
|
367
371
|
let source;
|
|
368
372
|
let sourceType;
|
|
373
|
+
tileJSON['name'] = id;
|
|
374
|
+
tileJSON['format'] = 'pbf';
|
|
375
|
+
tileJSON['encoding'] = params['encoding'];
|
|
376
|
+
tileJSON['tileSize'] = params['tileSize'];
|
|
377
|
+
tileJSON['sparse'] = params['sparse'];
|
|
378
|
+
|
|
369
379
|
if (inputType === 'pmtiles') {
|
|
370
380
|
source = openPMtiles(inputFile);
|
|
371
381
|
sourceType = 'pmtiles';
|
|
372
382
|
const metadata = await getPMtilesInfo(source);
|
|
373
|
-
|
|
374
|
-
tileJSON['encoding'] = params['encoding'];
|
|
375
|
-
tileJSON['tileSize'] = params['tileSize'];
|
|
376
|
-
tileJSON['name'] = id;
|
|
377
|
-
tileJSON['format'] = 'pbf';
|
|
378
383
|
Object.assign(tileJSON, metadata);
|
|
379
|
-
|
|
380
|
-
tileJSON['tilejson'] = '2.0.0';
|
|
381
|
-
delete tileJSON['filesize'];
|
|
382
|
-
delete tileJSON['mtime'];
|
|
383
|
-
delete tileJSON['scheme'];
|
|
384
|
-
|
|
385
|
-
Object.assign(tileJSON, params.tilejson || {});
|
|
386
|
-
fixTileJSONCenter(tileJSON);
|
|
387
|
-
|
|
388
|
-
if (options.dataDecoratorFunc) {
|
|
389
|
-
tileJSON = options.dataDecoratorFunc(id, 'tilejson', tileJSON);
|
|
390
|
-
}
|
|
391
384
|
} else if (inputType === 'mbtiles') {
|
|
392
385
|
sourceType = 'mbtiles';
|
|
393
386
|
const mbw = await openMbTilesWrapper(inputFile);
|
|
394
387
|
const info = await mbw.getInfo();
|
|
395
388
|
source = mbw.getMbTiles();
|
|
396
|
-
tileJSON['encoding'] = params['encoding'];
|
|
397
|
-
tileJSON['tileSize'] = params['tileSize'];
|
|
398
|
-
tileJSON['name'] = id;
|
|
399
|
-
tileJSON['format'] = 'pbf';
|
|
400
|
-
|
|
401
389
|
Object.assign(tileJSON, info);
|
|
390
|
+
}
|
|
402
391
|
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
392
|
+
delete tileJSON['filesize'];
|
|
393
|
+
delete tileJSON['mtime'];
|
|
394
|
+
delete tileJSON['scheme'];
|
|
395
|
+
tileJSON['tilejson'] = '3.0.0';
|
|
407
396
|
|
|
408
|
-
|
|
409
|
-
|
|
397
|
+
Object.assign(tileJSON, params.tilejson || {});
|
|
398
|
+
fixTileJSONCenter(tileJSON);
|
|
410
399
|
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
}
|
|
400
|
+
if (options.dataDecoratorFunc) {
|
|
401
|
+
tileJSON = options.dataDecoratorFunc(id, 'tilejson', tileJSON);
|
|
414
402
|
}
|
|
415
403
|
|
|
416
404
|
repo[id] = {
|
package/src/serve_rendered.js
CHANGED
|
@@ -1005,6 +1005,7 @@ export const serve_rendered = {
|
|
|
1005
1005
|
);
|
|
1006
1006
|
}
|
|
1007
1007
|
const info = clone(item.tileJSON);
|
|
1008
|
+
info.tileSize = tileSize != undefined ? tileSize : 256;
|
|
1008
1009
|
info.tiles = getTileUrls(
|
|
1009
1010
|
req,
|
|
1010
1011
|
info.tiles,
|
|
@@ -1122,7 +1123,16 @@ export const serve_rendered = {
|
|
|
1122
1123
|
x,
|
|
1123
1124
|
y,
|
|
1124
1125
|
);
|
|
1125
|
-
if (fetchTile == null) {
|
|
1126
|
+
if (fetchTile == null && sourceInfo.sparse == true) {
|
|
1127
|
+
if (verbose) {
|
|
1128
|
+
console.log(
|
|
1129
|
+
'fetchTile warning on %s, sparse response',
|
|
1130
|
+
req.url,
|
|
1131
|
+
);
|
|
1132
|
+
}
|
|
1133
|
+
callback();
|
|
1134
|
+
return;
|
|
1135
|
+
} else if (fetchTile == null) {
|
|
1126
1136
|
if (verbose) {
|
|
1127
1137
|
console.log(
|
|
1128
1138
|
'fetchTile error on %s, serving empty response',
|
|
@@ -1266,13 +1276,65 @@ export const serve_rendered = {
|
|
|
1266
1276
|
|
|
1267
1277
|
for (const layer of styleJSON.layers || []) {
|
|
1268
1278
|
if (layer && layer.paint) {
|
|
1279
|
+
const layerIdForWarning = layer.id || 'unnamed-layer';
|
|
1280
|
+
|
|
1269
1281
|
// Remove (flatten) 3D buildings
|
|
1270
1282
|
if (layer.paint['fill-extrusion-height']) {
|
|
1283
|
+
if (verbose) {
|
|
1284
|
+
console.warn(
|
|
1285
|
+
`Warning: Layer '${layerIdForWarning}' in style '${id}' has property 'fill-extrusion-height'. ` +
|
|
1286
|
+
`3D extrusion may appear distorted or misleading when rendered as a static image due to camera angle limitations. ` +
|
|
1287
|
+
`It will be flattened (set to 0) in rendered images. ` +
|
|
1288
|
+
`Note: This property will still work with MapLibre GL JS vector maps.`,
|
|
1289
|
+
);
|
|
1290
|
+
}
|
|
1271
1291
|
layer.paint['fill-extrusion-height'] = 0;
|
|
1272
1292
|
}
|
|
1273
1293
|
if (layer.paint['fill-extrusion-base']) {
|
|
1294
|
+
if (verbose) {
|
|
1295
|
+
console.warn(
|
|
1296
|
+
`Warning: Layer '${layerIdForWarning}' in style '${id}' has property 'fill-extrusion-base'. ` +
|
|
1297
|
+
`3D extrusion may appear distorted or misleading when rendered as a static image due to camera angle limitations. ` +
|
|
1298
|
+
`It will be flattened (set to 0) in rendered images. ` +
|
|
1299
|
+
`Note: This property will still work with MapLibre GL JS vector maps.`,
|
|
1300
|
+
);
|
|
1301
|
+
}
|
|
1274
1302
|
layer.paint['fill-extrusion-base'] = 0;
|
|
1275
1303
|
}
|
|
1304
|
+
|
|
1305
|
+
// --- Remove hillshade properties incompatible with MapLibre Native ---
|
|
1306
|
+
const hillshadePropertiesToRemove = [
|
|
1307
|
+
'hillshade-method',
|
|
1308
|
+
'hillshade-illumination-direction',
|
|
1309
|
+
'hillshade-highlight-color',
|
|
1310
|
+
];
|
|
1311
|
+
|
|
1312
|
+
for (const prop of hillshadePropertiesToRemove) {
|
|
1313
|
+
if (prop in layer.paint) {
|
|
1314
|
+
if (verbose) {
|
|
1315
|
+
console.warn(
|
|
1316
|
+
`Warning: Layer '${layerIdForWarning}' in style '${id}' has property '${prop}'. ` +
|
|
1317
|
+
`This property is not supported by MapLibre Native. ` +
|
|
1318
|
+
`It will be removed in rendered images. ` +
|
|
1319
|
+
`Note: This property will still work with MapLibre GL JS vector maps.`,
|
|
1320
|
+
);
|
|
1321
|
+
}
|
|
1322
|
+
delete layer.paint[prop];
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
// --- Remove 'hillshade-shadow-color' if it is an array. It can only be a string in MapLibre Native ---
|
|
1327
|
+
if (Array.isArray(layer.paint['hillshade-shadow-color'])) {
|
|
1328
|
+
if (verbose) {
|
|
1329
|
+
console.warn(
|
|
1330
|
+
`Warning: Layer '${layerIdForWarning}' in style '${id}' has property 'hillshade-shadow-color'. ` +
|
|
1331
|
+
`An array value is not supported by MapLibre Native for this property (expected string/color). ` +
|
|
1332
|
+
`It will be removed in rendered images. ` +
|
|
1333
|
+
`Note: Using an array for this property will still work with MapLibre GL JS vector maps.`,
|
|
1334
|
+
);
|
|
1335
|
+
}
|
|
1336
|
+
delete layer.paint['hillshade-shadow-color'];
|
|
1337
|
+
}
|
|
1276
1338
|
}
|
|
1277
1339
|
}
|
|
1278
1340
|
|
|
@@ -1308,6 +1370,7 @@ export const serve_rendered = {
|
|
|
1308
1370
|
|
|
1309
1371
|
for (const name of Object.keys(styleJSON.sources)) {
|
|
1310
1372
|
let sourceType;
|
|
1373
|
+
let sparse;
|
|
1311
1374
|
let source = styleJSON.sources[name];
|
|
1312
1375
|
let url = source.url;
|
|
1313
1376
|
if (
|
|
@@ -1332,6 +1395,7 @@ export const serve_rendered = {
|
|
|
1332
1395
|
if (dataInfo.inputFile) {
|
|
1333
1396
|
inputFile = dataInfo.inputFile;
|
|
1334
1397
|
sourceType = dataInfo.fileType;
|
|
1398
|
+
sparse = dataInfo.sparse;
|
|
1335
1399
|
} else {
|
|
1336
1400
|
console.error(`ERROR: data "${inputFile}" not found!`);
|
|
1337
1401
|
process.exit(1);
|
|
@@ -1360,6 +1424,7 @@ export const serve_rendered = {
|
|
|
1360
1424
|
const type = source.type;
|
|
1361
1425
|
Object.assign(source, metadata);
|
|
1362
1426
|
source.type = type;
|
|
1427
|
+
source.sparse = sparse;
|
|
1363
1428
|
source.tiles = [
|
|
1364
1429
|
// meta url which will be detected when requested
|
|
1365
1430
|
`pmtiles://${name}/{z}/{x}/{y}.${metadata.format || 'pbf'}`,
|
|
@@ -1399,6 +1464,7 @@ export const serve_rendered = {
|
|
|
1399
1464
|
const type = source.type;
|
|
1400
1465
|
Object.assign(source, info);
|
|
1401
1466
|
source.type = type;
|
|
1467
|
+
source.sparse = sparse;
|
|
1402
1468
|
source.tiles = [
|
|
1403
1469
|
// meta url which will be detected when requested
|
|
1404
1470
|
`mbtiles://${name}/{z}/{x}/{y}.${info.format || 'pbf'}`,
|
package/src/server.js
CHANGED
|
@@ -268,23 +268,80 @@ async function start(opts) {
|
|
|
268
268
|
opts,
|
|
269
269
|
styleJSON,
|
|
270
270
|
function dataResolver(styleSourceId) {
|
|
271
|
-
let
|
|
272
|
-
let
|
|
271
|
+
let resolvedFileType;
|
|
272
|
+
let resolvedInputFile;
|
|
273
|
+
let resolvedSparse = false;
|
|
274
|
+
|
|
273
275
|
for (const id of Object.keys(data)) {
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
276
|
+
const sourceData = data[id];
|
|
277
|
+
let currentFileType;
|
|
278
|
+
let currentInputFileValue;
|
|
279
|
+
|
|
280
|
+
// Check for recognized file type keys
|
|
281
|
+
if (sourceData.hasOwnProperty('pmtiles')) {
|
|
282
|
+
currentFileType = 'pmtiles';
|
|
283
|
+
currentInputFileValue = sourceData.pmtiles;
|
|
284
|
+
} else if (sourceData.hasOwnProperty('mbtiles')) {
|
|
285
|
+
currentFileType = 'mbtiles';
|
|
286
|
+
currentInputFileValue = sourceData.mbtiles;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (currentFileType && currentInputFileValue) {
|
|
290
|
+
// Check if this source matches the styleSourceId
|
|
291
|
+
if (
|
|
292
|
+
styleSourceId === id ||
|
|
293
|
+
styleSourceId === currentInputFileValue
|
|
294
|
+
) {
|
|
295
|
+
resolvedFileType = currentFileType;
|
|
296
|
+
resolvedInputFile = currentInputFileValue;
|
|
297
|
+
|
|
298
|
+
// Get sparse flag specifically from this matching source
|
|
299
|
+
// Default to false if 'sparse' key doesn't exist or is falsy in a boolean context
|
|
300
|
+
if (sourceData.hasOwnProperty('sparse')) {
|
|
301
|
+
resolvedSparse = !!sourceData.sparse; // Ensure boolean
|
|
302
|
+
} else {
|
|
303
|
+
resolvedSparse = false; // Explicitly set default if not present on item
|
|
304
|
+
}
|
|
305
|
+
break; // Found our match, exit the outer loop
|
|
306
|
+
}
|
|
281
307
|
}
|
|
282
308
|
}
|
|
283
|
-
|
|
284
|
-
|
|
309
|
+
|
|
310
|
+
// If no match was found
|
|
311
|
+
if (!resolvedInputFile || !resolvedFileType) {
|
|
312
|
+
console.warn(
|
|
313
|
+
`Data source not found for styleSourceId: ${styleSourceId}`,
|
|
314
|
+
);
|
|
315
|
+
return {
|
|
316
|
+
inputFile: undefined,
|
|
317
|
+
fileType: undefined,
|
|
318
|
+
sparse: false,
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if (!isValidHttpUrl(resolvedInputFile)) {
|
|
323
|
+
// Ensure options.paths and options.paths[resolvedFileType] exist before trying to use them
|
|
324
|
+
if (
|
|
325
|
+
options &&
|
|
326
|
+
options.paths &&
|
|
327
|
+
options.paths[resolvedFileType]
|
|
328
|
+
) {
|
|
329
|
+
resolvedInputFile = path.resolve(
|
|
330
|
+
options.paths[resolvedFileType],
|
|
331
|
+
resolvedInputFile,
|
|
332
|
+
);
|
|
333
|
+
} else {
|
|
334
|
+
console.warn(
|
|
335
|
+
`Path configuration missing for fileType: ${resolvedFileType}. Using relative path for: ${resolvedInputFile}`,
|
|
336
|
+
);
|
|
337
|
+
}
|
|
285
338
|
}
|
|
286
339
|
|
|
287
|
-
return {
|
|
340
|
+
return {
|
|
341
|
+
inputFile: resolvedInputFile,
|
|
342
|
+
fileType: resolvedFileType,
|
|
343
|
+
sparse: resolvedSparse,
|
|
344
|
+
};
|
|
288
345
|
},
|
|
289
346
|
),
|
|
290
347
|
);
|
package/src/utils.js
CHANGED
|
@@ -119,7 +119,11 @@ function getUrlObject(req) {
|
|
|
119
119
|
*/
|
|
120
120
|
export function getPublicUrl(publicUrl, req) {
|
|
121
121
|
if (publicUrl) {
|
|
122
|
-
|
|
122
|
+
try {
|
|
123
|
+
return new URL(publicUrl).toString();
|
|
124
|
+
} catch {
|
|
125
|
+
return new URL(publicUrl, getUrlObject(req)).toString();
|
|
126
|
+
}
|
|
123
127
|
}
|
|
124
128
|
return getUrlObject(req).toString();
|
|
125
129
|
}
|
|
@@ -198,9 +202,12 @@ export function getTileUrls(
|
|
|
198
202
|
const uris = [];
|
|
199
203
|
if (!publicUrl) {
|
|
200
204
|
let xForwardedPath = `${req.get('X-Forwarded-Path') ? '/' + req.get('X-Forwarded-Path') : ''}`;
|
|
205
|
+
let protocol = req.get('X-Forwarded-Protocol')
|
|
206
|
+
? req.get('X-Forwarded-Protocol')
|
|
207
|
+
: req.protocol;
|
|
201
208
|
for (const domain of domains) {
|
|
202
209
|
uris.push(
|
|
203
|
-
`${
|
|
210
|
+
`${protocol}://${domain}${xForwardedPath}/${path}/${tileParams}${format}${query}`,
|
|
204
211
|
);
|
|
205
212
|
}
|
|
206
213
|
} else {
|
package/test/style.js
CHANGED
|
@@ -25,7 +25,7 @@ describe('Styles', function () {
|
|
|
25
25
|
expect(res.body.sources).to.be.a('object');
|
|
26
26
|
expect(res.body.glyphs).to.be.a('string');
|
|
27
27
|
expect(res.body.sprite).to.be.a('string');
|
|
28
|
-
expect(res.body.sprite).to.
|
|
28
|
+
expect(res.body.sprite).to.contain('/test/styles/test-style/sprite');
|
|
29
29
|
expect(res.body.layers).to.be.a('array');
|
|
30
30
|
})
|
|
31
31
|
.end(done);
|