* handle local geojson files in styles and rendered tiles - use 'file://' as indicator for local files - add directory as default directory - serve local files at - add documentation for static file serving - add some minor fixes (icon directory, directory checking, decodeURIComponent, extend error message) * Update .gitignore --------- Co-authored-by: Miko <miko@home-laptop.fritz.box> Co-authored-by: Andrew Calcutt <acalcutt@techidiots.net>
This commit is contained in:
parent
e0be79b09d
commit
44cf365d65
11 changed files with 68 additions and 16 deletions
|
@ -5,3 +5,4 @@
|
||||||
!package.json
|
!package.json
|
||||||
!package-lock.json
|
!package-lock.json
|
||||||
!docker-entrypoint.sh
|
!docker-entrypoint.sh
|
||||||
|
**.gitignore
|
2
.github/workflows/ct.yml
vendored
2
.github/workflows/ct.yml
vendored
|
@ -46,7 +46,7 @@ jobs:
|
||||||
https://github.com/maptiler/tileserver-gl/releases/download/v1.3.0/test_data.zip
|
https://github.com/maptiler/tileserver-gl/releases/download/v1.3.0/test_data.zip
|
||||||
|
|
||||||
- name: Prepare test data 📦
|
- name: Prepare test data 📦
|
||||||
run: unzip -q test_data.zip -d test_data
|
run: unzip -q test_data.zip
|
||||||
|
|
||||||
- name: Run tests 🧪
|
- name: Run tests 🧪
|
||||||
run: xvfb-run --server-args="-screen 0 1024x768x24" npm test
|
run: xvfb-run --server-args="-screen 0 1024x768x24" npm test
|
||||||
|
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -1,8 +1,11 @@
|
||||||
docs/_build
|
docs/_build
|
||||||
node_modules
|
node_modules
|
||||||
test_data
|
test_data
|
||||||
|
test_data.zip
|
||||||
data
|
data
|
||||||
light
|
light
|
||||||
plugins
|
plugins
|
||||||
config.json
|
config.json
|
||||||
*.mbtiles
|
*.mbtiles
|
||||||
|
styles
|
||||||
|
fonts
|
||||||
|
|
|
@ -17,7 +17,8 @@ Example:
|
||||||
"icons": "icons",
|
"icons": "icons",
|
||||||
"styles": "styles",
|
"styles": "styles",
|
||||||
"mbtiles": "data",
|
"mbtiles": "data",
|
||||||
"pmtiles": "data"
|
"pmtiles": "data",
|
||||||
|
"files": "public/files"
|
||||||
},
|
},
|
||||||
"domains": [
|
"domains": [
|
||||||
"localhost:8080",
|
"localhost:8080",
|
||||||
|
|
|
@ -100,6 +100,18 @@ Source data
|
||||||
|
|
||||||
* TileJSON at ``/data/{id}.json``
|
* TileJSON at ``/data/{id}.json``
|
||||||
|
|
||||||
|
Static files
|
||||||
|
===========
|
||||||
|
* Static files are served at ``/files/{filename}``
|
||||||
|
|
||||||
|
* The source folder can be configured (``options.paths.files``), default is ``public/files``
|
||||||
|
|
||||||
|
* This feature can be used to serve ``geojson`` files for styles and rendered tiles.
|
||||||
|
|
||||||
|
* Keep in mind, that each rendered tile loads the whole geojson file, if performance matters a conversion to a tiled format (e.g. with https://github.com/felt/tippecanoe)may be a better approch.
|
||||||
|
|
||||||
|
* Use ``file://{filename}`` to have matching paths for both endoints
|
||||||
|
|
||||||
TileJSON arrays
|
TileJSON arrays
|
||||||
===============
|
===============
|
||||||
Array of all TileJSONs is at ``[/{tileSize}]/index.json`` (``[/{tileSize}]/rendered.json``; ``/data.json``)
|
Array of all TileJSONs is at ``[/{tileSize}]/index.json`` (``[/{tileSize}]/rendered.json``; ``/data.json``)
|
||||||
|
|
0
public/files/.gitignore
vendored
Normal file
0
public/files/.gitignore
vendored
Normal file
|
@ -109,6 +109,8 @@ const startWithInputFile = async (inputFile) => {
|
||||||
'../node_modules/tileserver-gl-styles/',
|
'../node_modules/tileserver-gl-styles/',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const filesDir = path.resolve(__dirname, '../public/files');
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
options: {
|
options: {
|
||||||
paths: {
|
paths: {
|
||||||
|
@ -117,6 +119,7 @@ const startWithInputFile = async (inputFile) => {
|
||||||
styles: 'styles',
|
styles: 'styles',
|
||||||
mbtiles: inputFilePath,
|
mbtiles: inputFilePath,
|
||||||
pmtiles: inputFilePath,
|
pmtiles: inputFilePath,
|
||||||
|
files: filesDir,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
styles: {},
|
styles: {},
|
||||||
|
|
|
@ -41,7 +41,7 @@ import {
|
||||||
} from './pmtiles_adapter.js';
|
} from './pmtiles_adapter.js';
|
||||||
import { renderOverlay, renderWatermark, renderAttribution } from './render.js';
|
import { renderOverlay, renderWatermark, renderAttribution } from './render.js';
|
||||||
import fsp from 'node:fs/promises';
|
import fsp from 'node:fs/promises';
|
||||||
import { gunzipP } from './promises.js';
|
import { existsP, gunzipP } from './promises.js';
|
||||||
import { openMbTilesWrapper } from './mbtiles_wrapper.js';
|
import { openMbTilesWrapper } from './mbtiles_wrapper.js';
|
||||||
|
|
||||||
const FLOAT_PATTERN = '[+-]?(?:\\d+|\\d+.?\\d+)';
|
const FLOAT_PATTERN = '[+-]?(?:\\d+|\\d+.?\\d+)';
|
||||||
|
@ -893,13 +893,15 @@ export const serve_rendered = {
|
||||||
// console.log('Handling request:', req);
|
// console.log('Handling request:', req);
|
||||||
if (protocol === 'sprites') {
|
if (protocol === 'sprites') {
|
||||||
const dir = options.paths[protocol];
|
const dir = options.paths[protocol];
|
||||||
const file = unescape(req.url).substring(protocol.length + 3);
|
const file = decodeURIComponent(req.url).substring(
|
||||||
|
protocol.length + 3,
|
||||||
|
);
|
||||||
fs.readFile(path.join(dir, file), (err, data) => {
|
fs.readFile(path.join(dir, file), (err, data) => {
|
||||||
callback(err, { data: data });
|
callback(err, { data: data });
|
||||||
});
|
});
|
||||||
} else if (protocol === 'fonts') {
|
} else if (protocol === 'fonts') {
|
||||||
const parts = req.url.split('/');
|
const parts = req.url.split('/');
|
||||||
const fontstack = unescape(parts[2]);
|
const fontstack = decodeURIComponent(parts[2]);
|
||||||
const range = parts[3].split('.')[0];
|
const range = parts[3].split('.')[0];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -1039,6 +1041,25 @@ export const serve_rendered = {
|
||||||
const format = extensionToFormat[extension] || '';
|
const format = extensionToFormat[extension] || '';
|
||||||
createEmptyResponse(format, '', callback);
|
createEmptyResponse(format, '', callback);
|
||||||
}
|
}
|
||||||
|
} else if (protocol === 'file') {
|
||||||
|
const name = decodeURI(req.url).substring(protocol.length + 3);
|
||||||
|
const file = path.join(options.paths['files'], name);
|
||||||
|
if (await existsP(file)) {
|
||||||
|
const inputFileStats = await fsp.stat(file);
|
||||||
|
if (!inputFileStats.isFile() || inputFileStats.size === 0) {
|
||||||
|
throw Error(
|
||||||
|
`File is not valid: "${req.url}" - resolved to "${file}"`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.readFile(file, (err, data) => {
|
||||||
|
callback(err, { data: data });
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
throw Error(
|
||||||
|
`File does not exist: "${req.url}" - resolved to "${file}"`,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -26,6 +26,9 @@ export const serve_style = {
|
||||||
for (const name of Object.keys(styleJSON_.sources)) {
|
for (const name of Object.keys(styleJSON_.sources)) {
|
||||||
const source = styleJSON_.sources[name];
|
const source = styleJSON_.sources[name];
|
||||||
source.url = fixUrl(req, source.url, item.publicUrl);
|
source.url = fixUrl(req, source.url, item.publicUrl);
|
||||||
|
if (typeof source.data == 'string') {
|
||||||
|
source.data = fixUrl(req, source.data, item.publicUrl);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// mapbox-gl-js viewer cannot handle sprite urls with query
|
// mapbox-gl-js viewer cannot handle sprite urls with query
|
||||||
if (styleJSON_.sprite) {
|
if (styleJSON_.sprite) {
|
||||||
|
@ -89,7 +92,7 @@ export const serve_style = {
|
||||||
try {
|
try {
|
||||||
styleFileData = fs.readFileSync(styleFile); // TODO: could be made async if this function was
|
styleFileData = fs.readFileSync(styleFile); // TODO: could be made async if this function was
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log('Error reading style file');
|
console.log(`Error reading style file "${params.style}"`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,6 +131,16 @@ export const serve_style = {
|
||||||
}
|
}
|
||||||
source.url = `local://data/${identifier}.json`;
|
source.url = `local://data/${identifier}.json`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let data = source.data;
|
||||||
|
if (data && typeof data == 'string' && data.startsWith('file://')) {
|
||||||
|
source.data =
|
||||||
|
'local://files' +
|
||||||
|
path.resolve(
|
||||||
|
'/',
|
||||||
|
data.replace('file://', '').replace(options.paths.files, ''),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const obj of styleJSON.layers) {
|
for (const obj of styleJSON.layers) {
|
||||||
|
|
|
@ -94,24 +94,22 @@ function start(opts) {
|
||||||
paths.sprites = path.resolve(paths.root, paths.sprites || '');
|
paths.sprites = path.resolve(paths.root, paths.sprites || '');
|
||||||
paths.mbtiles = path.resolve(paths.root, paths.mbtiles || '');
|
paths.mbtiles = path.resolve(paths.root, paths.mbtiles || '');
|
||||||
paths.pmtiles = path.resolve(paths.root, paths.pmtiles || '');
|
paths.pmtiles = path.resolve(paths.root, paths.pmtiles || '');
|
||||||
paths.icons = path.resolve(paths.root, paths.icons || '');
|
paths.icons = path.resolve(
|
||||||
|
paths.root,
|
||||||
|
paths.icons || 'public/resources/images',
|
||||||
|
);
|
||||||
|
paths.files = path.resolve(paths.root, paths.files || 'public/files');
|
||||||
|
|
||||||
const startupPromises = [];
|
const startupPromises = [];
|
||||||
|
|
||||||
const checkPath = (type) => {
|
for (const type of Object.keys(paths)) {
|
||||||
if (!fs.existsSync(paths[type])) {
|
if (!fs.existsSync(paths[type])) {
|
||||||
console.error(
|
console.error(
|
||||||
`The specified path for "${type}" does not exist (${paths[type]}).`,
|
`The specified path for "${type}" does not exist (${paths[type]}).`,
|
||||||
);
|
);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
checkPath('styles');
|
|
||||||
checkPath('fonts');
|
|
||||||
checkPath('sprites');
|
|
||||||
checkPath('mbtiles');
|
|
||||||
checkPath('pmtiles');
|
|
||||||
checkPath('icons');
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Recursively get all files within a directory.
|
* Recursively get all files within a directory.
|
||||||
|
@ -161,6 +159,7 @@ function start(opts) {
|
||||||
}
|
}
|
||||||
|
|
||||||
app.use('/data/', serve_data.init(options, serving.data));
|
app.use('/data/', serve_data.init(options, serving.data));
|
||||||
|
app.use('/files/', express.static(paths.files));
|
||||||
app.use('/styles/', serve_style.init(options, serving.styles));
|
app.use('/styles/', serve_style.init(options, serving.styles));
|
||||||
if (!isLight) {
|
if (!isLight) {
|
||||||
startupPromises.push(
|
startupPromises.push(
|
||||||
|
|
|
@ -9,7 +9,6 @@ global.supertest = supertest;
|
||||||
|
|
||||||
before(function () {
|
before(function () {
|
||||||
console.log('global setup');
|
console.log('global setup');
|
||||||
process.chdir('test_data');
|
|
||||||
const running = server({
|
const running = server({
|
||||||
configPath: 'config.json',
|
configPath: 'config.json',
|
||||||
port: 8888,
|
port: 8888,
|
||||||
|
|
Loading…
Reference in a new issue