chiitiler 1.12.2 → 1.12.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/dist/cache/file.d.ts +7 -0
- package/dist/cache/file.js +33 -0
- package/dist/cache/index.d.ts +12 -0
- package/dist/cache/index.js +9 -0
- package/dist/cache/memory.d.ts +7 -0
- package/dist/cache/memory.js +18 -0
- package/dist/cache/s3.d.ts +8 -0
- package/dist/cache/s3.js +65 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +101 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/main.d.ts +1 -0
- package/dist/main.js +22 -0
- package/dist/render/index.d.ts +25 -0
- package/dist/render/index.js +146 -0
- package/dist/render/pool.d.ts +6 -0
- package/dist/render/pool.js +75 -0
- package/dist/render/rasterize.d.ts +10 -0
- package/dist/render/rasterize.js +66 -0
- package/dist/s3.d.ts +6 -0
- package/dist/s3.js +24 -0
- package/dist/server/debug.d.ts +4 -0
- package/dist/server/debug.js +201 -0
- package/dist/server/index.d.ts +15 -0
- package/dist/server/index.js +187 -0
- package/dist/source/fs.d.ts +3 -0
- package/dist/source/fs.js +15 -0
- package/dist/source/http.d.ts +5 -0
- package/dist/source/http.js +25 -0
- package/dist/source/index.d.ts +10 -0
- package/dist/source/index.js +27 -0
- package/dist/source/mbtiles.d.ts +6 -0
- package/dist/source/mbtiles.js +39 -0
- package/dist/source/pmtiles.d.ts +9 -0
- package/dist/source/pmtiles.js +106 -0
- package/dist/source/s3.d.ts +3 -0
- package/dist/source/s3.js +27 -0
- package/package.json +4 -3
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Cache as FileSystemCache } from 'file-system-cache';
|
|
2
|
+
const fileCache = function (options) {
|
|
3
|
+
const cache = new FileSystemCache({
|
|
4
|
+
basePath: options.dir,
|
|
5
|
+
hash: 'sha1',
|
|
6
|
+
ttl: options.ttl,
|
|
7
|
+
});
|
|
8
|
+
return {
|
|
9
|
+
name: 'file',
|
|
10
|
+
set: async function (key, value) {
|
|
11
|
+
try {
|
|
12
|
+
await cache.set(key, value.toString('hex'));
|
|
13
|
+
}
|
|
14
|
+
catch (e) {
|
|
15
|
+
console.error(e);
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
get: async function (key) {
|
|
19
|
+
try {
|
|
20
|
+
const val = await cache.get(key, undefined);
|
|
21
|
+
if (val === undefined)
|
|
22
|
+
return undefined;
|
|
23
|
+
return Buffer.from(val, 'hex');
|
|
24
|
+
}
|
|
25
|
+
catch (e) {
|
|
26
|
+
console.error(e);
|
|
27
|
+
cache.remove(key);
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
export { fileCache };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/// <reference types="node" resolution-mode="require"/>
|
|
2
|
+
import { memoryCache } from './memory.js';
|
|
3
|
+
import { s3Cache } from './s3.js';
|
|
4
|
+
import { fileCache } from './file.js';
|
|
5
|
+
type Value = Buffer;
|
|
6
|
+
type Cache = {
|
|
7
|
+
name: string;
|
|
8
|
+
get: (key: string) => Promise<Value | undefined>;
|
|
9
|
+
set: (key: string, value: Value) => Promise<void>;
|
|
10
|
+
};
|
|
11
|
+
declare const noneCache: () => Cache;
|
|
12
|
+
export { noneCache, memoryCache, s3Cache, fileCache, type Value, type Cache };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { memoryCache } from './memory.js';
|
|
2
|
+
import { s3Cache } from './s3.js';
|
|
3
|
+
import { fileCache } from './file.js';
|
|
4
|
+
const noneCache = () => ({
|
|
5
|
+
name: 'none',
|
|
6
|
+
get: async () => undefined,
|
|
7
|
+
set: async () => undefined,
|
|
8
|
+
});
|
|
9
|
+
export { noneCache, memoryCache, s3Cache, fileCache };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { LRUCache } from 'lru-cache';
|
|
2
|
+
const memoryCache = function (options) {
|
|
3
|
+
const MEMORY_CACHE = new LRUCache({
|
|
4
|
+
max: options.maxItemCount,
|
|
5
|
+
ttl: options.ttl * 1000,
|
|
6
|
+
});
|
|
7
|
+
return {
|
|
8
|
+
name: 'memory',
|
|
9
|
+
set: async function (key, value) {
|
|
10
|
+
MEMORY_CACHE.set(key, value);
|
|
11
|
+
},
|
|
12
|
+
get: async function (key) {
|
|
13
|
+
const item = MEMORY_CACHE.get(key);
|
|
14
|
+
return item;
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
export { memoryCache };
|
package/dist/cache/s3.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { PutObjectCommand, GetObjectCommand } from '@aws-sdk/client-s3';
|
|
2
|
+
import { getS3Client } from '../s3.js';
|
|
3
|
+
function s3Cache(options) {
|
|
4
|
+
const s3Client = getS3Client({
|
|
5
|
+
region: options.region,
|
|
6
|
+
endpoint: options.endpoint,
|
|
7
|
+
});
|
|
8
|
+
return {
|
|
9
|
+
name: 's3',
|
|
10
|
+
set: async function (key, value) {
|
|
11
|
+
try {
|
|
12
|
+
const cmd = new PutObjectCommand({
|
|
13
|
+
Bucket: options.bucket,
|
|
14
|
+
Key: escapeFileName(key),
|
|
15
|
+
Body: value,
|
|
16
|
+
});
|
|
17
|
+
await s3Client.send(cmd);
|
|
18
|
+
}
|
|
19
|
+
catch (e) {
|
|
20
|
+
console.log(`[error]: ${e}`);
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
get: async function (key) {
|
|
24
|
+
const cmd = new GetObjectCommand({
|
|
25
|
+
Bucket: options.bucket,
|
|
26
|
+
Key: escapeFileName(key),
|
|
27
|
+
});
|
|
28
|
+
try {
|
|
29
|
+
const obj = await s3Client.send(cmd);
|
|
30
|
+
if (obj.Body === undefined)
|
|
31
|
+
return undefined;
|
|
32
|
+
const buf = Buffer.from(await obj.Body.transformToByteArray());
|
|
33
|
+
return buf;
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
// miss or any error
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function escapeFileName(url) {
|
|
43
|
+
return url
|
|
44
|
+
.replace(/\//g, '_') // replace slashes with underscores
|
|
45
|
+
.replace(/\?/g, '-') // replace question marks with dashes
|
|
46
|
+
.replace(/&/g, '-') // replace ampersands with dashes
|
|
47
|
+
.replace(/=/g, '-') // replace equals signs with dashes
|
|
48
|
+
.replace(/%/g, '-') // replace percent signs with dashes
|
|
49
|
+
.replace(/#/g, '-') // replace hash signs with dashes
|
|
50
|
+
.replace(/:/g, '-') // replace colons with dashes
|
|
51
|
+
.replace(/\+/g, '-') // replace plus signs with dashes
|
|
52
|
+
.replace(/ /g, '-') // replace spaces with dashes
|
|
53
|
+
.replace(/</g, '-') // replace less than signs with dashes
|
|
54
|
+
.replace(/>/g, '-') // replace greater than signs with dashes
|
|
55
|
+
.replace(/\*/g, '-') // replace asterisks with dashes
|
|
56
|
+
.replace(/\|/g, '-') // replace vertical bars with dashes
|
|
57
|
+
.replace(/"/g, '-') // replace double quotes with dashes
|
|
58
|
+
.replace(/'/g, '-') // replace single quotes with dashes
|
|
59
|
+
.replace(/\?/g, '-') // replace question marks with dashes
|
|
60
|
+
.replace(/\./g, '-') // replace dots with dashes
|
|
61
|
+
.replace(/,/g, '-') // replace commas with dashes
|
|
62
|
+
.replace(/;/g, '-') // replace semicolons with dashes
|
|
63
|
+
.replace(/\\/g, '-'); // replace backslashes with dashes
|
|
64
|
+
}
|
|
65
|
+
export { s3Cache };
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import process from 'node:process';
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import { initServer } from './server/index.js';
|
|
4
|
+
import * as caches from './cache/index.js';
|
|
5
|
+
function parseCacheStrategy(method, options) {
|
|
6
|
+
// command-line option
|
|
7
|
+
if (method === 'memory')
|
|
8
|
+
return caches.memoryCache({
|
|
9
|
+
ttl: options.cacheTtl,
|
|
10
|
+
maxItemCount: options.memoryCacheMaxItemCount,
|
|
11
|
+
});
|
|
12
|
+
if (method === 'file')
|
|
13
|
+
return caches.fileCache({
|
|
14
|
+
dir: options.fileCacheDir,
|
|
15
|
+
ttl: options.cacheTtl,
|
|
16
|
+
});
|
|
17
|
+
if (method === 's3')
|
|
18
|
+
return caches.s3Cache({
|
|
19
|
+
bucket: options.s3CacheBucket,
|
|
20
|
+
region: options.s3Region,
|
|
21
|
+
endpoint: options.s3Endpoint,
|
|
22
|
+
});
|
|
23
|
+
// command-line is not specified -> try to read from env
|
|
24
|
+
const cacheEnv = process.env.CHIITILER_CACHE_METHOD;
|
|
25
|
+
if (cacheEnv === 'memory')
|
|
26
|
+
return caches.memoryCache({
|
|
27
|
+
ttl: Number(process.env.CHIITILER_CACHE_TTL_SEC ?? '3600'),
|
|
28
|
+
maxItemCount: Number(process.env.CHIITILER_MEMORYCACHE_MAXITEMCOUNT ?? '1000'),
|
|
29
|
+
});
|
|
30
|
+
if (cacheEnv === 'file')
|
|
31
|
+
return caches.fileCache({
|
|
32
|
+
dir: process.env.CHIITILER_FILECACHE_DIR ?? './.cache',
|
|
33
|
+
ttl: Number(process.env.CHIITILER_CACHE_TTL_SEC ?? '3600'),
|
|
34
|
+
});
|
|
35
|
+
if (cacheEnv === 's3')
|
|
36
|
+
return caches.s3Cache({
|
|
37
|
+
bucket: process.env.CHIITILER_S3CACHE_BUCKET ?? '',
|
|
38
|
+
region: process.env.CHIITILER_S3_REGION ?? 'us-east1',
|
|
39
|
+
endpoint: process.env.CHIITILER_S3_ENDPOINT ?? null,
|
|
40
|
+
});
|
|
41
|
+
// undefined or invalid
|
|
42
|
+
return caches.noneCache();
|
|
43
|
+
}
|
|
44
|
+
function parsePort(port) {
|
|
45
|
+
// command-line option
|
|
46
|
+
if (port !== undefined)
|
|
47
|
+
return Number(port);
|
|
48
|
+
// command-line is not specified -> try to read from env
|
|
49
|
+
const portEnv = process.env.CHIITILER_PORT;
|
|
50
|
+
if (portEnv !== undefined)
|
|
51
|
+
return Number(portEnv);
|
|
52
|
+
// undefined or invalid
|
|
53
|
+
return 3000;
|
|
54
|
+
}
|
|
55
|
+
function parseDebug(debug) {
|
|
56
|
+
// command-line option
|
|
57
|
+
if (debug)
|
|
58
|
+
return true;
|
|
59
|
+
// command-line is not specified or false -> try to read from env
|
|
60
|
+
const debugEnv = process.env.CHIITILER_DEBUG;
|
|
61
|
+
if (debugEnv !== undefined)
|
|
62
|
+
return debugEnv === 'true';
|
|
63
|
+
// undefined or invalid
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
export function createProgram() {
|
|
67
|
+
const program = new Command();
|
|
68
|
+
program
|
|
69
|
+
.command('tile-server')
|
|
70
|
+
.option('-c, --cache <type>', 'cache type', 'none')
|
|
71
|
+
.option('-ctl --cache-ttl', 'cache ttl', '3600')
|
|
72
|
+
.option('-mci --memory-cache-max-item-count', 'memory cache max item count', '1000')
|
|
73
|
+
.option('-fcd --file-cache-dir <dir>', 'file cache directory', './.cache')
|
|
74
|
+
.option('-s3r --s3-region <region-name>', 's3 bucket region for get/put', 'us-east1')
|
|
75
|
+
.option('-s3b --s3-cache-bucket <bucket-name>', 's3 cache bucket name', '')
|
|
76
|
+
.option('-s3e --s3-endpoint <url>', 's3 endpoint url', '')
|
|
77
|
+
.option('-p --port <port>', 'port number')
|
|
78
|
+
.option('-D --debug', 'debug mode')
|
|
79
|
+
.action((options) => {
|
|
80
|
+
const serverOptions = {
|
|
81
|
+
cache: parseCacheStrategy(options.cache, {
|
|
82
|
+
cacheTtl: Number(options.cacheTtl),
|
|
83
|
+
memoryCacheMaxItemCount: Number(options.memoryCacheMaxItemCount),
|
|
84
|
+
fileCacheDir: options.fileCacheDir,
|
|
85
|
+
s3CacheBucket: options.s3CacheBucket,
|
|
86
|
+
s3Region: options.s3Region,
|
|
87
|
+
s3Endpoint: options.s3Endpoint,
|
|
88
|
+
}),
|
|
89
|
+
port: parsePort(options.port),
|
|
90
|
+
debug: parseDebug(options.debug),
|
|
91
|
+
};
|
|
92
|
+
if (serverOptions.debug) {
|
|
93
|
+
console.log(`running server: http://localhost:${serverOptions.port}`);
|
|
94
|
+
console.log(`cache method: ${serverOptions.cache.name}`);
|
|
95
|
+
console.log(`debug page: http://localhost:${serverOptions.port}/debug`);
|
|
96
|
+
}
|
|
97
|
+
const { start } = initServer(serverOptions);
|
|
98
|
+
start();
|
|
99
|
+
});
|
|
100
|
+
return program;
|
|
101
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { initServer, type InitServerOptions } from './server/index.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { initServer } from './server/index.js';
|
package/dist/main.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/main.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import cluster from 'node:cluster';
|
|
2
|
+
import { availableParallelism } from 'node:os';
|
|
3
|
+
import process from 'node:process';
|
|
4
|
+
import { createProgram } from './cli.js';
|
|
5
|
+
const program = createProgram();
|
|
6
|
+
const numProcesses = (() => {
|
|
7
|
+
if (process.env.CHIITILER_PROCESSES === undefined)
|
|
8
|
+
return 1;
|
|
9
|
+
const processesEnv = Number(process.env.CHIITILER_PROCESSES);
|
|
10
|
+
if (processesEnv === 0)
|
|
11
|
+
return availableParallelism();
|
|
12
|
+
else
|
|
13
|
+
return processesEnv;
|
|
14
|
+
})();
|
|
15
|
+
if (cluster.isPrimary && numProcesses !== 1) {
|
|
16
|
+
// Fork workers.
|
|
17
|
+
for (let i = 0; i < numProcesses; i++)
|
|
18
|
+
cluster.fork();
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
program.parse(process.argv);
|
|
22
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/// <reference types="node" resolution-mode="require"/>
|
|
2
|
+
import type { StyleSpecification } from '@maplibre/maplibre-gl-style-spec';
|
|
3
|
+
import type { Cache } from '../cache/index.js';
|
|
4
|
+
type RenderTilePipelineOptions = {
|
|
5
|
+
stylejson: string | StyleSpecification;
|
|
6
|
+
z: number;
|
|
7
|
+
x: number;
|
|
8
|
+
y: number;
|
|
9
|
+
tileSize: number;
|
|
10
|
+
cache: Cache;
|
|
11
|
+
margin: number;
|
|
12
|
+
ext: SupportedFormat;
|
|
13
|
+
quality: number;
|
|
14
|
+
};
|
|
15
|
+
type SupportedFormat = 'png' | 'jpeg' | 'jpg' | 'webp';
|
|
16
|
+
declare function getRenderedTileBuffer({ stylejson, z, x, y, tileSize, cache, margin, ext, quality, }: RenderTilePipelineOptions): Promise<Buffer>;
|
|
17
|
+
declare function getRenderedBboxBuffer({ stylejson, bbox, size, cache, ext, quality, }: {
|
|
18
|
+
stylejson: string | StyleSpecification;
|
|
19
|
+
bbox: [number, number, number, number];
|
|
20
|
+
size: number;
|
|
21
|
+
cache: Cache;
|
|
22
|
+
ext: SupportedFormat;
|
|
23
|
+
quality: number;
|
|
24
|
+
}): Promise<Buffer>;
|
|
25
|
+
export { getRenderedTileBuffer, getRenderedBboxBuffer, type SupportedFormat };
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import sharp from 'sharp';
|
|
2
|
+
// @ts-ignore
|
|
3
|
+
import SphericalMercator from '@mapbox/sphericalmercator';
|
|
4
|
+
const mercator = new SphericalMercator();
|
|
5
|
+
import { LRUCache } from 'lru-cache';
|
|
6
|
+
import { renderTile, render } from './rasterize.js';
|
|
7
|
+
import { getSource } from '../source/index.js';
|
|
8
|
+
const styleCache = new LRUCache({
|
|
9
|
+
max: 5,
|
|
10
|
+
});
|
|
11
|
+
async function loadStyle(stylejson, cache) {
|
|
12
|
+
let style;
|
|
13
|
+
if (typeof stylejson === 'string') {
|
|
14
|
+
// url
|
|
15
|
+
const cachedStyle = styleCache.get(stylejson);
|
|
16
|
+
if (cachedStyle !== undefined) {
|
|
17
|
+
// hit-cache
|
|
18
|
+
style = cachedStyle;
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
const styleJsonBuf = await getSource(stylejson, cache);
|
|
22
|
+
if (styleJsonBuf === null) {
|
|
23
|
+
throw new Error('style not found');
|
|
24
|
+
}
|
|
25
|
+
style = JSON.parse(styleJsonBuf.toString());
|
|
26
|
+
styleCache.set(stylejson, style);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
// as stylejson object
|
|
31
|
+
style = stylejson;
|
|
32
|
+
}
|
|
33
|
+
return style;
|
|
34
|
+
}
|
|
35
|
+
async function getRenderedTileBuffer({ stylejson, z, x, y, tileSize, cache, margin, ext, quality, }) {
|
|
36
|
+
const style = await loadStyle(stylejson, cache);
|
|
37
|
+
let pixels;
|
|
38
|
+
pixels = await renderTile(style, z, x, y, {
|
|
39
|
+
tileSize,
|
|
40
|
+
cache,
|
|
41
|
+
margin,
|
|
42
|
+
});
|
|
43
|
+
// hack: tile-margin clip area
|
|
44
|
+
// maplibre-native won't render outer area of meractor
|
|
45
|
+
// so top-end and bottom-end clipped area is special
|
|
46
|
+
const isTopEnd = y === 0;
|
|
47
|
+
const isBottomEnd = y === 2 ** z - 1;
|
|
48
|
+
const topMargin = isTopEnd ? 0 : isBottomEnd ? margin : margin / 2;
|
|
49
|
+
let _sharp;
|
|
50
|
+
if (tileSize === 256 && z === 0) {
|
|
51
|
+
// hack: when tileSize=256, z=0
|
|
52
|
+
// pixlels will be 512x512 so we need to resize to 256x256
|
|
53
|
+
_sharp = sharp(pixels, {
|
|
54
|
+
raw: {
|
|
55
|
+
width: 512,
|
|
56
|
+
height: 512,
|
|
57
|
+
channels: 4,
|
|
58
|
+
},
|
|
59
|
+
}).resize(256, 256);
|
|
60
|
+
}
|
|
61
|
+
else if (margin === 0) {
|
|
62
|
+
_sharp = sharp(pixels, {
|
|
63
|
+
raw: {
|
|
64
|
+
width: tileSize,
|
|
65
|
+
height: tileSize,
|
|
66
|
+
channels: 4,
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
_sharp = sharp(pixels, {
|
|
72
|
+
raw: {
|
|
73
|
+
width: tileSize + margin,
|
|
74
|
+
height: tileSize + margin,
|
|
75
|
+
channels: 4,
|
|
76
|
+
},
|
|
77
|
+
})
|
|
78
|
+
.extract({
|
|
79
|
+
left: margin / 2,
|
|
80
|
+
top: topMargin,
|
|
81
|
+
width: tileSize,
|
|
82
|
+
height: tileSize,
|
|
83
|
+
})
|
|
84
|
+
.resize(tileSize, tileSize);
|
|
85
|
+
}
|
|
86
|
+
let buf;
|
|
87
|
+
switch (ext) {
|
|
88
|
+
case 'png':
|
|
89
|
+
buf = await _sharp.png().toBuffer();
|
|
90
|
+
break;
|
|
91
|
+
case 'jpeg':
|
|
92
|
+
case 'jpg':
|
|
93
|
+
buf = await _sharp.jpeg({ quality }).toBuffer();
|
|
94
|
+
break;
|
|
95
|
+
case 'webp':
|
|
96
|
+
buf = await _sharp.webp({ quality, effort: 0 }).toBuffer();
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
return buf;
|
|
100
|
+
}
|
|
101
|
+
const calcRenderingParams = (bbox, size) => {
|
|
102
|
+
// reference: https://github.com/maptiler/tileserver-gl/blob/cc4b8f7954069fd0e1db731ff07f5349f7b9c8cd/src/serve_rendered.js#L346
|
|
103
|
+
// very hacky and it might be wrong
|
|
104
|
+
let zoom = 25;
|
|
105
|
+
const minCorner = mercator.px([bbox[0], bbox[3]], zoom);
|
|
106
|
+
const maxCorner = mercator.px([bbox[2], bbox[1]], zoom);
|
|
107
|
+
const dx = maxCorner[0] - minCorner[0];
|
|
108
|
+
const dy = maxCorner[1] - minCorner[1];
|
|
109
|
+
zoom -= Math.max(Math.log(dx / size), Math.log(dy / size)) / Math.LN2;
|
|
110
|
+
zoom = Math.max(Math.log(size / 256) / Math.LN2, Math.min(25, zoom)) - 1;
|
|
111
|
+
const width = dx > dy ? size : Math.ceil((dx / dy) * size);
|
|
112
|
+
const height = dx > dy ? Math.ceil((dy / dx) * size) : size;
|
|
113
|
+
const mercCenter = [
|
|
114
|
+
(maxCorner[0] + minCorner[0]) / 2,
|
|
115
|
+
(maxCorner[1] + minCorner[1]) / 2,
|
|
116
|
+
];
|
|
117
|
+
const center = mercator.ll(mercCenter, 25); // latlon
|
|
118
|
+
return { zoom, width, height, center };
|
|
119
|
+
};
|
|
120
|
+
async function getRenderedBboxBuffer({ stylejson, bbox, size, cache, ext, quality, }) {
|
|
121
|
+
const style = await loadStyle(stylejson, cache);
|
|
122
|
+
const { zoom, width, height, center } = calcRenderingParams(bbox, size);
|
|
123
|
+
const pixels = await render(style, {
|
|
124
|
+
zoom,
|
|
125
|
+
width,
|
|
126
|
+
height,
|
|
127
|
+
center,
|
|
128
|
+
}, cache, 'static');
|
|
129
|
+
let _sharp = sharp(pixels, {
|
|
130
|
+
raw: {
|
|
131
|
+
width,
|
|
132
|
+
height,
|
|
133
|
+
channels: 4,
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
switch (ext) {
|
|
137
|
+
case 'png':
|
|
138
|
+
return await _sharp.png().toBuffer();
|
|
139
|
+
case 'jpeg':
|
|
140
|
+
case 'jpg':
|
|
141
|
+
return await _sharp.jpeg({ quality }).toBuffer();
|
|
142
|
+
case 'webp':
|
|
143
|
+
return await _sharp.webp({ quality, effort: 0 }).toBuffer();
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
export { getRenderedTileBuffer, getRenderedBboxBuffer };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import mbgl from '@maplibre/maplibre-gl-native';
|
|
2
|
+
import { Pool } from 'lightning-pool';
|
|
3
|
+
import type { StyleSpecification } from '@maplibre/maplibre-gl-style-spec';
|
|
4
|
+
import type { Cache } from '../cache/index.js';
|
|
5
|
+
declare function getRenderPool(style: StyleSpecification, cache: Cache, mode: 'tile' | 'static'): Promise<Pool<mbgl.Map>>;
|
|
6
|
+
export { getRenderPool };
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import * as path from 'path';
|
|
2
|
+
import mbgl from '@maplibre/maplibre-gl-native';
|
|
3
|
+
import { Pool } from 'lightning-pool';
|
|
4
|
+
import { LRUCache } from 'lru-cache';
|
|
5
|
+
import { getSource } from '../source/index.js';
|
|
6
|
+
const EMPTY_BUFFER = Buffer.alloc(0);
|
|
7
|
+
const TRANSPARENT_BUFFER = {
|
|
8
|
+
// 1x1 transparent images
|
|
9
|
+
png: Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR4nGP4//8/AwAI/AL+p5qgoAAAAABJRU5ErkJggg==', 'base64'),
|
|
10
|
+
webp: Buffer.from('UklGRkAAAABXRUJQVlA4WAoAAAAQAAAAAAAAAAAAQUxQSAIAAAAAAFZQOCAYAAAAMAEAnQEqAQABAAFAJiWkAANwAP789AAA', 'base64'),
|
|
11
|
+
jpeg: Buffer.from('/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBDAQkJCQwLDBgNDRgyIRwhMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjL/wAARCAABAAEDASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD3+iiigD//2Q=='),
|
|
12
|
+
};
|
|
13
|
+
function handleFileExt(uri) {
|
|
14
|
+
// extract extension only, take into account query string or hash
|
|
15
|
+
const basename = path.basename(uri).split(/[?#]/)[0];
|
|
16
|
+
const ext = basename.split('.').pop();
|
|
17
|
+
if (ext === undefined)
|
|
18
|
+
return null;
|
|
19
|
+
const l = ext.toLowerCase();
|
|
20
|
+
if (l === 'jpeg')
|
|
21
|
+
return 'jpg';
|
|
22
|
+
return l;
|
|
23
|
+
}
|
|
24
|
+
const mapPoolCache = new LRUCache({
|
|
25
|
+
max: 10,
|
|
26
|
+
dispose: (pool, key) => {
|
|
27
|
+
pool.close();
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
async function getRenderPool(style, cache, mode) {
|
|
31
|
+
const cacheKey = JSON.stringify(style);
|
|
32
|
+
const pool = mapPoolCache.get(cacheKey);
|
|
33
|
+
if (pool !== undefined)
|
|
34
|
+
return pool;
|
|
35
|
+
const newPool = new Pool({
|
|
36
|
+
create: () => {
|
|
37
|
+
const map = new mbgl.Map({
|
|
38
|
+
request: function (req, callback) {
|
|
39
|
+
const ext = handleFileExt(req.url);
|
|
40
|
+
getSource(req.url, cache)
|
|
41
|
+
.then((buf) => {
|
|
42
|
+
if (buf) {
|
|
43
|
+
callback(undefined, { data: buf });
|
|
44
|
+
}
|
|
45
|
+
else if (ext && TRANSPARENT_BUFFER[ext])
|
|
46
|
+
callback(undefined, {
|
|
47
|
+
data: TRANSPARENT_BUFFER[ext],
|
|
48
|
+
});
|
|
49
|
+
else
|
|
50
|
+
callback(undefined, { data: EMPTY_BUFFER });
|
|
51
|
+
})
|
|
52
|
+
.catch(() => {
|
|
53
|
+
if (ext && TRANSPARENT_BUFFER[ext])
|
|
54
|
+
callback(undefined, {
|
|
55
|
+
data: TRANSPARENT_BUFFER[ext],
|
|
56
|
+
});
|
|
57
|
+
else
|
|
58
|
+
callback(undefined, { data: EMPTY_BUFFER });
|
|
59
|
+
});
|
|
60
|
+
},
|
|
61
|
+
ratio: 1,
|
|
62
|
+
// @ts-ignore
|
|
63
|
+
mode,
|
|
64
|
+
});
|
|
65
|
+
map.load(style);
|
|
66
|
+
return map;
|
|
67
|
+
},
|
|
68
|
+
destroy: (map) => {
|
|
69
|
+
map.release();
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
mapPoolCache.set(cacheKey, newPool);
|
|
73
|
+
return newPool;
|
|
74
|
+
}
|
|
75
|
+
export { getRenderPool };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { RenderOptions } from '@maplibre/maplibre-gl-native';
|
|
2
|
+
import type { StyleSpecification } from '@maplibre/maplibre-gl-style-spec';
|
|
3
|
+
import type { Cache } from '../cache/index.js';
|
|
4
|
+
declare function render(style: StyleSpecification, renderOptions: RenderOptions, cache: Cache, mode: 'tile' | 'static'): Promise<Uint8Array>;
|
|
5
|
+
declare function renderTile(style: StyleSpecification, z: number, x: number, y: number, options: {
|
|
6
|
+
tileSize: number;
|
|
7
|
+
cache: Cache;
|
|
8
|
+
margin?: number;
|
|
9
|
+
}): Promise<Uint8Array>;
|
|
10
|
+
export { renderTile, render };
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// @ts-ignore
|
|
2
|
+
import SphericalMercator from '@mapbox/sphericalmercator';
|
|
3
|
+
import { getRenderPool } from './pool.js';
|
|
4
|
+
function getTileCenter(z, x, y, tileSize = 256) {
|
|
5
|
+
const mercator = new SphericalMercator({
|
|
6
|
+
size: tileSize,
|
|
7
|
+
});
|
|
8
|
+
const px = tileSize / 2 + x * tileSize;
|
|
9
|
+
const py = tileSize / 2 + y * tileSize;
|
|
10
|
+
const tileCenter = mercator.ll([px, py], z);
|
|
11
|
+
return tileCenter;
|
|
12
|
+
}
|
|
13
|
+
async function render(style, renderOptions, cache, mode) {
|
|
14
|
+
const pool = await getRenderPool(style, cache, mode);
|
|
15
|
+
const worker = await pool.acquire();
|
|
16
|
+
const rendered = new Promise((resolve, reject) => {
|
|
17
|
+
worker.render(renderOptions, function (err, buffer) {
|
|
18
|
+
pool.release(worker);
|
|
19
|
+
if (err) {
|
|
20
|
+
reject(err);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
if (buffer === undefined) {
|
|
24
|
+
reject('buffer is undefined');
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
resolve(buffer);
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
return rendered;
|
|
31
|
+
}
|
|
32
|
+
async function renderTile(style, z, x, y, options) {
|
|
33
|
+
/**
|
|
34
|
+
* zoom(renderingOptions): tileSize=256 -> z-1, 512 -> z, 1024 -> z+1...
|
|
35
|
+
* width, height(renderingOptions): equal to tileSize but:
|
|
36
|
+
* when zoom=0, entire globe is rendered in 512x512
|
|
37
|
+
* even when tilesize=256, entire globe is rendered in "512x512 at zoom=0"
|
|
38
|
+
* so we have to set 512 when tilesize=256 and zoom=0, and adjust ratio
|
|
39
|
+
*/
|
|
40
|
+
const renderingParams = options.tileSize === 256 && z === 0
|
|
41
|
+
? {
|
|
42
|
+
zoom: 0,
|
|
43
|
+
height: 512,
|
|
44
|
+
width: 512,
|
|
45
|
+
ratio: 0.5,
|
|
46
|
+
}
|
|
47
|
+
: {
|
|
48
|
+
// offset = 128 -> -1, 256 -> 0, 512 -> 1, 1024 -> 2...
|
|
49
|
+
zoom: z - 1 + Math.log2(options.tileSize / 256),
|
|
50
|
+
height: options.tileSize,
|
|
51
|
+
width: options.tileSize,
|
|
52
|
+
ratio: 1,
|
|
53
|
+
};
|
|
54
|
+
const tileMode = options.margin === 0 &&
|
|
55
|
+
(options.tileSize === 256 || options.tileSize === 512); // mode=tile supports only 256 and 512
|
|
56
|
+
const rendered = await render(style, {
|
|
57
|
+
zoom: renderingParams.zoom,
|
|
58
|
+
width: renderingParams.width + (options.margin ?? 0),
|
|
59
|
+
height: renderingParams.height + (options.margin ?? 0),
|
|
60
|
+
center: getTileCenter(z, x, y, options.tileSize),
|
|
61
|
+
bearing: 0,
|
|
62
|
+
pitch: 0,
|
|
63
|
+
}, options.cache, tileMode ? 'tile' : 'static');
|
|
64
|
+
return rendered;
|
|
65
|
+
}
|
|
66
|
+
export { renderTile, render };
|
package/dist/s3.d.ts
ADDED
package/dist/s3.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { S3Client } from '@aws-sdk/client-s3';
|
|
2
|
+
let s3Client; // singleton
|
|
3
|
+
const getS3Client = function ({ region, endpoint, }) {
|
|
4
|
+
if (s3Client !== undefined)
|
|
5
|
+
return s3Client;
|
|
6
|
+
let s3ClientConfig = {
|
|
7
|
+
region,
|
|
8
|
+
endpoint: endpoint ?? undefined,
|
|
9
|
+
};
|
|
10
|
+
if (process.env.NODE_ENV === 'development') {
|
|
11
|
+
s3ClientConfig = {
|
|
12
|
+
region,
|
|
13
|
+
credentials: {
|
|
14
|
+
accessKeyId: 'minioadmin',
|
|
15
|
+
secretAccessKey: 'minioadmin',
|
|
16
|
+
},
|
|
17
|
+
forcePathStyle: true,
|
|
18
|
+
endpoint: 'http://minio:9000',
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
s3Client = new S3Client(s3ClientConfig);
|
|
22
|
+
return s3Client;
|
|
23
|
+
};
|
|
24
|
+
export { getS3Client };
|