tileserver-gl-light 4.12.0 → 4.13.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/docs/config.rst +22 -5
- package/package.json +8 -8
- 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 +10 -8
- package/src/promises.js +14 -0
- package/src/serve_data.js +10 -10
- package/src/serve_rendered.js +30 -11
- 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,6 +3,7 @@
|
|
|
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';
|
|
@@ -23,6 +24,7 @@ if (args.length >= 3 && args[2][0] !== '-') {
|
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
import { program } from 'commander';
|
|
27
|
+
import { existsP } from './promises.js';
|
|
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: {
|
|
@@ -189,7 +191,7 @@ const startWithInputFile = async (inputFile) => {
|
|
|
189
191
|
process.exit(1);
|
|
190
192
|
}
|
|
191
193
|
|
|
192
|
-
instance.getInfo((err, info) => {
|
|
194
|
+
instance.getInfo(async (err, info) => {
|
|
193
195
|
if (err || !info) {
|
|
194
196
|
console.log('ERROR: Metadata missing in the MBTiles.');
|
|
195
197
|
console.log(
|
|
@@ -207,11 +209,11 @@ const startWithInputFile = async (inputFile) => {
|
|
|
207
209
|
mbtiles: path.basename(inputFile),
|
|
208
210
|
};
|
|
209
211
|
|
|
210
|
-
const styles =
|
|
212
|
+
const styles = await fsp.readdir(path.resolve(styleDir, 'styles'));
|
|
211
213
|
for (const styleName of styles) {
|
|
212
214
|
const styleFileRel = styleName + '/style.json';
|
|
213
215
|
const styleFile = path.resolve(styleDir, 'styles', styleFileRel);
|
|
214
|
-
if (
|
|
216
|
+
if (await existsP(styleFile)) {
|
|
215
217
|
config['styles'][styleName] = {
|
|
216
218
|
style: styleFileRel,
|
|
217
219
|
tilejson: {
|
|
@@ -254,10 +256,10 @@ fs.stat(path.resolve(opts.config), async (err, stats) => {
|
|
|
254
256
|
return startWithInputFile(inputFile);
|
|
255
257
|
} else {
|
|
256
258
|
// try to find in the cwd
|
|
257
|
-
const files =
|
|
259
|
+
const files = await fsp.readdir(process.cwd());
|
|
258
260
|
for (const filename of files) {
|
|
259
261
|
if (filename.endsWith('.mbtiles') || filename.endsWith('.pmtiles')) {
|
|
260
|
-
const inputFilesStats =
|
|
262
|
+
const inputFilesStats = await fsp.stat(filename);
|
|
261
263
|
if (inputFilesStats.isFile() && inputFilesStats.size > 0) {
|
|
262
264
|
inputFile = filename;
|
|
263
265
|
break;
|
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,8 +1,7 @@
|
|
|
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';
|
|
@@ -10,12 +9,13 @@ import MBTiles from '@mapbox/mbtiles';
|
|
|
10
9
|
import Pbf from 'pbf';
|
|
11
10
|
import { VectorTile } from '@mapbox/vector-tile';
|
|
12
11
|
|
|
13
|
-
import { getTileUrls, isValidHttpUrl
|
|
12
|
+
import { fixTileJSONCenter, getTileUrls, isValidHttpUrl } from './utils.js';
|
|
14
13
|
import {
|
|
15
|
-
openPMtiles,
|
|
16
14
|
getPMtilesInfo,
|
|
17
15
|
getPMtilesTile,
|
|
16
|
+
openPMtiles,
|
|
18
17
|
} from './pmtiles_adapter.js';
|
|
18
|
+
import { gunzipP, gzipP } from './promises.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
|
}
|
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';
|
|
@@ -42,6 +41,8 @@ import {
|
|
|
42
41
|
getPMtilesTile,
|
|
43
42
|
} from './pmtiles_adapter.js';
|
|
44
43
|
import { renderOverlay, renderWatermark, renderAttribution } from './render.js';
|
|
44
|
+
import fsp from 'node:fs/promises';
|
|
45
|
+
import { gunzipP } from './promises.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;
|
|
@@ -1145,7 +1164,7 @@ export const serve_rendered = {
|
|
|
1145
1164
|
}
|
|
1146
1165
|
|
|
1147
1166
|
if (!isValidHttpUrl(inputFile)) {
|
|
1148
|
-
const inputFileStats =
|
|
1167
|
+
const inputFileStats = await fsp.stat(inputFile);
|
|
1149
1168
|
if (!inputFileStats.isFile() || inputFileStats.size === 0) {
|
|
1150
1169
|
throw Error(`Not valid PMTiles file: "${inputFile}"`);
|
|
1151
1170
|
}
|
|
@@ -1187,9 +1206,9 @@ export const serve_rendered = {
|
|
|
1187
1206
|
}
|
|
1188
1207
|
} else {
|
|
1189
1208
|
queue.push(
|
|
1190
|
-
new Promise((resolve, reject) => {
|
|
1209
|
+
new Promise(async (resolve, reject) => {
|
|
1191
1210
|
inputFile = path.resolve(options.paths.mbtiles, inputFile);
|
|
1192
|
-
const inputFileStats =
|
|
1211
|
+
const inputFileStats = await fsp.stat(inputFile);
|
|
1193
1212
|
if (!inputFileStats.isFile() || inputFileStats.size === 0) {
|
|
1194
1213
|
throw Error(`Not valid MBTiles file: "${inputFile}"`);
|
|
1195
1214
|
}
|
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
|
}
|