@mapwhit/tileserver 3.5.0
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/LICENSE.md +26 -0
- package/README.md +71 -0
- package/bin/tileserver +3 -0
- package/index.js +28 -0
- package/lib/server.js +84 -0
- package/lib/tiles.js +110 -0
- package/lib/utils.js +37 -0
- package/package.json +36 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
Copyright (c) 2016, Klokan Technologies GmbH
|
|
2
|
+
|
|
3
|
+
All rights reserved.
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are
|
|
7
|
+
met:
|
|
8
|
+
|
|
9
|
+
1. Redistributions of source code must retain the above copyright
|
|
10
|
+
notice, this list of conditions and the following disclaimer.
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright
|
|
12
|
+
notice, this list of conditions and the following disclaimer in
|
|
13
|
+
the documentation and/or other materials provided with the
|
|
14
|
+
distribution.
|
|
15
|
+
|
|
16
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
|
17
|
+
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
|
18
|
+
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|
19
|
+
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
|
20
|
+
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
|
21
|
+
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
22
|
+
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
23
|
+
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|
24
|
+
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
25
|
+
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
26
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
package/README.md
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
[![NPM version][npm-image]][npm-url]
|
|
2
|
+
[![Build Status][build-image]][build-url]
|
|
3
|
+
[![Dependency Status][deps-image]][deps-url]
|
|
4
|
+
|
|
5
|
+
# @mapwhit/tileserver
|
|
6
|
+
|
|
7
|
+
This is a slimmed down clone of the [tileserver-gl] package.
|
|
8
|
+
It's intended to be used as a standalone server for tiles in [mbtiles] format.
|
|
9
|
+
|
|
10
|
+
The following features of the original are not supported:
|
|
11
|
+
|
|
12
|
+
- server side rasterization
|
|
13
|
+
- font server - if needed use [map-glyph-server]
|
|
14
|
+
- CORS support - if needed use behind NGINX or alternative
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
npm install -g @mapwhit/tileserver-gl-tiny
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
You will also need [mbtiles]
|
|
24
|
+
|
|
25
|
+
## Usage
|
|
26
|
+
|
|
27
|
+
```sh
|
|
28
|
+
tileserver-gl-tiny --config path/to/config/file
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Config file can be in `.json` or `yaml`. If config file name is not specified
|
|
32
|
+
`tilesrc` config file is located according to the rules describe in the [rc] project.
|
|
33
|
+
|
|
34
|
+
Config file example:
|
|
35
|
+
|
|
36
|
+
```ini
|
|
37
|
+
port = 5080
|
|
38
|
+
max-age = 10d
|
|
39
|
+
|
|
40
|
+
[options.paths]
|
|
41
|
+
|
|
42
|
+
; this is where .mbtiles files are located
|
|
43
|
+
root = /var/lib/tiles
|
|
44
|
+
|
|
45
|
+
[data.v3]
|
|
46
|
+
|
|
47
|
+
mbtiles = planet.mbtiles
|
|
48
|
+
|
|
49
|
+
; add more [data.xxx] section to serve additional .mbtiles
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Documentation
|
|
53
|
+
|
|
54
|
+
Most of the [tileserver-gl documentation] is relevant - specifically parts related to conig format.
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
[mbtiles]: https://wiki.openstreetmap.org/wiki/MBTiles
|
|
58
|
+
[tileserver-gl]: https://www.npmjs.com/package/tileserver-gl
|
|
59
|
+
[rc]: https://www.npmjs.com/package/rc
|
|
60
|
+
[tileserver-gl documentation]: http://tileserver.readthedocs.io
|
|
61
|
+
[map-glyph-server]: https://github.com/furkot/map-glyph-server
|
|
62
|
+
|
|
63
|
+
[npm-image]: https://img.shields.io/npm/v/@mapwhit/tileserver
|
|
64
|
+
[npm-url]: https://npmjs.org/package/@mapwhit/tileserver
|
|
65
|
+
|
|
66
|
+
[build-image]: https://img.shields.io/github/actions/workflow/status/mapwhit/tileserver/check.yaml?branch=main
|
|
67
|
+
[build-url]: https://github.com/mapwhit/tileserver/actions/workflows/check.yaml
|
|
68
|
+
|
|
69
|
+
[deps-image]: https://img.shields.io/librariesio/release/npm/@mapwhit/tileserver
|
|
70
|
+
[deps-url]: https://libraries.io/npm/@mapwhit%2Ftileserver
|
|
71
|
+
|
package/bin/tileserver
ADDED
package/index.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
const ms = require('ms');
|
|
2
|
+
const config = require('rc')('tiles', {
|
|
3
|
+
port: process.env.PORT || 5080,
|
|
4
|
+
bind: process.env.BIND
|
|
5
|
+
// max-age: // 'max-age for Cache-Control header: "5d", "3h", "1y" etc.'
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
const { name, version } = require('./package.json');
|
|
9
|
+
const makeServer = require('./lib/server');
|
|
10
|
+
|
|
11
|
+
function startServer() {
|
|
12
|
+
config.cacheControl = cacheControl(config['max-age']);
|
|
13
|
+
|
|
14
|
+
console.log(`Starting ${name} v${version}`);
|
|
15
|
+
console.log(`Port ${config.port} on ${config.bind}`);
|
|
16
|
+
|
|
17
|
+
return makeServer(config);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function cacheControl(maxAgeMillis) {
|
|
21
|
+
if (!maxAgeMillis) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
const maxAge = Math.floor(ms(maxAgeMillis) / 1000);
|
|
25
|
+
return `public, max-age=${maxAge}`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
startServer();
|
package/lib/server.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
process.env.UV_THREADPOOL_SIZE = Math.ceil(
|
|
2
|
+
Math.max(4, require('node:os').cpus().length * 1.5)
|
|
3
|
+
);
|
|
4
|
+
|
|
5
|
+
const fs = require('node:fs');
|
|
6
|
+
const path = require('node:path');
|
|
7
|
+
const connect = require('@pirxpilot/connect');
|
|
8
|
+
const Router = require('@pirxpilot/router');
|
|
9
|
+
const timings = require('server-timings');
|
|
10
|
+
|
|
11
|
+
const tiles = require('./tiles');
|
|
12
|
+
|
|
13
|
+
module.exports = makeServer;
|
|
14
|
+
|
|
15
|
+
function makeServer(config, callback = () => {}) {
|
|
16
|
+
const { options = {}, data = {}, cacheControl } = config;
|
|
17
|
+
|
|
18
|
+
options.paths = checkPath(options.paths);
|
|
19
|
+
|
|
20
|
+
const app = connect();
|
|
21
|
+
app.use((req, res, next) => {
|
|
22
|
+
res.locals ??= Object.create(null);
|
|
23
|
+
timings(req, res, next);
|
|
24
|
+
});
|
|
25
|
+
if (cacheControl) {
|
|
26
|
+
app.use(function (req, res, next) {
|
|
27
|
+
res.setHeader('Cache-Control', cacheControl);
|
|
28
|
+
next();
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const serving = Object.entries(data)
|
|
33
|
+
.map(([id, item]) => {
|
|
34
|
+
if (!item.mbtiles || item.mbtiles.length === 0) {
|
|
35
|
+
console.error(`Missing "mbtiles" property for ${id}`);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const mountPath = `/data/${id}`;
|
|
39
|
+
const { router, createTileJSON } = tiles(mountPath, options, item);
|
|
40
|
+
app.use(mountPath, router);
|
|
41
|
+
return createTileJSON;
|
|
42
|
+
})
|
|
43
|
+
.filter(Boolean);
|
|
44
|
+
|
|
45
|
+
const router = new Router();
|
|
46
|
+
router.get(['/index.json', '/data.json'], sendTileJSONs);
|
|
47
|
+
app.use(router);
|
|
48
|
+
|
|
49
|
+
const server = app.listen(config.port, config.bind, function () {
|
|
50
|
+
const { address, port } = this.address();
|
|
51
|
+
console.log('Listening on http://[%s]:%d/', address, port);
|
|
52
|
+
return callback();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
process.on('SIGINT', function () {
|
|
56
|
+
process.exit();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
app,
|
|
61
|
+
server
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
function sendTileJSONs(req, res) {
|
|
65
|
+
res.setHeader('Content-Type', 'application/json');
|
|
66
|
+
const result = serving.map(fn => fn(req));
|
|
67
|
+
res.end(JSON.stringify(result));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function checkPath(paths = {}, type = 'mbtiles') {
|
|
72
|
+
const resolved = path.resolve(paths.root || '', paths[type] || '');
|
|
73
|
+
if (!fs.existsSync(resolved)) {
|
|
74
|
+
console.error(
|
|
75
|
+
'The specified path for "%s" does not exist (%s).',
|
|
76
|
+
type,
|
|
77
|
+
resolved
|
|
78
|
+
);
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
81
|
+
console.log('%s located in %s', type, paths);
|
|
82
|
+
paths[type] = resolved;
|
|
83
|
+
return paths;
|
|
84
|
+
}
|
package/lib/tiles.js
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
const fs = require('node:fs');
|
|
2
|
+
const path = require('node:path');
|
|
3
|
+
const zlib = require('node:zlib');
|
|
4
|
+
|
|
5
|
+
const Router = require('@pirxpilot/router');
|
|
6
|
+
const mbtiles = require('@mapwhit/mbtiles');
|
|
7
|
+
|
|
8
|
+
const { fixTileJSONCenter, getTileUrls } = require('./utils');
|
|
9
|
+
|
|
10
|
+
module.exports = serveData;
|
|
11
|
+
|
|
12
|
+
function serveData(mountPath, options, item) {
|
|
13
|
+
const domains = item.domains || options.domains;
|
|
14
|
+
const format = 'pbf';
|
|
15
|
+
const suffix = options.pbfAlias ?? format;
|
|
16
|
+
const tileJSON = {
|
|
17
|
+
tilejson: '2.0.0',
|
|
18
|
+
format
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const filename = resolveFilename(options.paths.mbtiles, item.mbtiles);
|
|
22
|
+
const source = new mbtiles(filename);
|
|
23
|
+
const { filesize, mtime, scheme, tilejson, ...info } = source.getInfo();
|
|
24
|
+
Object.assign(tileJSON, info, item.tilejson);
|
|
25
|
+
fixTileJSONCenter(tileJSON);
|
|
26
|
+
|
|
27
|
+
const zoomRanges = initZoomRanges(tileJSON.minzoom, tileJSON.maxzoom);
|
|
28
|
+
|
|
29
|
+
const router = new Router();
|
|
30
|
+
router.get(`/:z/:x/:y.${suffix}`, checkParams, sendTile);
|
|
31
|
+
router.get('/{.json}', sendData);
|
|
32
|
+
return { router, createTileJSON };
|
|
33
|
+
|
|
34
|
+
function checkParams(req, res, next) {
|
|
35
|
+
const z = req.params.z | 0;
|
|
36
|
+
const x = req.params.x | 0;
|
|
37
|
+
const y = req.params.y | 0;
|
|
38
|
+
|
|
39
|
+
if (z < tileJSON.minzoom || z > tileJSON.maxzoom) {
|
|
40
|
+
return next(404);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const max = zoomRanges[z];
|
|
44
|
+
|
|
45
|
+
if (x < 0 || x >= max || y < 0 || y >= max) {
|
|
46
|
+
return next(404);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
req.params.z = z;
|
|
50
|
+
req.params.x = x;
|
|
51
|
+
req.params.y = y;
|
|
52
|
+
|
|
53
|
+
next();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function sendTile(req, res, next) {
|
|
57
|
+
const { z, x, y } = req.params;
|
|
58
|
+
const { tile, headers, error } = source.getTile(z, x, y);
|
|
59
|
+
if (error) {
|
|
60
|
+
const { message } = error;
|
|
61
|
+
const status = /does not exist/.test(message) ? 404 : 500;
|
|
62
|
+
return res.writeHead(status, message).end();
|
|
63
|
+
}
|
|
64
|
+
if (tile == null) {
|
|
65
|
+
return next(404);
|
|
66
|
+
}
|
|
67
|
+
if (options.removeETag) {
|
|
68
|
+
delete headers['ETag']; // do not trust the tile ETag -- regenerate
|
|
69
|
+
}
|
|
70
|
+
headers['Content-Type'] ??= 'application/x-protobuf';
|
|
71
|
+
res.setHeaders(new Headers(headers));
|
|
72
|
+
res.end(tile);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function sendData(req, res) {
|
|
76
|
+
res.setHeader('Content-Type', 'application/json');
|
|
77
|
+
const info = createTileJSON(req);
|
|
78
|
+
return res.end(JSON.stringify(info));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function createTileJSON(req) {
|
|
82
|
+
return {
|
|
83
|
+
...tileJSON,
|
|
84
|
+
tiles: getTileUrls(req, domains, mountPath, suffix)
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function resolveFilename(dir, name) {
|
|
90
|
+
const filename = path.resolve(dir, name);
|
|
91
|
+
const stats = fs.statSync(filename);
|
|
92
|
+
if (!stats.isFile() || stats.size === 0) {
|
|
93
|
+
throw Error(`Not valid MBTiles file: ${filename}`);
|
|
94
|
+
}
|
|
95
|
+
return filename;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function isGzipped(data) {
|
|
99
|
+
return data[0] === 0x1f && data[1] === 0x8b;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function initZoomRanges(min, max) {
|
|
103
|
+
const ranges = [];
|
|
104
|
+
let value = 2 ** min;
|
|
105
|
+
for (let i = min; i <= max; i++) {
|
|
106
|
+
ranges[i] = value;
|
|
107
|
+
value *= 2;
|
|
108
|
+
}
|
|
109
|
+
return ranges;
|
|
110
|
+
}
|
package/lib/utils.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
getTileUrls,
|
|
3
|
+
fixTileJSONCenter
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
function getTileUrls(req, domains, path, format) {
|
|
7
|
+
if (domains && typeof domains === 'string') {
|
|
8
|
+
domains = domains.split(/\s*,\s*/);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
if (!domains || domains.length === 0) {
|
|
12
|
+
domains = [req.headers.host];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const protocol = req.headers['x-forwarded-proto']?.startsWith('https')
|
|
16
|
+
? 'https'
|
|
17
|
+
: 'http';
|
|
18
|
+
|
|
19
|
+
return domains.map(
|
|
20
|
+
domain => `${protocol}://${domain}${path}/{z}/{x}/{y}.${format}`
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function fixTileJSONCenter(tileJSON) {
|
|
25
|
+
if (tileJSON.bounds && !tileJSON.center) {
|
|
26
|
+
const fitWidth = 1024;
|
|
27
|
+
const tiles = fitWidth / 256;
|
|
28
|
+
tileJSON.center = [
|
|
29
|
+
(tileJSON.bounds[0] + tileJSON.bounds[2]) / 2,
|
|
30
|
+
(tileJSON.bounds[1] + tileJSON.bounds[3]) / 2,
|
|
31
|
+
Math.round(
|
|
32
|
+
-Math.log((tileJSON.bounds[2] - tileJSON.bounds[0]) / 360 / tiles) /
|
|
33
|
+
Math.LN2
|
|
34
|
+
)
|
|
35
|
+
];
|
|
36
|
+
}
|
|
37
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mapwhit/tileserver",
|
|
3
|
+
"version": "3.5.0",
|
|
4
|
+
"description": "Map tile server for JSON GL styles - serving vector tiles",
|
|
5
|
+
"bin": {
|
|
6
|
+
"tileserver-gl-tiny": "bin/tileserver"
|
|
7
|
+
},
|
|
8
|
+
"authors": [
|
|
9
|
+
"Petr Sloup <petr.sloup@klokantech.com>",
|
|
10
|
+
"Damian Krzeminski <pirxpilot@furkot.com>"
|
|
11
|
+
],
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "git+https://github.com/mapwhit/tileserver.git"
|
|
15
|
+
},
|
|
16
|
+
"license": "BSD-2-Clause",
|
|
17
|
+
"scripts": {
|
|
18
|
+
"test": "make check"
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"@mapwhit/mbtiles": "^1.0.1",
|
|
22
|
+
"@pirxpilot/connect": "~4",
|
|
23
|
+
"@pirxpilot/router": "~1",
|
|
24
|
+
"ms": "~2",
|
|
25
|
+
"rc": "~1",
|
|
26
|
+
"server-timings": "~2"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@biomejs/biome": "^1.9.4",
|
|
30
|
+
"supertest": "~7"
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"index.js",
|
|
34
|
+
"lib"
|
|
35
|
+
]
|
|
36
|
+
}
|