@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 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
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+
3
+ require('..');
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
+ }