tileserver-gl/src/serve_style.js
Andrew Calcutt 92b528498d fix: allow old sprite endpoints to work
Signed-off-by: Andrew Calcutt <acalcutt@techidiots.net>
2024-04-22 20:17:14 -04:00

207 lines
6.2 KiB
JavaScript

'use strict';
import path from 'path';
import fs from 'node:fs';
import clone from 'clone';
import express from 'express';
import { validateStyleMin } from '@maplibre/maplibre-gl-style-spec';
import { getPublicUrl } from './utils.js';
const httpTester = /^(http(s)?:)?\/\//;
const fixUrl = (req, url, publicUrl) => {
if (!url || typeof url !== 'string' || url.indexOf('local://') !== 0) {
return url;
}
const queryParams = [];
if (req.query.key) {
queryParams.unshift(`key=${encodeURIComponent(req.query.key)}`);
}
let query = '';
if (queryParams.length) {
query = `?${queryParams.join('&')}`;
}
return url.replace('local://', getPublicUrl(publicUrl, req)) + query;
};
export const serve_style = {
init: (options, repo) => {
const app = express().disable('x-powered-by');
app.get('/:id/style.json', (req, res, next) => {
const item = repo[req.params.id];
if (!item) {
return res.sendStatus(404);
}
const styleJSON_ = clone(item.styleJSON);
for (const name of Object.keys(styleJSON_.sources)) {
const source = styleJSON_.sources[name];
source.url = fixUrl(req, source.url, item.publicUrl);
}
// mapbox-gl-js viewer cannot handle sprite urls with query
if (styleJSON_.sprite) {
if (Array.isArray(styleJSON_.sprite)) {
styleJSON_.sprite.forEach((spriteItem) => {
spriteItem.url = fixUrl(req, spriteItem.url, item.publicUrl);
});
} else {
styleJSON_.sprite = fixUrl(req, styleJSON_.sprite, item.publicUrl);
}
}
if (styleJSON_.glyphs) {
styleJSON_.glyphs = fixUrl(req, styleJSON_.glyphs, item.publicUrl);
}
return res.send(styleJSON_);
});
app.get('/:id/sprite(/:name)?:scale(@[23]x)?.:format([\\w]+)', (req, res, next) => {
const item = repo[req.params.id];
const spriteName = req.params.name || 'sprite';
if (!item || !item.spritePaths) {
return res.sendStatus(404);
}
let spritePath
for (const sprite of item.spritePaths) {
if (sprite.name === spriteName) {
spritePath = sprite.path;
}
}
if (!spritePath) {
return res.sendStatus(404);
}
const scale = req.params.scale;
const format = req.params.format;
const filename = `${spritePath + (scale || '')}.${format}`;
return fs.readFile(filename, (err, data) => {
if (err) {
console.log('Sprite load error:', filename);
return res.sendStatus(404);
} else {
if (format === 'json') res.header('Content-type', 'application/json');
if (format === 'png') res.header('Content-type', 'image/png');
return res.send(data);
}
});
});
return app;
},
remove: (repo, id) => {
delete repo[id];
},
add: (options, repo, params, id, publicUrl, reportTiles, reportFont) => {
const styleFile = path.resolve(options.paths.styles, params.style);
let styleFileData;
try {
styleFileData = fs.readFileSync(styleFile);
} catch (e) {
console.log('Error reading style file');
return false;
}
const styleJSON = JSON.parse(styleFileData);
const validationErrors = validateStyleMin(styleJSON);
if (validationErrors.length > 0) {
console.log(`The file "${params.style}" is not a valid style file:`);
for (const err of validationErrors) {
console.log(`${err.line}: ${err.message}`);
}
return false;
}
for (const name of Object.keys(styleJSON.sources)) {
const source = styleJSON.sources[name];
let url = source.url;
if (
url &&
(url.startsWith('pmtiles://') || url.startsWith('mbtiles://'))
) {
const protocol = url.split(':')[0];
let dataId = url.replace('pmtiles://', '').replace('mbtiles://', '');
if (dataId.startsWith('{') && dataId.endsWith('}')) {
dataId = dataId.slice(1, -1);
}
const mapsTo = (params.mapping || {})[dataId];
if (mapsTo) {
dataId = mapsTo;
}
const identifier = reportTiles(dataId, protocol);
if (!identifier) {
return false;
}
source.url = `local://data/${identifier}.json`;
}
}
for (const obj of styleJSON.layers) {
if (obj['type'] === 'symbol') {
const fonts = (obj['layout'] || {})['text-font'];
if (fonts && fonts.length) {
fonts.forEach(reportFont);
} else {
reportFont('Open Sans Regular');
reportFont('Arial Unicode MS Regular');
}
}
}
let spritePaths = [];
if (styleJSON.sprite && styleJSON.sprite) {
if (Array.isArray(styleJSON.sprite)) {
styleJSON.sprite.forEach((spriteItem) => {
if (!httpTester.test(spriteItem.url)) {
let spriteName = spriteItem.url.substring(spriteItem.url.lastIndexOf('/') + 1);
let spritePath = path.join(
options.paths.sprites,
spriteItem.url
.replace('{style}', path.basename(styleFile, '.json'))
.replace(
'{styleJsonFolder}',
path.relative(options.paths.sprites, path.dirname(styleFile))
)
);
spriteItem.url = `local://styles/${id}/sprite/` + spriteName;
spritePaths.push({id: spriteItem.id, name: spriteName, path: spritePath});
}
});
} else {
if (!httpTester.test(styleJSON.sprite)) {
let spritePath = path.join(
options.paths.sprites,
styleJSON.sprite
.replace('{style}', path.basename(styleFile, '.json'))
.replace(
'{styleJsonFolder}',
path.relative(options.paths.sprites, path.dirname(styleFile))
)
);
styleJSON.sprite = `local://styles/${id}/sprite`;
spritePaths.push({id: 'default', name: 'sprite', path: spritePath});
}
}
}
if (styleJSON.glyphs && !httpTester.test(styleJSON.glyphs)) {
styleJSON.glyphs = 'local://fonts/{fontstack}/{range}.pbf';
}
repo[id] = {
styleJSON,
spritePaths,
publicUrl,
name: styleJSON.name,
};
return true;
},
};