tileserver-gl-light 5.5.0-pre.1 → 5.5.0-pre.3
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/CHANGELOG.md +6 -2
- package/docs/config.rst +41 -1
- package/docs/usage.rst +5 -0
- package/package.json +11 -11
- package/public/resources/elevation-control.js +1 -1
- package/public/resources/maplibre-gl-inspect.js +2823 -2770
- package/public/resources/maplibre-gl-inspect.js.map +1 -1
- package/public/resources/maplibre-gl.css +1 -1
- package/public/resources/maplibre-gl.js +4 -4
- package/public/resources/maplibre-gl.js.map +1 -1
- package/src/main.js +16 -17
- package/src/pmtiles_adapter.js +77 -42
- package/src/promises.js +1 -1
- package/src/render.js +270 -93
- package/src/serve_data.js +8 -12
- package/src/serve_light.js +0 -1
- package/src/serve_rendered.js +375 -205
- package/src/server.js +30 -27
- package/src/utils.js +18 -19
- package/test/fixtures/visual/encoded-path-auto.png +0 -0
- package/test/fixtures/visual/linecap-linejoin-bevel-square.png +0 -0
- package/test/fixtures/visual/linecap-linejoin-round-round.png +0 -0
- package/test/fixtures/visual/path-auto.png +0 -0
- package/test/fixtures/visual/static-bbox.png +0 -0
- package/test/fixtures/visual/static-bearing-pitch.png +0 -0
- package/test/fixtures/visual/static-bearing.png +0 -0
- package/test/fixtures/visual/static-border-global.png +0 -0
- package/test/fixtures/visual/static-lat-lng.png +0 -0
- package/test/fixtures/visual/static-markers.png +0 -0
- package/test/fixtures/visual/static-multiple-paths.png +0 -0
- package/test/fixtures/visual/static-path-border-isolated.png +0 -0
- package/test/fixtures/visual/static-path-border-stroke.png +0 -0
- package/test/fixtures/visual/static-path-latlng.png +0 -0
- package/test/fixtures/visual/static-pixel-ratio-2x.png +0 -0
- package/test/static_images.js +241 -0
package/src/main.js
CHANGED
|
@@ -12,7 +12,8 @@ import fs from 'node:fs';
|
|
|
12
12
|
import fsp from 'node:fs/promises';
|
|
13
13
|
import path from 'path';
|
|
14
14
|
import { fileURLToPath } from 'url';
|
|
15
|
-
import
|
|
15
|
+
import { Readable } from 'node:stream';
|
|
16
|
+
import { pipeline } from 'node:stream/promises';
|
|
16
17
|
import { server } from './server.js';
|
|
17
18
|
import { isValidRemoteUrl } from './utils.js';
|
|
18
19
|
import { openPMtiles, getPMtilesInfo } from './pmtiles_adapter.js';
|
|
@@ -163,7 +164,6 @@ const startWithInputFile = async (inputFile) => {
|
|
|
163
164
|
inputFile = path.resolve(process.cwd(), inputFile);
|
|
164
165
|
inputFilePath = path.dirname(inputFile);
|
|
165
166
|
|
|
166
|
-
// eslint-disable-next-line security/detect-non-literal-fs-filename -- Validating local file from CLI argument
|
|
167
167
|
const inputFileStats = await fsp.stat(inputFile);
|
|
168
168
|
if (!inputFileStats.isFile() || inputFileStats.size === 0) {
|
|
169
169
|
console.log(`ERROR: Not a valid input file: ${inputFile}`);
|
|
@@ -314,7 +314,6 @@ const startWithInputFile = async (inputFile) => {
|
|
|
314
314
|
}
|
|
315
315
|
};
|
|
316
316
|
|
|
317
|
-
// eslint-disable-next-line security/detect-non-literal-fs-filename -- Config path from CLI argument is expected behavior
|
|
318
317
|
fs.stat(path.resolve(opts.config), async (err, stats) => {
|
|
319
318
|
if (err || !stats.isFile() || stats.size === 0) {
|
|
320
319
|
let inputFile;
|
|
@@ -331,7 +330,6 @@ fs.stat(path.resolve(opts.config), async (err, stats) => {
|
|
|
331
330
|
const files = await fsp.readdir(process.cwd());
|
|
332
331
|
for (const filename of files) {
|
|
333
332
|
if (filename.endsWith('.mbtiles') || filename.endsWith('.pmtiles')) {
|
|
334
|
-
// eslint-disable-next-line security/detect-non-literal-fs-filename -- Scanning current directory for tile files
|
|
335
333
|
const inputFilesStats = await fsp.stat(filename);
|
|
336
334
|
if (inputFilesStats.isFile() && inputFilesStats.size > 0) {
|
|
337
335
|
inputFile = filename;
|
|
@@ -346,25 +344,26 @@ fs.stat(path.resolve(opts.config), async (err, stats) => {
|
|
|
346
344
|
const url =
|
|
347
345
|
'https://github.com/maptiler/tileserver-gl/releases/download/v1.3.0/zurich_switzerland.mbtiles';
|
|
348
346
|
const filename = 'zurich_switzerland.mbtiles';
|
|
349
|
-
|
|
347
|
+
|
|
350
348
|
const writer = fs.createWriteStream(filename);
|
|
351
349
|
console.log(`No input file found`);
|
|
352
350
|
console.log(`[DEMO] Downloading sample data (${filename}) from ${url}`);
|
|
353
351
|
|
|
354
352
|
try {
|
|
355
|
-
const response = await
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
);
|
|
353
|
+
const response = await fetch(url);
|
|
354
|
+
|
|
355
|
+
if (!response.ok) {
|
|
356
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// Convert web ReadableStream to Node.js Readable stream and pipe to file
|
|
360
|
+
const nodeStream = Readable.fromWeb(response.body);
|
|
361
|
+
await pipeline(nodeStream, writer);
|
|
362
|
+
|
|
363
|
+
console.log('Download complete');
|
|
364
|
+
startWithInputFile(filename);
|
|
366
365
|
} catch (error) {
|
|
367
|
-
console.error(`Error downloading file: ${error}`);
|
|
366
|
+
console.error(`Error downloading file: ${error.message || error}`);
|
|
368
367
|
}
|
|
369
368
|
}
|
|
370
369
|
}
|
package/src/pmtiles_adapter.js
CHANGED
|
@@ -17,6 +17,7 @@ class S3Source {
|
|
|
17
17
|
* @param {string} [s3Profile] - Optional AWS credential profile name from config.
|
|
18
18
|
* @param {boolean} [configRequestPayer] - Optional flag from config for requester pays buckets.
|
|
19
19
|
* @param {string} [configRegion] - Optional AWS region from config.
|
|
20
|
+
* @param {string} [s3UrlFormat] - Optional S3 URL format from config: 'aws' or 'custom'.
|
|
20
21
|
* @param {boolean} [verbose] - Whether to show verbose logging.
|
|
21
22
|
*/
|
|
22
23
|
constructor(
|
|
@@ -24,24 +25,37 @@ class S3Source {
|
|
|
24
25
|
s3Profile,
|
|
25
26
|
configRequestPayer,
|
|
26
27
|
configRegion,
|
|
28
|
+
s3UrlFormat,
|
|
27
29
|
verbose = false,
|
|
28
30
|
) {
|
|
29
|
-
const parsed = this.parseS3Url(s3Url);
|
|
31
|
+
const parsed = this.parseS3Url(s3Url, s3UrlFormat);
|
|
30
32
|
this.bucket = parsed.bucket;
|
|
31
33
|
this.key = parsed.key;
|
|
32
34
|
this.endpoint = parsed.endpoint;
|
|
33
35
|
this.url = s3Url;
|
|
34
36
|
this.verbose = verbose;
|
|
35
37
|
|
|
36
|
-
//
|
|
38
|
+
// Apply configuration precedence: Config > URL > Default
|
|
39
|
+
// Using || for strings (empty string = not set)
|
|
40
|
+
// Using ?? for booleans (false is valid value)
|
|
37
41
|
const profile = s3Profile || parsed.profile;
|
|
38
|
-
|
|
39
|
-
// Determine requestPayer: Config takes precedence over URL
|
|
40
42
|
this.requestPayer = configRequestPayer ?? parsed.requestPayer;
|
|
41
|
-
|
|
42
|
-
// Determine region: Config takes precedence over URL
|
|
43
43
|
this.region = configRegion || parsed.region;
|
|
44
44
|
|
|
45
|
+
// Log precedence decisions for debugging
|
|
46
|
+
if (verbose >= 3) {
|
|
47
|
+
console.log(`S3 config precedence for ${s3Url}:`);
|
|
48
|
+
console.log(
|
|
49
|
+
` Profile: ${s3Profile ? 'config' : parsed.profile ? 'url' : 'default'} = ${profile || 'none'}`,
|
|
50
|
+
);
|
|
51
|
+
console.log(
|
|
52
|
+
` Region: ${configRegion ? 'config' : parsed.region !== (process.env.AWS_REGION || 'us-east-1') ? 'url' : 'env/default'} = ${this.region}`,
|
|
53
|
+
);
|
|
54
|
+
console.log(
|
|
55
|
+
` RequestPayer: ${configRequestPayer !== undefined ? 'config' : parsed.requestPayer ? 'url' : 'default'} = ${this.requestPayer}`,
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
45
59
|
// Create S3 client
|
|
46
60
|
this.s3Client = this.createS3Client(
|
|
47
61
|
parsed.endpoint,
|
|
@@ -54,60 +68,78 @@ class S3Source {
|
|
|
54
68
|
/**
|
|
55
69
|
* Parses various S3 URL formats into bucket, key, endpoint, region, and profile.
|
|
56
70
|
* @param {string} url - The S3 URL to parse.
|
|
71
|
+
* @param {string} [s3UrlFormat] - Optional format override: 'aws' or 'custom'.
|
|
57
72
|
* @returns {object} - An object containing bucket, key, endpoint, region, and profile.
|
|
58
73
|
* @throws {Error} - Throws an error if the URL format is invalid.
|
|
59
74
|
*/
|
|
60
|
-
parseS3Url(url) {
|
|
61
|
-
//
|
|
75
|
+
parseS3Url(url, s3UrlFormat) {
|
|
76
|
+
// Validate s3UrlFormat if provided
|
|
77
|
+
if (s3UrlFormat && s3UrlFormat !== 'aws' && s3UrlFormat !== 'custom') {
|
|
78
|
+
console.warn(
|
|
79
|
+
`Invalid s3UrlFormat: "${s3UrlFormat}". Must be "aws" or "custom". Using auto-detection.`,
|
|
80
|
+
);
|
|
81
|
+
s3UrlFormat = undefined;
|
|
82
|
+
}
|
|
83
|
+
|
|
62
84
|
let region = process.env.AWS_REGION || 'us-east-1';
|
|
63
85
|
let profile = null;
|
|
64
86
|
let requestPayer = false;
|
|
65
87
|
|
|
66
|
-
|
|
88
|
+
// Parse URL parameters
|
|
89
|
+
const [cleanUrl, queryString] = url.split('?');
|
|
67
90
|
if (queryString) {
|
|
68
91
|
const params = new URLSearchParams(queryString);
|
|
69
|
-
//
|
|
92
|
+
// URL parameters override defaults
|
|
70
93
|
profile = params.get('profile') ?? profile;
|
|
71
|
-
// Update region if provided in url parameters
|
|
72
94
|
region = params.get('region') ?? region;
|
|
73
|
-
|
|
95
|
+
s3UrlFormat = s3UrlFormat ?? params.get('s3UrlFormat'); // Config overrides URL
|
|
96
|
+
|
|
74
97
|
const payerVal = params.get('requestPayer');
|
|
75
98
|
requestPayer = payerVal === 'true' || payerVal === '1';
|
|
76
99
|
}
|
|
77
100
|
|
|
78
|
-
//
|
|
79
|
-
const
|
|
80
|
-
|
|
101
|
+
// Helper to build result object
|
|
102
|
+
const buildResult = (endpoint, bucket, key) => ({
|
|
103
|
+
endpoint: endpoint ? `https://${endpoint}` : null,
|
|
104
|
+
bucket,
|
|
105
|
+
key,
|
|
106
|
+
region,
|
|
107
|
+
profile,
|
|
108
|
+
requestPayer,
|
|
109
|
+
});
|
|
81
110
|
|
|
82
|
-
//
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
bucket: endpointMatch[2],
|
|
89
|
-
key: endpointMatch[3],
|
|
90
|
-
region,
|
|
91
|
-
profile,
|
|
92
|
-
requestPayer,
|
|
93
|
-
};
|
|
94
|
-
}
|
|
111
|
+
// Define patterns based on format
|
|
112
|
+
const patterns = {
|
|
113
|
+
customWithDot: /^s3:\/\/([^/]*\.[^/]+)\/([^/]+)\/(.+)$/, // Auto-detect: requires dot
|
|
114
|
+
customForced: /^s3:\/\/([^/]+)\/([^/]+)\/(.+)$/, // Explicit: no dot required
|
|
115
|
+
aws: /^s3:\/\/([^/]+)\/(.+)$/,
|
|
116
|
+
};
|
|
95
117
|
|
|
96
|
-
//
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
if (
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
118
|
+
// Match based on s3UrlFormat or auto-detect
|
|
119
|
+
let match;
|
|
120
|
+
|
|
121
|
+
if (s3UrlFormat === 'custom') {
|
|
122
|
+
match = cleanUrl.match(patterns.customForced);
|
|
123
|
+
if (match) return buildResult(match[1], match[2], match[3]);
|
|
124
|
+
} else if (s3UrlFormat === 'aws') {
|
|
125
|
+
match = cleanUrl.match(patterns.aws);
|
|
126
|
+
if (match) return buildResult(null, match[1], match[2]);
|
|
127
|
+
} else {
|
|
128
|
+
// Auto-detection: try custom (with dot) first, then AWS
|
|
129
|
+
match = cleanUrl.match(patterns.customWithDot);
|
|
130
|
+
if (match) return buildResult(match[1], match[2], match[3]);
|
|
131
|
+
|
|
132
|
+
match = cleanUrl.match(patterns.aws);
|
|
133
|
+
if (match) return buildResult(null, match[1], match[2]);
|
|
108
134
|
}
|
|
109
135
|
|
|
110
|
-
throw new Error(
|
|
136
|
+
throw new Error(
|
|
137
|
+
`Invalid S3 URL format: ${url}\n` +
|
|
138
|
+
`Expected formats:\n` +
|
|
139
|
+
` AWS S3: s3://bucket-name/path/to/file.pmtiles\n` +
|
|
140
|
+
` Custom endpoint: s3://endpoint.com/bucket/path/to/file.pmtiles\n` +
|
|
141
|
+
`Use s3UrlFormat parameter to override auto-detection if needed.`,
|
|
142
|
+
);
|
|
111
143
|
}
|
|
112
144
|
|
|
113
145
|
/**
|
|
@@ -282,6 +314,7 @@ async function readFileBytes(fd, buffer, offset) {
|
|
|
282
314
|
* @param {string} [s3Profile] - Optional AWS credential profile name.
|
|
283
315
|
* @param {boolean} [requestPayer] - Optional flag for requester pays buckets.
|
|
284
316
|
* @param {string} [s3Region] - Optional AWS region.
|
|
317
|
+
* @param {string} [s3UrlFormat] - Optional S3 URL format: 'aws' or 'custom'.
|
|
285
318
|
* @param {boolean} [verbose] - Whether to show verbose logging.
|
|
286
319
|
* @returns {PMTiles} - A PMTiles instance.
|
|
287
320
|
*/
|
|
@@ -290,6 +323,7 @@ export function openPMtiles(
|
|
|
290
323
|
s3Profile,
|
|
291
324
|
requestPayer,
|
|
292
325
|
s3Region,
|
|
326
|
+
s3UrlFormat,
|
|
293
327
|
verbose = 0,
|
|
294
328
|
) {
|
|
295
329
|
let pmtiles = undefined;
|
|
@@ -303,6 +337,7 @@ export function openPMtiles(
|
|
|
303
337
|
s3Profile,
|
|
304
338
|
requestPayer,
|
|
305
339
|
s3Region,
|
|
340
|
+
s3UrlFormat,
|
|
306
341
|
verbose,
|
|
307
342
|
);
|
|
308
343
|
pmtiles = new PMTiles(source);
|
|
@@ -316,7 +351,7 @@ export function openPMtiles(
|
|
|
316
351
|
if (verbose >= 2) {
|
|
317
352
|
console.log(`Opening PMTiles from local file: ${filePath}`);
|
|
318
353
|
}
|
|
319
|
-
|
|
354
|
+
|
|
320
355
|
const fd = fs.openSync(filePath, 'r');
|
|
321
356
|
const source = new PMTilesFileSource(fd);
|
|
322
357
|
pmtiles = new PMTiles(source);
|