tileserver-gl 4.1.1 → 4.2.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/.dockerignore +1 -0
- package/.eslintrc.cjs +32 -0
- package/.gitattributes +11 -0
- package/.husky/commit-msg +21 -0
- package/.husky/pre-push +4 -0
- package/Dockerfile +67 -45
- package/Dockerfile_light +27 -6
- package/Dockerfile_test +30 -21
- package/README.md +3 -3
- package/commitlint.config.cjs +3 -0
- package/docker-entrypoint.sh +1 -1
- package/docker-entrypoint_light.sh +1 -1
- package/docs/config.rst +9 -0
- package/docs/endpoints.rst +27 -1
- package/docs/installation.rst +1 -1
- package/lint-staged.config.cjs +4 -0
- package/package.json +46 -13
- package/prettier.config.cjs +13 -0
- package/public/templates/wmts.tmpl +4 -4
- package/publish.js +17 -9
- package/run.sh +1 -1
- package/src/healthcheck.js +18 -0
- package/src/main.js +57 -65
- package/src/serve_data.js +99 -82
- package/src/serve_font.js +19 -10
- package/src/serve_light.js +5 -6
- package/src/serve_rendered.js +889 -291
- package/src/serve_style.js +31 -16
- package/src/server.js +268 -144
- package/src/utils.js +62 -43
- package/test/metadata.js +44 -43
- package/test/setup.js +8 -7
- package/test/static.js +127 -31
- package/test/style.js +31 -26
- package/test/tiles_data.js +5 -5
- package/test/tiles_rendered.js +7 -7
package/src/server.js
CHANGED
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
4
|
import os from 'os';
|
|
5
|
-
process.env.UV_THREADPOOL_SIZE =
|
|
6
|
-
Math.ceil(Math.max(4, os.cpus().length * 1.5));
|
|
5
|
+
process.env.UV_THREADPOOL_SIZE = Math.ceil(Math.max(4, os.cpus().length * 1.5));
|
|
7
6
|
|
|
8
7
|
import fs from 'node:fs';
|
|
9
8
|
import path from 'path';
|
|
@@ -17,20 +16,28 @@ import handlebars from 'handlebars';
|
|
|
17
16
|
import SphericalMercator from '@mapbox/sphericalmercator';
|
|
18
17
|
const mercator = new SphericalMercator();
|
|
19
18
|
import morgan from 'morgan';
|
|
20
|
-
import {serve_data} from './serve_data.js';
|
|
21
|
-
import {serve_style} from './serve_style.js';
|
|
22
|
-
import {serve_font} from './serve_font.js';
|
|
23
|
-
import {getTileUrls, getPublicUrl} from './utils.js';
|
|
19
|
+
import { serve_data } from './serve_data.js';
|
|
20
|
+
import { serve_style } from './serve_style.js';
|
|
21
|
+
import { serve_font } from './serve_font.js';
|
|
22
|
+
import { getTileUrls, getPublicUrl } from './utils.js';
|
|
24
23
|
|
|
25
|
-
import {fileURLToPath} from 'url';
|
|
24
|
+
import { fileURLToPath } from 'url';
|
|
26
25
|
const __filename = fileURLToPath(import.meta.url);
|
|
27
26
|
const __dirname = path.dirname(__filename);
|
|
28
|
-
const packageJson = JSON.parse(
|
|
27
|
+
const packageJson = JSON.parse(
|
|
28
|
+
fs.readFileSync(__dirname + '/../package.json', 'utf8'),
|
|
29
|
+
);
|
|
29
30
|
|
|
30
31
|
const isLight = packageJson.name.slice(-6) === '-light';
|
|
31
|
-
const serve_rendered = (
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
const serve_rendered = (
|
|
33
|
+
await import(`${!isLight ? `./serve_rendered.js` : `./serve_light.js`}`)
|
|
34
|
+
).serve_rendered;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
*
|
|
38
|
+
* @param opts
|
|
39
|
+
*/
|
|
40
|
+
function start(opts) {
|
|
34
41
|
console.log('Starting server');
|
|
35
42
|
|
|
36
43
|
const app = express().disable('x-powered-by');
|
|
@@ -38,18 +45,24 @@ export function server(opts) {
|
|
|
38
45
|
styles: {},
|
|
39
46
|
rendered: {},
|
|
40
47
|
data: {},
|
|
41
|
-
fonts: {}
|
|
48
|
+
fonts: {},
|
|
42
49
|
};
|
|
43
50
|
|
|
44
51
|
app.enable('trust proxy');
|
|
45
52
|
|
|
46
53
|
if (process.env.NODE_ENV !== 'test') {
|
|
47
|
-
const defaultLogFormat =
|
|
54
|
+
const defaultLogFormat =
|
|
55
|
+
process.env.NODE_ENV === 'production' ? 'tiny' : 'dev';
|
|
48
56
|
const logFormat = opts.logFormat || defaultLogFormat;
|
|
49
|
-
app.use(
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
57
|
+
app.use(
|
|
58
|
+
morgan(logFormat, {
|
|
59
|
+
stream: opts.logFile
|
|
60
|
+
? fs.createWriteStream(opts.logFile, { flags: 'a' })
|
|
61
|
+
: process.stdout,
|
|
62
|
+
skip: (req, res) =>
|
|
63
|
+
opts.silent && (res.statusCode === 200 || res.statusCode === 304),
|
|
64
|
+
}),
|
|
65
|
+
);
|
|
53
66
|
}
|
|
54
67
|
|
|
55
68
|
let config = opts.config || null;
|
|
@@ -74,17 +87,21 @@ export function server(opts) {
|
|
|
74
87
|
options.paths = paths;
|
|
75
88
|
paths.root = path.resolve(
|
|
76
89
|
configPath ? path.dirname(configPath) : process.cwd(),
|
|
77
|
-
paths.root || ''
|
|
90
|
+
paths.root || '',
|
|
91
|
+
);
|
|
78
92
|
paths.styles = path.resolve(paths.root, paths.styles || '');
|
|
79
93
|
paths.fonts = path.resolve(paths.root, paths.fonts || '');
|
|
80
94
|
paths.sprites = path.resolve(paths.root, paths.sprites || '');
|
|
81
95
|
paths.mbtiles = path.resolve(paths.root, paths.mbtiles || '');
|
|
96
|
+
paths.icons = path.resolve(paths.root, paths.icons || '');
|
|
82
97
|
|
|
83
98
|
const startupPromises = [];
|
|
84
99
|
|
|
85
100
|
const checkPath = (type) => {
|
|
86
101
|
if (!fs.existsSync(paths[type])) {
|
|
87
|
-
console.error(
|
|
102
|
+
console.error(
|
|
103
|
+
`The specified path for "${type}" does not exist (${paths[type]}).`,
|
|
104
|
+
);
|
|
88
105
|
process.exit(1);
|
|
89
106
|
}
|
|
90
107
|
};
|
|
@@ -92,10 +109,51 @@ export function server(opts) {
|
|
|
92
109
|
checkPath('fonts');
|
|
93
110
|
checkPath('sprites');
|
|
94
111
|
checkPath('mbtiles');
|
|
112
|
+
checkPath('icons');
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Recursively get all files within a directory.
|
|
116
|
+
* Inspired by https://stackoverflow.com/a/45130990/10133863
|
|
117
|
+
*
|
|
118
|
+
* @param {string} directory Absolute path to a directory to get files from.
|
|
119
|
+
*/
|
|
120
|
+
const getFiles = async (directory) => {
|
|
121
|
+
// Fetch all entries of the directory and attach type information
|
|
122
|
+
const dirEntries = await fs.promises.readdir(directory, {
|
|
123
|
+
withFileTypes: true,
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// Iterate through entries and return the relative file-path to the icon directory if it is not a directory
|
|
127
|
+
// otherwise initiate a recursive call
|
|
128
|
+
const files = await Promise.all(
|
|
129
|
+
dirEntries.map((dirEntry) => {
|
|
130
|
+
const entryPath = path.resolve(directory, dirEntry.name);
|
|
131
|
+
return dirEntry.isDirectory()
|
|
132
|
+
? getFiles(entryPath)
|
|
133
|
+
: entryPath.replace(paths.icons + path.sep, '');
|
|
134
|
+
}),
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
// Flatten the list of files to a single array
|
|
138
|
+
return files.flat();
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
// Load all available icons into a settings object
|
|
142
|
+
startupPromises.push(
|
|
143
|
+
new Promise((resolve) => {
|
|
144
|
+
getFiles(paths.icons).then((files) => {
|
|
145
|
+
paths.availableIcons = files;
|
|
146
|
+
resolve();
|
|
147
|
+
});
|
|
148
|
+
}),
|
|
149
|
+
);
|
|
95
150
|
|
|
96
151
|
if (options.dataDecorator) {
|
|
97
152
|
try {
|
|
98
|
-
options.dataDecoratorFunc = require(path.resolve(
|
|
153
|
+
options.dataDecoratorFunc = require(path.resolve(
|
|
154
|
+
paths.root,
|
|
155
|
+
options.dataDecorator,
|
|
156
|
+
));
|
|
99
157
|
} catch (e) {}
|
|
100
158
|
}
|
|
101
159
|
|
|
@@ -109,54 +167,69 @@ export function server(opts) {
|
|
|
109
167
|
app.use('/styles/', serve_style.init(options, serving.styles));
|
|
110
168
|
if (!isLight) {
|
|
111
169
|
startupPromises.push(
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
})
|
|
170
|
+
serve_rendered.init(options, serving.rendered).then((sub) => {
|
|
171
|
+
app.use('/styles/', sub);
|
|
172
|
+
}),
|
|
116
173
|
);
|
|
117
174
|
}
|
|
118
175
|
|
|
119
176
|
const addStyle = (id, item, allowMoreData, reportFonts) => {
|
|
120
177
|
let success = true;
|
|
121
178
|
if (item.serve_data !== false) {
|
|
122
|
-
success = serve_style.add(
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
179
|
+
success = serve_style.add(
|
|
180
|
+
options,
|
|
181
|
+
serving.styles,
|
|
182
|
+
item,
|
|
183
|
+
id,
|
|
184
|
+
opts.publicUrl,
|
|
185
|
+
(mbtiles, fromData) => {
|
|
186
|
+
let dataItemId;
|
|
187
|
+
for (const id of Object.keys(data)) {
|
|
188
|
+
if (fromData) {
|
|
189
|
+
if (id === mbtiles) {
|
|
190
|
+
dataItemId = id;
|
|
134
191
|
}
|
|
135
|
-
}
|
|
136
|
-
if (dataItemId) { // mbtiles exist in the data config
|
|
137
|
-
return dataItemId;
|
|
138
192
|
} else {
|
|
139
|
-
if (
|
|
140
|
-
|
|
141
|
-
return undefined;
|
|
142
|
-
} else {
|
|
143
|
-
let id = mbtiles.substr(0, mbtiles.lastIndexOf('.')) || mbtiles;
|
|
144
|
-
while (data[id]) id += '_';
|
|
145
|
-
data[id] = {
|
|
146
|
-
'mbtiles': mbtiles
|
|
147
|
-
};
|
|
148
|
-
return id;
|
|
193
|
+
if (data[id].mbtiles === mbtiles) {
|
|
194
|
+
dataItemId = id;
|
|
149
195
|
}
|
|
150
196
|
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
|
|
197
|
+
}
|
|
198
|
+
if (dataItemId) {
|
|
199
|
+
// mbtiles exist in the data config
|
|
200
|
+
return dataItemId;
|
|
201
|
+
} else {
|
|
202
|
+
if (fromData || !allowMoreData) {
|
|
203
|
+
console.log(
|
|
204
|
+
`ERROR: style "${item.style}" using unknown mbtiles "${mbtiles}"! Skipping...`,
|
|
205
|
+
);
|
|
206
|
+
return undefined;
|
|
207
|
+
} else {
|
|
208
|
+
let id = mbtiles.substr(0, mbtiles.lastIndexOf('.')) || mbtiles;
|
|
209
|
+
while (data[id]) id += '_';
|
|
210
|
+
data[id] = {
|
|
211
|
+
mbtiles: mbtiles,
|
|
212
|
+
};
|
|
213
|
+
return id;
|
|
154
214
|
}
|
|
155
|
-
}
|
|
215
|
+
}
|
|
216
|
+
},
|
|
217
|
+
(font) => {
|
|
218
|
+
if (reportFonts) {
|
|
219
|
+
serving.fonts[font] = true;
|
|
220
|
+
}
|
|
221
|
+
},
|
|
222
|
+
);
|
|
156
223
|
}
|
|
157
224
|
if (success && item.serve_rendered !== false) {
|
|
158
225
|
if (!isLight) {
|
|
159
|
-
startupPromises.push(
|
|
226
|
+
startupPromises.push(
|
|
227
|
+
serve_rendered.add(
|
|
228
|
+
options,
|
|
229
|
+
serving.rendered,
|
|
230
|
+
item,
|
|
231
|
+
id,
|
|
232
|
+
opts.publicUrl,
|
|
160
233
|
(mbtiles) => {
|
|
161
234
|
let mbtilesFile;
|
|
162
235
|
for (const id of Object.keys(data)) {
|
|
@@ -165,8 +238,9 @@ export function server(opts) {
|
|
|
165
238
|
}
|
|
166
239
|
}
|
|
167
240
|
return mbtilesFile;
|
|
168
|
-
}
|
|
169
|
-
|
|
241
|
+
},
|
|
242
|
+
),
|
|
243
|
+
);
|
|
170
244
|
} else {
|
|
171
245
|
item.serve_rendered = false;
|
|
172
246
|
}
|
|
@@ -184,9 +258,9 @@ export function server(opts) {
|
|
|
184
258
|
}
|
|
185
259
|
|
|
186
260
|
startupPromises.push(
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
261
|
+
serve_font(options, serving.fonts).then((sub) => {
|
|
262
|
+
app.use('/', sub);
|
|
263
|
+
}),
|
|
190
264
|
);
|
|
191
265
|
|
|
192
266
|
for (const id of Object.keys(data)) {
|
|
@@ -197,61 +271,65 @@ export function server(opts) {
|
|
|
197
271
|
}
|
|
198
272
|
|
|
199
273
|
startupPromises.push(
|
|
200
|
-
|
|
274
|
+
serve_data.add(options, serving.data, item, id, opts.publicUrl),
|
|
201
275
|
);
|
|
202
276
|
}
|
|
203
277
|
|
|
204
278
|
if (options.serveAllStyles) {
|
|
205
|
-
fs.readdir(options.paths.styles, {withFileTypes: true}, (err, files) => {
|
|
279
|
+
fs.readdir(options.paths.styles, { withFileTypes: true }, (err, files) => {
|
|
206
280
|
if (err) {
|
|
207
281
|
return;
|
|
208
282
|
}
|
|
209
283
|
for (const file of files) {
|
|
210
|
-
if (file.isFile() &&
|
|
211
|
-
path.extname(file.name).toLowerCase() == '.json') {
|
|
284
|
+
if (file.isFile() && path.extname(file.name).toLowerCase() == '.json') {
|
|
212
285
|
const id = path.basename(file.name, '.json');
|
|
213
286
|
const item = {
|
|
214
|
-
style: file.name
|
|
287
|
+
style: file.name,
|
|
215
288
|
};
|
|
216
289
|
addStyle(id, item, false, false);
|
|
217
290
|
}
|
|
218
291
|
}
|
|
219
292
|
});
|
|
220
293
|
|
|
221
|
-
const watcher = chokidar.watch(
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
294
|
+
const watcher = chokidar.watch(
|
|
295
|
+
path.join(options.paths.styles, '*.json'),
|
|
296
|
+
{},
|
|
297
|
+
);
|
|
298
|
+
watcher.on('all', (eventType, filename) => {
|
|
299
|
+
if (filename) {
|
|
300
|
+
const id = path.basename(filename, '.json');
|
|
301
|
+
console.log(`Style "${id}" changed, updating...`);
|
|
302
|
+
|
|
303
|
+
serve_style.remove(serving.styles, id);
|
|
304
|
+
if (!isLight) {
|
|
305
|
+
serve_rendered.remove(serving.rendered, id);
|
|
306
|
+
}
|
|
234
307
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
308
|
+
if (eventType == 'add' || eventType == 'change') {
|
|
309
|
+
const item = {
|
|
310
|
+
style: filename,
|
|
311
|
+
};
|
|
312
|
+
addStyle(id, item, false, false);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
});
|
|
243
316
|
}
|
|
244
317
|
|
|
245
318
|
app.get('/styles.json', (req, res, next) => {
|
|
246
319
|
const result = [];
|
|
247
|
-
const query = req.query.key
|
|
320
|
+
const query = req.query.key
|
|
321
|
+
? `?key=${encodeURIComponent(req.query.key)}`
|
|
322
|
+
: '';
|
|
248
323
|
for (const id of Object.keys(serving.styles)) {
|
|
249
324
|
const styleJSON = serving.styles[id].styleJSON;
|
|
250
325
|
result.push({
|
|
251
326
|
version: styleJSON.version,
|
|
252
327
|
name: styleJSON.name,
|
|
253
328
|
id: id,
|
|
254
|
-
url: `${getPublicUrl(
|
|
329
|
+
url: `${getPublicUrl(
|
|
330
|
+
opts.publicUrl,
|
|
331
|
+
req,
|
|
332
|
+
)}styles/${id}/style.json${query}`,
|
|
255
333
|
});
|
|
256
334
|
}
|
|
257
335
|
res.send(result);
|
|
@@ -266,9 +344,16 @@ export function server(opts) {
|
|
|
266
344
|
} else {
|
|
267
345
|
path = `${type}/${id}`;
|
|
268
346
|
}
|
|
269
|
-
info.tiles = getTileUrls(
|
|
270
|
-
|
|
271
|
-
|
|
347
|
+
info.tiles = getTileUrls(
|
|
348
|
+
req,
|
|
349
|
+
info.tiles,
|
|
350
|
+
path,
|
|
351
|
+
info.format,
|
|
352
|
+
opts.publicUrl,
|
|
353
|
+
{
|
|
354
|
+
pbf: options.pbfAlias,
|
|
355
|
+
},
|
|
356
|
+
);
|
|
272
357
|
arr.push(info);
|
|
273
358
|
}
|
|
274
359
|
return arr;
|
|
@@ -294,40 +379,49 @@ export function server(opts) {
|
|
|
294
379
|
if (template === 'index') {
|
|
295
380
|
if (options.frontPage === false) {
|
|
296
381
|
return;
|
|
297
|
-
} else if (
|
|
298
|
-
options.frontPage
|
|
382
|
+
} else if (
|
|
383
|
+
options.frontPage &&
|
|
384
|
+
options.frontPage.constructor === String
|
|
385
|
+
) {
|
|
299
386
|
templateFile = path.resolve(paths.root, options.frontPage);
|
|
300
387
|
}
|
|
301
388
|
}
|
|
302
|
-
startupPromises.push(
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
const compiled = handlebars.compile(content.toString());
|
|
310
|
-
|
|
311
|
-
app.use(urlPath, (req, res, next) => {
|
|
312
|
-
let data = {};
|
|
313
|
-
if (dataGetter) {
|
|
314
|
-
data = dataGetter(req);
|
|
315
|
-
if (!data) {
|
|
316
|
-
return res.status(404).send('Not found');
|
|
317
|
-
}
|
|
389
|
+
startupPromises.push(
|
|
390
|
+
new Promise((resolve, reject) => {
|
|
391
|
+
fs.readFile(templateFile, (err, content) => {
|
|
392
|
+
if (err) {
|
|
393
|
+
err = new Error(`Template not found: ${err.message}`);
|
|
394
|
+
reject(err);
|
|
395
|
+
return;
|
|
318
396
|
}
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
397
|
+
const compiled = handlebars.compile(content.toString());
|
|
398
|
+
|
|
399
|
+
app.use(urlPath, (req, res, next) => {
|
|
400
|
+
let data = {};
|
|
401
|
+
if (dataGetter) {
|
|
402
|
+
data = dataGetter(req);
|
|
403
|
+
if (!data) {
|
|
404
|
+
return res.status(404).send('Not found');
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
data[
|
|
408
|
+
'server_version'
|
|
409
|
+
] = `${packageJson.name} v${packageJson.version}`;
|
|
410
|
+
data['public_url'] = opts.publicUrl || '/';
|
|
411
|
+
data['is_light'] = isLight;
|
|
412
|
+
data['key_query_part'] = req.query.key
|
|
413
|
+
? `key=${encodeURIComponent(req.query.key)}&`
|
|
414
|
+
: '';
|
|
415
|
+
data['key_query'] = req.query.key
|
|
416
|
+
? `?key=${encodeURIComponent(req.query.key)}`
|
|
417
|
+
: '';
|
|
418
|
+
if (template === 'wmts') res.set('Content-Type', 'text/xml');
|
|
419
|
+
return res.status(200).send(compiled(data));
|
|
420
|
+
});
|
|
421
|
+
resolve();
|
|
327
422
|
});
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
}));
|
|
423
|
+
}),
|
|
424
|
+
);
|
|
331
425
|
};
|
|
332
426
|
|
|
333
427
|
serveTemplate('/$', 'index', (req) => {
|
|
@@ -340,15 +434,23 @@ export function server(opts) {
|
|
|
340
434
|
if (style.serving_rendered) {
|
|
341
435
|
const center = style.serving_rendered.tileJSON.center;
|
|
342
436
|
if (center) {
|
|
343
|
-
style.viewer_hash = `#${center[2]}/${center[1].toFixed(
|
|
437
|
+
style.viewer_hash = `#${center[2]}/${center[1].toFixed(
|
|
438
|
+
5,
|
|
439
|
+
)}/${center[0].toFixed(5)}`;
|
|
344
440
|
|
|
345
441
|
const centerPx = mercator.px([center[0], center[1]], center[2]);
|
|
346
|
-
style.thumbnail = `${center[2]}/${Math.floor(
|
|
442
|
+
style.thumbnail = `${center[2]}/${Math.floor(
|
|
443
|
+
centerPx[0] / 256,
|
|
444
|
+
)}/${Math.floor(centerPx[1] / 256)}.png`;
|
|
347
445
|
}
|
|
348
446
|
|
|
349
447
|
style.xyz_link = getTileUrls(
|
|
350
|
-
|
|
351
|
-
|
|
448
|
+
req,
|
|
449
|
+
style.serving_rendered.tileJSON.tiles,
|
|
450
|
+
`styles/${id}`,
|
|
451
|
+
style.serving_rendered.tileJSON.format,
|
|
452
|
+
opts.publicUrl,
|
|
453
|
+
)[0];
|
|
352
454
|
}
|
|
353
455
|
}
|
|
354
456
|
const data = clone(serving.data || {});
|
|
@@ -357,19 +459,29 @@ export function server(opts) {
|
|
|
357
459
|
const tilejson = data[id].tileJSON;
|
|
358
460
|
const center = tilejson.center;
|
|
359
461
|
if (center) {
|
|
360
|
-
data_.viewer_hash = `#${center[2]}/${center[1].toFixed(
|
|
462
|
+
data_.viewer_hash = `#${center[2]}/${center[1].toFixed(
|
|
463
|
+
5,
|
|
464
|
+
)}/${center[0].toFixed(5)}`;
|
|
361
465
|
}
|
|
362
466
|
data_.is_vector = tilejson.format === 'pbf';
|
|
363
467
|
if (!data_.is_vector) {
|
|
364
468
|
if (center) {
|
|
365
469
|
const centerPx = mercator.px([center[0], center[1]], center[2]);
|
|
366
|
-
data_.thumbnail = `${center[2]}/${Math.floor(
|
|
470
|
+
data_.thumbnail = `${center[2]}/${Math.floor(
|
|
471
|
+
centerPx[0] / 256,
|
|
472
|
+
)}/${Math.floor(centerPx[1] / 256)}.${data_.tileJSON.format}`;
|
|
367
473
|
}
|
|
368
474
|
|
|
369
475
|
data_.xyz_link = getTileUrls(
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
476
|
+
req,
|
|
477
|
+
tilejson.tiles,
|
|
478
|
+
`data/${id}`,
|
|
479
|
+
tilejson.format,
|
|
480
|
+
opts.publicUrl,
|
|
481
|
+
{
|
|
482
|
+
pbf: options.pbfAlias,
|
|
483
|
+
},
|
|
484
|
+
)[0];
|
|
373
485
|
}
|
|
374
486
|
if (data_.filesize) {
|
|
375
487
|
let suffix = 'kB';
|
|
@@ -387,7 +499,7 @@ export function server(opts) {
|
|
|
387
499
|
}
|
|
388
500
|
return {
|
|
389
501
|
styles: Object.keys(styles).length ? styles : null,
|
|
390
|
-
data: Object.keys(data).length ? data : null
|
|
502
|
+
data: Object.keys(data).length ? data : null,
|
|
391
503
|
};
|
|
392
504
|
});
|
|
393
505
|
|
|
@@ -420,7 +532,15 @@ export function server(opts) {
|
|
|
420
532
|
}
|
|
421
533
|
wmts.id = id;
|
|
422
534
|
wmts.name = (serving.styles[id] || serving.rendered[id]).name;
|
|
423
|
-
|
|
535
|
+
if (opts.publicUrl) {
|
|
536
|
+
wmts.baseUrl = opts.publicUrl;
|
|
537
|
+
} else {
|
|
538
|
+
wmts.baseUrl = `${
|
|
539
|
+
req.get('X-Forwarded-Protocol')
|
|
540
|
+
? req.get('X-Forwarded-Protocol')
|
|
541
|
+
: req.protocol
|
|
542
|
+
}://${req.get('host')}/`;
|
|
543
|
+
}
|
|
424
544
|
return wmts;
|
|
425
545
|
});
|
|
426
546
|
|
|
@@ -448,13 +568,17 @@ export function server(opts) {
|
|
|
448
568
|
}
|
|
449
569
|
});
|
|
450
570
|
|
|
451
|
-
const server = app.listen(
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
571
|
+
const server = app.listen(
|
|
572
|
+
process.env.PORT || opts.port,
|
|
573
|
+
process.env.BIND || opts.bind,
|
|
574
|
+
function () {
|
|
575
|
+
let address = this.address().address;
|
|
576
|
+
if (address.indexOf('::') === 0) {
|
|
577
|
+
address = `[${address}]`; // literal IPv6 address
|
|
578
|
+
}
|
|
579
|
+
console.log(`Listening at http://${address}:${this.address().port}/`);
|
|
580
|
+
},
|
|
581
|
+
);
|
|
458
582
|
|
|
459
583
|
// add server.shutdown() to gracefully stop serving
|
|
460
584
|
enableShutdown(server);
|
|
@@ -462,11 +586,15 @@ export function server(opts) {
|
|
|
462
586
|
return {
|
|
463
587
|
app: app,
|
|
464
588
|
server: server,
|
|
465
|
-
startupPromise: startupPromise
|
|
589
|
+
startupPromise: startupPromise,
|
|
466
590
|
};
|
|
467
591
|
}
|
|
468
592
|
|
|
469
|
-
|
|
593
|
+
/**
|
|
594
|
+
*
|
|
595
|
+
* @param opts
|
|
596
|
+
*/
|
|
597
|
+
export function server(opts) {
|
|
470
598
|
const running = start(opts);
|
|
471
599
|
|
|
472
600
|
running.startupPromise.catch((err) => {
|
|
@@ -482,10 +610,6 @@ export const exports = (opts) => {
|
|
|
482
610
|
console.log('Stopping server and reloading config');
|
|
483
611
|
|
|
484
612
|
running.server.shutdown(() => {
|
|
485
|
-
for (const key in require.cache) {
|
|
486
|
-
delete require.cache[key];
|
|
487
|
-
}
|
|
488
|
-
|
|
489
613
|
const restarted = start(opts);
|
|
490
614
|
running.server = restarted.server;
|
|
491
615
|
running.app = restarted.app;
|
|
@@ -493,4 +617,4 @@ export const exports = (opts) => {
|
|
|
493
617
|
});
|
|
494
618
|
|
|
495
619
|
return running;
|
|
496
|
-
}
|
|
620
|
+
}
|