tileserver-gl 4.12.0 → 4.13.1
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/docs/config.rst +22 -5
- package/package.json +20 -20
- package/public/resources/index.css +21 -4
- package/public/resources/maplibre-gl.js +4 -4
- package/public/resources/maplibre-gl.js.map +1 -1
- package/public/templates/index.tmpl +24 -2
- package/src/main.js +52 -57
- package/src/mbtiles_wrapper.js +46 -0
- package/src/promises.js +14 -0
- package/src/serve_data.js +26 -40
- package/src/serve_rendered.js +69 -68
- package/src/serve_style.js +1 -1
- package/src/utils.js +3 -2
|
@@ -13,17 +13,39 @@
|
|
|
13
13
|
el.setSelectionRange(0, el.value.length);
|
|
14
14
|
return false;
|
|
15
15
|
}
|
|
16
|
+
|
|
17
|
+
function filter() {
|
|
18
|
+
var filter = document.getElementById('filter').value.toLowerCase();
|
|
19
|
+
var items = document.getElementsByClassName('item');
|
|
20
|
+
for (var i = 0; i < items.length; i++) {
|
|
21
|
+
var item = items[i];
|
|
22
|
+
var dataName = item.getAttribute('data-name')?.toLowerCase() ?? '';
|
|
23
|
+
var dataIdentifier = item.getAttribute('data-id')?.toLowerCase() ?? '';
|
|
24
|
+
item.hidden = !(dataName.indexOf(filter) > -1 || dataIdentifier.indexOf(filter) > -1);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
16
27
|
</script>
|
|
17
28
|
</head>
|
|
18
29
|
<body>
|
|
19
30
|
<section>
|
|
20
31
|
<h1 class="title {{#if is_light}}light{{/if}}"><img src="{{public_url}}images/logo.png{{&key_query}}" alt="TileServer GL" /></h1>
|
|
21
32
|
<h2 class="subtitle">Vector {{#if is_light}}<s>and raster</s>{{else}}and raster{{/if}} maps with GL styles</h2>
|
|
33
|
+
<h2 class="box-header">Filter</h2>
|
|
34
|
+
<!-- filter box -->
|
|
35
|
+
<div class="box">
|
|
36
|
+
<div class="filter-item">
|
|
37
|
+
<div class="filter-details">
|
|
38
|
+
<h3>Filter styles and data by name or identifier</h3>
|
|
39
|
+
<!-- filter input , needs to call filter() when content changes...-->
|
|
40
|
+
<input id="filter" type="text" oninput="filter()" placeholder="Start typing name or identifier" />
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
22
44
|
{{#if styles}}
|
|
23
45
|
<h2 class="box-header">Styles</h2>
|
|
24
46
|
<div class="box">
|
|
25
47
|
{{#each styles}}
|
|
26
|
-
<div class="item">
|
|
48
|
+
<div class="item" data-id="{{@key}}" data-name="{{name}}">
|
|
27
49
|
{{#if thumbnail}}
|
|
28
50
|
<img src="{{public_url}}styles/{{@key}}/{{thumbnail}}{{&../key_query}}" alt="{{name}} preview" />
|
|
29
51
|
{{else}}
|
|
@@ -70,7 +92,7 @@
|
|
|
70
92
|
<h2 class="box-header">Data</h2>
|
|
71
93
|
<div class="box">
|
|
72
94
|
{{#each data}}
|
|
73
|
-
<div class="item">
|
|
95
|
+
<div class="item" data-id="{{@key}}" data-name="{{tileJSON.name}}">
|
|
74
96
|
{{#if thumbnail}}
|
|
75
97
|
<img src="{{public_url}}data/{{@key}}/{{thumbnail}}{{&../key_query}}" alt="{{name}} preview" />
|
|
76
98
|
{{else}}
|
package/src/main.js
CHANGED
|
@@ -3,13 +3,16 @@
|
|
|
3
3
|
'use strict';
|
|
4
4
|
|
|
5
5
|
import fs from 'node:fs';
|
|
6
|
+
import fsp from 'node:fs/promises';
|
|
6
7
|
import path from 'path';
|
|
7
8
|
import { fileURLToPath } from 'url';
|
|
8
9
|
import axios from 'axios';
|
|
9
10
|
import { server } from './server.js';
|
|
10
|
-
import MBTiles from '@mapbox/mbtiles';
|
|
11
11
|
import { isValidHttpUrl } from './utils.js';
|
|
12
12
|
import { openPMtiles, getPMtilesInfo } from './pmtiles_adapter.js';
|
|
13
|
+
import { program } from 'commander';
|
|
14
|
+
import { existsP } from './promises.js';
|
|
15
|
+
import { openMbTilesWrapper } from './mbtiles_wrapper.js';
|
|
13
16
|
|
|
14
17
|
const __filename = fileURLToPath(import.meta.url);
|
|
15
18
|
const __dirname = path.dirname(__filename);
|
|
@@ -22,7 +25,6 @@ if (args.length >= 3 && args[2][0] !== '-') {
|
|
|
22
25
|
args.splice(2, 0, '--mbtiles');
|
|
23
26
|
}
|
|
24
27
|
|
|
25
|
-
import { program } from 'commander';
|
|
26
28
|
program
|
|
27
29
|
.description('tileserver-gl startup options')
|
|
28
30
|
.usage('tileserver-gl [mbtiles] [options]')
|
|
@@ -95,7 +97,7 @@ const startWithInputFile = async (inputFile) => {
|
|
|
95
97
|
inputFile = path.resolve(process.cwd(), inputFile);
|
|
96
98
|
inputFilePath = path.dirname(inputFile);
|
|
97
99
|
|
|
98
|
-
const inputFileStats =
|
|
100
|
+
const inputFileStats = await fsp.stat(inputFile);
|
|
99
101
|
if (!inputFileStats.isFile() || inputFileStats.size === 0) {
|
|
100
102
|
console.log(`ERROR: Not a valid input file: `);
|
|
101
103
|
process.exit(1);
|
|
@@ -140,11 +142,11 @@ const startWithInputFile = async (inputFile) => {
|
|
|
140
142
|
};
|
|
141
143
|
}
|
|
142
144
|
|
|
143
|
-
const styles =
|
|
145
|
+
const styles = await fsp.readdir(path.resolve(styleDir, 'styles'));
|
|
144
146
|
for (const styleName of styles) {
|
|
145
147
|
const styleFileRel = styleName + '/style.json';
|
|
146
148
|
const styleFile = path.resolve(styleDir, 'styles', styleFileRel);
|
|
147
|
-
if (
|
|
149
|
+
if (await existsP(styleFile)) {
|
|
148
150
|
config['styles'][styleName] = {
|
|
149
151
|
style: styleFileRel,
|
|
150
152
|
tilejson: {
|
|
@@ -182,62 +184,55 @@ const startWithInputFile = async (inputFile) => {
|
|
|
182
184
|
);
|
|
183
185
|
process.exit(1);
|
|
184
186
|
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
`Make sure ${path.basename(inputFile)} is valid MBTiles.`,
|
|
197
|
-
);
|
|
198
|
-
process.exit(1);
|
|
199
|
-
}
|
|
200
|
-
const bounds = info.bounds;
|
|
187
|
+
let info;
|
|
188
|
+
try {
|
|
189
|
+
const mbw = await openMbTilesWrapper(inputFile);
|
|
190
|
+
info = await mbw.getInfo();
|
|
191
|
+
if (!info) throw new Error('Metadata missing in the MBTiles.');
|
|
192
|
+
} catch (err) {
|
|
193
|
+
console.log('ERROR: Unable to open MBTiles or read metadata:', err);
|
|
194
|
+
console.log(`Make sure ${path.basename(inputFile)} is valid MBTiles.`);
|
|
195
|
+
process.exit(1);
|
|
196
|
+
}
|
|
197
|
+
const bounds = info.bounds;
|
|
201
198
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
199
|
+
if (
|
|
200
|
+
info.format === 'pbf' &&
|
|
201
|
+
info.name.toLowerCase().indexOf('openmaptiles') > -1
|
|
202
|
+
) {
|
|
203
|
+
config['data'][`v3`] = {
|
|
204
|
+
mbtiles: path.basename(inputFile),
|
|
205
|
+
};
|
|
209
206
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
};
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
} else {
|
|
224
|
-
console.log(
|
|
225
|
-
`WARN: MBTiles not in "openmaptiles" format. Serving raw data only...`,
|
|
226
|
-
);
|
|
227
|
-
config['data'][(info.id || 'mbtiles').replace(/[?/:]/g, '_')] = {
|
|
228
|
-
mbtiles: path.basename(inputFile),
|
|
207
|
+
const styles = await fsp.readdir(path.resolve(styleDir, 'styles'));
|
|
208
|
+
for (const styleName of styles) {
|
|
209
|
+
const styleFileRel = styleName + '/style.json';
|
|
210
|
+
const styleFile = path.resolve(styleDir, 'styles', styleFileRel);
|
|
211
|
+
if (await existsP(styleFile)) {
|
|
212
|
+
config['styles'][styleName] = {
|
|
213
|
+
style: styleFileRel,
|
|
214
|
+
tilejson: {
|
|
215
|
+
bounds,
|
|
216
|
+
},
|
|
229
217
|
};
|
|
230
218
|
}
|
|
219
|
+
}
|
|
220
|
+
} else {
|
|
221
|
+
console.log(
|
|
222
|
+
`WARN: MBTiles not in "openmaptiles" format. Serving raw data only...`,
|
|
223
|
+
);
|
|
224
|
+
config['data'][(info.id || 'mbtiles').replace(/[?/:]/g, '_')] = {
|
|
225
|
+
mbtiles: path.basename(inputFile),
|
|
226
|
+
};
|
|
227
|
+
}
|
|
231
228
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
229
|
+
if (opts.verbose) {
|
|
230
|
+
console.log(JSON.stringify(config, undefined, 2));
|
|
231
|
+
} else {
|
|
232
|
+
console.log('Run with --verbose to see the config file here.');
|
|
233
|
+
}
|
|
237
234
|
|
|
238
|
-
|
|
239
|
-
});
|
|
240
|
-
});
|
|
235
|
+
return startServer(null, config);
|
|
241
236
|
}
|
|
242
237
|
};
|
|
243
238
|
|
|
@@ -254,10 +249,10 @@ fs.stat(path.resolve(opts.config), async (err, stats) => {
|
|
|
254
249
|
return startWithInputFile(inputFile);
|
|
255
250
|
} else {
|
|
256
251
|
// try to find in the cwd
|
|
257
|
-
const files =
|
|
252
|
+
const files = await fsp.readdir(process.cwd());
|
|
258
253
|
for (const filename of files) {
|
|
259
254
|
if (filename.endsWith('.mbtiles') || filename.endsWith('.pmtiles')) {
|
|
260
|
-
const inputFilesStats =
|
|
255
|
+
const inputFilesStats = await fsp.stat(filename);
|
|
261
256
|
if (inputFilesStats.isFile() && inputFilesStats.size > 0) {
|
|
262
257
|
inputFile = filename;
|
|
263
258
|
break;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import MBTiles from '@mapbox/mbtiles';
|
|
2
|
+
import util from 'node:util';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Promise-ful wrapper around the MBTiles class.
|
|
6
|
+
*/
|
|
7
|
+
class MBTilesWrapper {
|
|
8
|
+
constructor(mbtiles) {
|
|
9
|
+
this._mbtiles = mbtiles;
|
|
10
|
+
this._getInfoP = util.promisify(mbtiles.getInfo.bind(mbtiles));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Get the underlying MBTiles object.
|
|
15
|
+
* @returns {MBTiles}
|
|
16
|
+
*/
|
|
17
|
+
getMbTiles() {
|
|
18
|
+
return this._mbtiles;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Get the MBTiles metadata object.
|
|
23
|
+
* @returns {Promise<object>}
|
|
24
|
+
*/
|
|
25
|
+
getInfo() {
|
|
26
|
+
return this._getInfoP();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Open the given MBTiles file and return a promise that resolves with a
|
|
32
|
+
* MBTilesWrapper instance.
|
|
33
|
+
* @param inputFile Input file
|
|
34
|
+
* @returns {Promise<MBTilesWrapper>}
|
|
35
|
+
*/
|
|
36
|
+
export function openMbTilesWrapper(inputFile) {
|
|
37
|
+
return new Promise((resolve, reject) => {
|
|
38
|
+
const mbtiles = new MBTiles(inputFile + '?mode=ro', (err) => {
|
|
39
|
+
if (err) {
|
|
40
|
+
reject(err);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
resolve(new MBTilesWrapper(mbtiles));
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
}
|
package/src/promises.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import util from 'node:util';
|
|
2
|
+
import fsp from 'node:fs/promises';
|
|
3
|
+
import zlib from 'zlib';
|
|
4
|
+
|
|
5
|
+
export const gzipP = util.promisify(zlib.gzip);
|
|
6
|
+
export const gunzipP = util.promisify(zlib.gunzip);
|
|
7
|
+
export const existsP = async (path) => {
|
|
8
|
+
try {
|
|
9
|
+
await fsp.access(path); // Defaults to F_OK: indicating that the file is visible to the calling process
|
|
10
|
+
return true;
|
|
11
|
+
} catch (err) {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
};
|
package/src/serve_data.js
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
import
|
|
3
|
+
import fsp from 'node:fs/promises';
|
|
4
4
|
import path from 'path';
|
|
5
|
-
import zlib from 'zlib';
|
|
6
5
|
|
|
7
6
|
import clone from 'clone';
|
|
8
7
|
import express from 'express';
|
|
9
|
-
import MBTiles from '@mapbox/mbtiles';
|
|
10
8
|
import Pbf from 'pbf';
|
|
11
9
|
import { VectorTile } from '@mapbox/vector-tile';
|
|
12
10
|
|
|
13
|
-
import { getTileUrls, isValidHttpUrl
|
|
11
|
+
import { fixTileJSONCenter, getTileUrls, isValidHttpUrl } from './utils.js';
|
|
14
12
|
import {
|
|
15
|
-
openPMtiles,
|
|
16
13
|
getPMtilesInfo,
|
|
17
14
|
getPMtilesTile,
|
|
15
|
+
openPMtiles,
|
|
18
16
|
} from './pmtiles_adapter.js';
|
|
17
|
+
import { gunzipP, gzipP } from './promises.js';
|
|
18
|
+
import { openMbTilesWrapper } from './mbtiles_wrapper.js';
|
|
19
19
|
|
|
20
20
|
export const serve_data = {
|
|
21
21
|
init: (options, repo) => {
|
|
@@ -89,12 +89,12 @@ export const serve_data = {
|
|
|
89
89
|
headers['Content-Encoding'] = 'gzip';
|
|
90
90
|
res.set(headers);
|
|
91
91
|
|
|
92
|
-
data =
|
|
92
|
+
data = await gzipP(data);
|
|
93
93
|
|
|
94
94
|
return res.status(200).send(data);
|
|
95
95
|
}
|
|
96
96
|
} else if (item.sourceType === 'mbtiles') {
|
|
97
|
-
item.source.getTile(z, x, y, (err, data, headers) => {
|
|
97
|
+
item.source.getTile(z, x, y, async (err, data, headers) => {
|
|
98
98
|
let isGzipped;
|
|
99
99
|
if (err) {
|
|
100
100
|
if (/does not exist/.test(err.message)) {
|
|
@@ -114,7 +114,7 @@ export const serve_data = {
|
|
|
114
114
|
data.slice(0, 2).indexOf(Buffer.from([0x1f, 0x8b])) === 0;
|
|
115
115
|
if (options.dataDecoratorFunc) {
|
|
116
116
|
if (isGzipped) {
|
|
117
|
-
data =
|
|
117
|
+
data = await gunzipP(data);
|
|
118
118
|
isGzipped = false;
|
|
119
119
|
}
|
|
120
120
|
data = options.dataDecoratorFunc(id, 'data', data, z, x, y);
|
|
@@ -126,7 +126,7 @@ export const serve_data = {
|
|
|
126
126
|
headers['Content-Type'] = 'application/json';
|
|
127
127
|
|
|
128
128
|
if (isGzipped) {
|
|
129
|
-
data =
|
|
129
|
+
data = await gunzipP(data);
|
|
130
130
|
isGzipped = false;
|
|
131
131
|
}
|
|
132
132
|
|
|
@@ -151,7 +151,7 @@ export const serve_data = {
|
|
|
151
151
|
res.set(headers);
|
|
152
152
|
|
|
153
153
|
if (!isGzipped) {
|
|
154
|
-
data =
|
|
154
|
+
data = await gzipP(data);
|
|
155
155
|
}
|
|
156
156
|
|
|
157
157
|
return res.status(200).send(data);
|
|
@@ -212,7 +212,7 @@ export const serve_data = {
|
|
|
212
212
|
};
|
|
213
213
|
|
|
214
214
|
if (!isValidHttpUrl(inputFile)) {
|
|
215
|
-
const inputFileStats =
|
|
215
|
+
const inputFileStats = await fsp.stat(inputFile);
|
|
216
216
|
if (!inputFileStats.isFile() || inputFileStats.size === 0) {
|
|
217
217
|
throw Error(`Not valid input file: "${inputFile}"`);
|
|
218
218
|
}
|
|
@@ -242,39 +242,25 @@ export const serve_data = {
|
|
|
242
242
|
}
|
|
243
243
|
} else if (inputType === 'mbtiles') {
|
|
244
244
|
sourceType = 'mbtiles';
|
|
245
|
-
const
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
}
|
|
251
|
-
source.getInfo((err, info) => {
|
|
252
|
-
if (err) {
|
|
253
|
-
reject(err);
|
|
254
|
-
return;
|
|
255
|
-
}
|
|
256
|
-
tileJSON['name'] = id;
|
|
257
|
-
tileJSON['format'] = 'pbf';
|
|
258
|
-
|
|
259
|
-
Object.assign(tileJSON, info);
|
|
245
|
+
const mbw = await openMbTilesWrapper(inputFile);
|
|
246
|
+
const info = await mbw.getInfo();
|
|
247
|
+
source = mbw.getMbTiles();
|
|
248
|
+
tileJSON['name'] = id;
|
|
249
|
+
tileJSON['format'] = 'pbf';
|
|
260
250
|
|
|
261
|
-
|
|
262
|
-
delete tileJSON['filesize'];
|
|
263
|
-
delete tileJSON['mtime'];
|
|
264
|
-
delete tileJSON['scheme'];
|
|
251
|
+
Object.assign(tileJSON, info);
|
|
265
252
|
|
|
266
|
-
|
|
267
|
-
|
|
253
|
+
tileJSON['tilejson'] = '2.0.0';
|
|
254
|
+
delete tileJSON['filesize'];
|
|
255
|
+
delete tileJSON['mtime'];
|
|
256
|
+
delete tileJSON['scheme'];
|
|
268
257
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
}
|
|
272
|
-
resolve();
|
|
273
|
-
});
|
|
274
|
-
});
|
|
275
|
-
});
|
|
258
|
+
Object.assign(tileJSON, params.tilejson || {});
|
|
259
|
+
fixTileJSONCenter(tileJSON);
|
|
276
260
|
|
|
277
|
-
|
|
261
|
+
if (options.dataDecoratorFunc) {
|
|
262
|
+
tileJSON = options.dataDecoratorFunc(id, 'tilejson', tileJSON);
|
|
263
|
+
}
|
|
278
264
|
}
|
|
279
265
|
|
|
280
266
|
repo[id] = {
|
package/src/serve_rendered.js
CHANGED
|
@@ -17,7 +17,6 @@ import fs from 'node:fs';
|
|
|
17
17
|
import path from 'path';
|
|
18
18
|
import url from 'url';
|
|
19
19
|
import util from 'util';
|
|
20
|
-
import zlib from 'zlib';
|
|
21
20
|
import sharp from 'sharp';
|
|
22
21
|
import clone from 'clone';
|
|
23
22
|
import Color from 'color';
|
|
@@ -25,7 +24,6 @@ import express from 'express';
|
|
|
25
24
|
import sanitize from 'sanitize-filename';
|
|
26
25
|
import SphericalMercator from '@mapbox/sphericalmercator';
|
|
27
26
|
import mlgl from '@maplibre/maplibre-gl-native';
|
|
28
|
-
import MBTiles from '@mapbox/mbtiles';
|
|
29
27
|
import polyline from '@mapbox/polyline';
|
|
30
28
|
import proj4 from 'proj4';
|
|
31
29
|
import axios from 'axios';
|
|
@@ -42,6 +40,9 @@ import {
|
|
|
42
40
|
getPMtilesTile,
|
|
43
41
|
} from './pmtiles_adapter.js';
|
|
44
42
|
import { renderOverlay, renderWatermark, renderAttribution } from './render.js';
|
|
43
|
+
import fsp from 'node:fs/promises';
|
|
44
|
+
import { gunzipP } from './promises.js';
|
|
45
|
+
import { openMbTilesWrapper } from './mbtiles_wrapper.js';
|
|
45
46
|
|
|
46
47
|
const FLOAT_PATTERN = '[+-]?(?:\\d+|\\d+.?\\d+)';
|
|
47
48
|
const PATH_PATTERN =
|
|
@@ -503,14 +504,32 @@ const respondImage = (
|
|
|
503
504
|
image.composite(composites);
|
|
504
505
|
}
|
|
505
506
|
|
|
506
|
-
|
|
507
|
+
// Legacy formatQuality is deprecated but still works
|
|
508
|
+
const formatQualities = options.formatQuality || {};
|
|
509
|
+
if (Object.keys(formatQualities).length !== 0) {
|
|
510
|
+
console.log(
|
|
511
|
+
'WARNING: The formatQuality option is deprecated and has been replaced with formatOptions. Please see the documentation. The values from formatQuality will be used if a quality setting is not provided via formatOptions.',
|
|
512
|
+
);
|
|
513
|
+
}
|
|
514
|
+
const formatQuality = formatQualities[format];
|
|
515
|
+
|
|
516
|
+
const formatOptions = (options.formatOptions || {})[format] || {};
|
|
507
517
|
|
|
508
518
|
if (format === 'png') {
|
|
509
|
-
image.png({
|
|
519
|
+
image.png({
|
|
520
|
+
progressive: formatOptions.progressive,
|
|
521
|
+
compressionLevel: formatOptions.compressionLevel,
|
|
522
|
+
adaptiveFiltering: formatOptions.adaptiveFiltering,
|
|
523
|
+
palette: formatOptions.palette,
|
|
524
|
+
quality: formatOptions.quality,
|
|
525
|
+
effort: formatOptions.effort,
|
|
526
|
+
colors: formatOptions.colors,
|
|
527
|
+
dither: formatOptions.dither,
|
|
528
|
+
});
|
|
510
529
|
} else if (format === 'jpeg') {
|
|
511
|
-
image.jpeg({ quality: formatQuality || 80 });
|
|
530
|
+
image.jpeg({ quality: formatOptions.quality || formatQuality || 80 });
|
|
512
531
|
} else if (format === 'webp') {
|
|
513
|
-
image.webp({ quality: formatQuality || 90 });
|
|
532
|
+
image.webp({ quality: formatOptions.quality || formatQuality || 90 });
|
|
514
533
|
}
|
|
515
534
|
image.toBuffer((err, buffer, info) => {
|
|
516
535
|
if (!buffer) {
|
|
@@ -943,7 +962,7 @@ export const serve_rendered = {
|
|
|
943
962
|
callback(null, response);
|
|
944
963
|
}
|
|
945
964
|
} else if (sourceType === 'mbtiles') {
|
|
946
|
-
source.getTile(z, x, y, (err, data, headers) => {
|
|
965
|
+
source.getTile(z, x, y, async (err, data, headers) => {
|
|
947
966
|
if (err) {
|
|
948
967
|
if (options.verbose)
|
|
949
968
|
console.log('MBTiles error, serving empty', err);
|
|
@@ -962,7 +981,7 @@ export const serve_rendered = {
|
|
|
962
981
|
|
|
963
982
|
if (format === 'pbf') {
|
|
964
983
|
try {
|
|
965
|
-
response.data =
|
|
984
|
+
response.data = await gunzipP(data);
|
|
966
985
|
} catch (err) {
|
|
967
986
|
console.log(
|
|
968
987
|
'Skipping incorrect header for tile mbtiles://%s/%s/%s/%s.pbf',
|
|
@@ -1039,7 +1058,7 @@ export const serve_rendered = {
|
|
|
1039
1058
|
const styleFile = params.style;
|
|
1040
1059
|
const styleJSONPath = path.resolve(options.paths.styles, styleFile);
|
|
1041
1060
|
try {
|
|
1042
|
-
styleJSON = JSON.parse(
|
|
1061
|
+
styleJSON = JSON.parse(await fsp.readFile(styleJSONPath));
|
|
1043
1062
|
} catch (e) {
|
|
1044
1063
|
console.log('Error parsing style file');
|
|
1045
1064
|
return false;
|
|
@@ -1112,7 +1131,6 @@ export const serve_rendered = {
|
|
|
1112
1131
|
};
|
|
1113
1132
|
repo[id] = repoobj;
|
|
1114
1133
|
|
|
1115
|
-
const queue = [];
|
|
1116
1134
|
for (const name of Object.keys(styleJSON.sources)) {
|
|
1117
1135
|
let sourceType;
|
|
1118
1136
|
let source = styleJSON.sources[name];
|
|
@@ -1145,7 +1163,7 @@ export const serve_rendered = {
|
|
|
1145
1163
|
}
|
|
1146
1164
|
|
|
1147
1165
|
if (!isValidHttpUrl(inputFile)) {
|
|
1148
|
-
const inputFileStats =
|
|
1166
|
+
const inputFileStats = await fsp.stat(inputFile);
|
|
1149
1167
|
if (!inputFileStats.isFile() || inputFileStats.size === 0) {
|
|
1150
1168
|
throw Error(`Not valid PMTiles file: "${inputFile}"`);
|
|
1151
1169
|
}
|
|
@@ -1186,69 +1204,52 @@ export const serve_rendered = {
|
|
|
1186
1204
|
}
|
|
1187
1205
|
}
|
|
1188
1206
|
} else {
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
map.sources[name].getInfo((err, info) => {
|
|
1198
|
-
if (err) {
|
|
1199
|
-
console.error(err);
|
|
1200
|
-
return;
|
|
1201
|
-
}
|
|
1202
|
-
map.sourceTypes[name] = 'mbtiles';
|
|
1203
|
-
|
|
1204
|
-
if (!repoobj.dataProjWGStoInternalWGS && info.proj4) {
|
|
1205
|
-
// how to do this for multiple sources with different proj4 defs?
|
|
1206
|
-
const to3857 = proj4('EPSG:3857');
|
|
1207
|
-
const toDataProj = proj4(info.proj4);
|
|
1208
|
-
repoobj.dataProjWGStoInternalWGS = (xy) =>
|
|
1209
|
-
to3857.inverse(toDataProj.forward(xy));
|
|
1210
|
-
}
|
|
1207
|
+
const inputFileStats = await fsp.stat(inputFile);
|
|
1208
|
+
if (!inputFileStats.isFile() || inputFileStats.size === 0) {
|
|
1209
|
+
throw Error(`Not valid MBTiles file: "${inputFile}"`);
|
|
1210
|
+
}
|
|
1211
|
+
const mbw = await openMbTilesWrapper(inputFile);
|
|
1212
|
+
const info = await mbw.getInfo();
|
|
1213
|
+
map.sources[name] = mbw.getMbTiles();
|
|
1214
|
+
map.sourceTypes[name] = 'mbtiles';
|
|
1211
1215
|
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
delete source.scheme;
|
|
1220
|
-
|
|
1221
|
-
if (options.dataDecoratorFunc) {
|
|
1222
|
-
source = options.dataDecoratorFunc(
|
|
1223
|
-
name,
|
|
1224
|
-
'tilejson',
|
|
1225
|
-
source,
|
|
1226
|
-
);
|
|
1227
|
-
}
|
|
1216
|
+
if (!repoobj.dataProjWGStoInternalWGS && info.proj4) {
|
|
1217
|
+
// how to do this for multiple sources with different proj4 defs?
|
|
1218
|
+
const to3857 = proj4('EPSG:3857');
|
|
1219
|
+
const toDataProj = proj4(info.proj4);
|
|
1220
|
+
repoobj.dataProjWGStoInternalWGS = (xy) =>
|
|
1221
|
+
to3857.inverse(toDataProj.forward(xy));
|
|
1222
|
+
}
|
|
1228
1223
|
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1224
|
+
const type = source.type;
|
|
1225
|
+
Object.assign(source, info);
|
|
1226
|
+
source.type = type;
|
|
1227
|
+
source.tiles = [
|
|
1228
|
+
// meta url which will be detected when requested
|
|
1229
|
+
`mbtiles://${name}/{z}/{x}/{y}.${info.format || 'pbf'}`,
|
|
1230
|
+
];
|
|
1231
|
+
delete source.scheme;
|
|
1232
|
+
|
|
1233
|
+
if (options.dataDecoratorFunc) {
|
|
1234
|
+
source = options.dataDecoratorFunc(name, 'tilejson', source);
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
if (
|
|
1238
|
+
!attributionOverride &&
|
|
1239
|
+
source.attribution &&
|
|
1240
|
+
source.attribution.length > 0
|
|
1241
|
+
) {
|
|
1242
|
+
if (!tileJSON.attribution.includes(source.attribution)) {
|
|
1243
|
+
if (tileJSON.attribution.length > 0) {
|
|
1244
|
+
tileJSON.attribution += ' | ';
|
|
1245
|
+
}
|
|
1246
|
+
tileJSON.attribution += source.attribution;
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1246
1249
|
}
|
|
1247
1250
|
}
|
|
1248
1251
|
}
|
|
1249
1252
|
|
|
1250
|
-
await Promise.all(queue);
|
|
1251
|
-
|
|
1252
1253
|
// standard and @2x tiles are much more usual -> default to larger pools
|
|
1253
1254
|
const minPoolSizes = options.minRendererPoolSizes || [8, 4, 2];
|
|
1254
1255
|
const maxPoolSizes = options.maxRendererPoolSizes || [16, 8, 4];
|
package/src/serve_style.js
CHANGED
|
@@ -87,7 +87,7 @@ export const serve_style = {
|
|
|
87
87
|
|
|
88
88
|
let styleFileData;
|
|
89
89
|
try {
|
|
90
|
-
styleFileData = fs.readFileSync(styleFile);
|
|
90
|
+
styleFileData = fs.readFileSync(styleFile); // TODO: could be made async if this function was
|
|
91
91
|
} catch (e) {
|
|
92
92
|
console.log('Error reading style file');
|
|
93
93
|
return false;
|
package/src/utils.js
CHANGED
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import fsPromises from 'fs/promises';
|
|
5
|
-
import fs
|
|
5
|
+
import fs from 'node:fs';
|
|
6
6
|
import clone from 'clone';
|
|
7
7
|
import { combine } from '@jsse/pbfont';
|
|
8
|
+
import { existsP } from './promises.js';
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Restrict user input to an allowed set of options.
|
|
@@ -225,7 +226,7 @@ export const listFonts = async (fontPath) => {
|
|
|
225
226
|
const stats = await fsPromises.stat(path.join(fontPath, file));
|
|
226
227
|
if (
|
|
227
228
|
stats.isDirectory() &&
|
|
228
|
-
|
|
229
|
+
(await existsP(path.join(fontPath, file, '0-255.pbf')))
|
|
229
230
|
) {
|
|
230
231
|
existingFonts[path.basename(file)] = true;
|
|
231
232
|
}
|