Skip to content

Commit

Permalink
Allow handling of local GeoJSON files #1324 (#1326)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
3 people authored Sep 1, 2024
1 parent e0be79b commit 44cf365
Show file tree
Hide file tree
Showing 11 changed files with 68 additions and 16 deletions.
1 change: 1 addition & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@
!package.json
!package-lock.json
!docker-entrypoint.sh
**.gitignore
2 changes: 1 addition & 1 deletion .github/workflows/ct.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ jobs:
https://github.com/maptiler/tileserver-gl/releases/download/v1.3.0/test_data.zip
- name: Prepare test data 📦
run: unzip -q test_data.zip -d test_data
run: unzip -q test_data.zip

- name: Run tests 🧪
run: xvfb-run --server-args="-screen 0 1024x768x24" npm test
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
docs/_build
node_modules
test_data
test_data.zip
data
light
plugins
config.json
*.mbtiles
styles
fonts
3 changes: 2 additions & 1 deletion docs/config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ Example:
"icons": "icons",
"styles": "styles",
"mbtiles": "data",
"pmtiles": "data"
"pmtiles": "data",
"files": "public/files"
},
"domains": [
"localhost:8080",
Expand Down
12 changes: 12 additions & 0 deletions docs/endpoints.rst
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,18 @@ Source data

* 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
===============
Array of all TileJSONs is at ``[/{tileSize}]/index.json`` (``[/{tileSize}]/rendered.json``; ``/data.json``)
Expand Down
Empty file added public/files/.gitignore
Empty file.
3 changes: 3 additions & 0 deletions src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ const startWithInputFile = async (inputFile) => {
'../node_modules/tileserver-gl-styles/',
);

const filesDir = path.resolve(__dirname, '../public/files');

const config = {
options: {
paths: {
Expand All @@ -117,6 +119,7 @@ const startWithInputFile = async (inputFile) => {
styles: 'styles',
mbtiles: inputFilePath,
pmtiles: inputFilePath,
files: filesDir,
},
},
styles: {},
Expand Down
27 changes: 24 additions & 3 deletions src/serve_rendered.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ import {
} from './pmtiles_adapter.js';
import { renderOverlay, renderWatermark, renderAttribution } from './render.js';
import fsp from 'node:fs/promises';
import { gunzipP } from './promises.js';
import { existsP, gunzipP } from './promises.js';
import { openMbTilesWrapper } from './mbtiles_wrapper.js';

const FLOAT_PATTERN = '[+-]?(?:\\d+|\\d+.?\\d+)';
Expand Down Expand Up @@ -893,13 +893,15 @@ export const serve_rendered = {
// console.log('Handling request:', req);
if (protocol === 'sprites') {
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) => {
callback(err, { data: data });
});
} else if (protocol === 'fonts') {
const parts = req.url.split('/');
const fontstack = unescape(parts[2]);
const fontstack = decodeURIComponent(parts[2]);
const range = parts[3].split('.')[0];

try {
Expand Down Expand Up @@ -1039,6 +1041,25 @@ export const serve_rendered = {
const format = extensionToFormat[extension] || '';
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}"`,
);
}
}
},
});
Expand Down
15 changes: 14 additions & 1 deletion src/serve_style.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ export const serve_style = {
for (const name of Object.keys(styleJSON_.sources)) {
const source = styleJSON_.sources[name];
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
if (styleJSON_.sprite) {
Expand Down Expand Up @@ -89,7 +92,7 @@ export const serve_style = {
try {
styleFileData = fs.readFileSync(styleFile); // TODO: could be made async if this function was
} catch (e) {
console.log('Error reading style file');
console.log(`Error reading style file "${params.style}"`);
return false;
}

Expand Down Expand Up @@ -128,6 +131,16 @@ export const serve_style = {
}
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) {
Expand Down
17 changes: 8 additions & 9 deletions src/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,24 +94,22 @@ function start(opts) {
paths.sprites = path.resolve(paths.root, paths.sprites || '');
paths.mbtiles = path.resolve(paths.root, paths.mbtiles || '');
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 checkPath = (type) => {
for (const type of Object.keys(paths)) {
if (!fs.existsSync(paths[type])) {
console.error(
`The specified path for "${type}" does not exist (${paths[type]}).`,
);
process.exit(1);
}
};
checkPath('styles');
checkPath('fonts');
checkPath('sprites');
checkPath('mbtiles');
checkPath('pmtiles');
checkPath('icons');
}

/**
* Recursively get all files within a directory.
Expand Down Expand Up @@ -161,6 +159,7 @@ function start(opts) {
}

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));
if (!isLight) {
startupPromises.push(
Expand Down
1 change: 0 additions & 1 deletion test/setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ global.supertest = supertest;

before(function () {
console.log('global setup');
process.chdir('test_data');
const running = server({
configPath: 'config.json',
port: 8888,
Expand Down

0 comments on commit 44cf365

Please sign in to comment.