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/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) return res.status(204).send();
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
- tileJSON['tilejson'] = '2.0.0';
404
- delete tileJSON['filesize'];
405
- delete tileJSON['mtime'];
406
- delete tileJSON['scheme'];
392
+ delete tileJSON['filesize'];
393
+ delete tileJSON['mtime'];
394
+ delete tileJSON['scheme'];
395
+ tileJSON['tilejson'] = '3.0.0';
407
396
 
408
- Object.assign(tileJSON, params.tilejson || {});
409
- fixTileJSONCenter(tileJSON);
397
+ Object.assign(tileJSON, params.tilejson || {});
398
+ fixTileJSONCenter(tileJSON);
410
399
 
411
- if (options.dataDecoratorFunc) {
412
- tileJSON = options.dataDecoratorFunc(id, 'tilejson', tileJSON);
413
- }
400
+ if (options.dataDecoratorFunc) {
401
+ tileJSON = options.dataDecoratorFunc(id, 'tilejson', tileJSON);
414
402
  }
415
403
 
416
404
  repo[id] = {
@@ -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 fileType;
272
- let inputFile;
271
+ let resolvedFileType;
272
+ let resolvedInputFile;
273
+ let resolvedSparse = false;
274
+
273
275
  for (const id of Object.keys(data)) {
274
- fileType = Object.keys(data[id])[0];
275
- if (styleSourceId == id) {
276
- inputFile = data[id][fileType];
277
- break;
278
- } else if (data[id][fileType] == styleSourceId) {
279
- inputFile = data[id][fileType];
280
- break;
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
- if (!isValidHttpUrl(inputFile)) {
284
- inputFile = path.resolve(options.paths[fileType], inputFile);
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 { inputFile, fileType };
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
- return publicUrl;
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
- `${req.protocol}://${domain}${xForwardedPath}/${path}/${tileParams}${format}${query}`,
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.be.equal('/test/styles/test-style/sprite');
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);