chiitiler 1.20.3 → 1.21.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/dist/render/index.js +27 -17
- package/dist/render/mercator.d.ts +5 -0
- package/dist/render/mercator.js +8 -0
- package/dist/render/rasterize.js +7 -10
- package/dist/server/routes/camera.d.ts +4 -4
- package/dist/server/routes/camera.js +6 -2
- package/dist/server/routes/clip.d.ts +4 -4
- package/dist/server/routes/clip.js +6 -2
- package/dist/server/routes/tiles.d.ts +4 -4
- package/dist/server/routes/tiles.js +6 -2
- package/dist/source/mbtiles.js +12 -6
- package/package.json +1 -4
package/dist/render/index.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import sharp from 'sharp';
|
|
2
|
-
import { SphericalMercator } from '@mapbox/sphericalmercator';
|
|
3
|
-
const mercator = new SphericalMercator();
|
|
4
2
|
import { LRUCache } from 'lru-cache';
|
|
5
3
|
import { renderTile, render } from './rasterize.js';
|
|
4
|
+
import { mercX, mercY, invMercY } from './mercator.js';
|
|
6
5
|
import { getSource } from '../source/index.js';
|
|
7
6
|
const styleCache = new LRUCache({
|
|
8
7
|
max: 5,
|
|
@@ -54,6 +53,10 @@ async function getRenderedTile({ stylejson, z, x, y, tileSize, cache, margin, ex
|
|
|
54
53
|
width: 512,
|
|
55
54
|
height: 512,
|
|
56
55
|
channels: 4,
|
|
56
|
+
// maplibre-native returns premultiplied alpha; tell sharp so it
|
|
57
|
+
// unpremultiplies to straight alpha, otherwise antialiased edges
|
|
58
|
+
// (e.g. white text-halo) come out gray on transparent backgrounds.
|
|
59
|
+
premultiplied: true,
|
|
57
60
|
},
|
|
58
61
|
}).resize(256, 256);
|
|
59
62
|
}
|
|
@@ -63,6 +66,7 @@ async function getRenderedTile({ stylejson, z, x, y, tileSize, cache, margin, ex
|
|
|
63
66
|
width: tileSize,
|
|
64
67
|
height: tileSize,
|
|
65
68
|
channels: 4,
|
|
69
|
+
premultiplied: true,
|
|
66
70
|
},
|
|
67
71
|
});
|
|
68
72
|
}
|
|
@@ -72,6 +76,7 @@ async function getRenderedTile({ stylejson, z, x, y, tileSize, cache, margin, ex
|
|
|
72
76
|
width: tileSize + margin,
|
|
73
77
|
height: tileSize + margin,
|
|
74
78
|
channels: 4,
|
|
79
|
+
premultiplied: true,
|
|
75
80
|
},
|
|
76
81
|
})
|
|
77
82
|
.extract({
|
|
@@ -93,22 +98,23 @@ async function getRenderedTile({ stylejson, z, x, y, tileSize, cache, margin, ex
|
|
|
93
98
|
}
|
|
94
99
|
}
|
|
95
100
|
const calcRenderingParams = (bbox, size) => {
|
|
96
|
-
//
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
const
|
|
100
|
-
const
|
|
101
|
-
const dx =
|
|
102
|
-
const dy =
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
const
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
101
|
+
// bbox width/height as fractions of the whole world ([0, 1] mercator)
|
|
102
|
+
const x1 = mercX(bbox[0]);
|
|
103
|
+
const x2 = mercX(bbox[2]);
|
|
104
|
+
const y1 = mercY(bbox[3]); // north -> smaller y
|
|
105
|
+
const y2 = mercY(bbox[1]);
|
|
106
|
+
const dx = x2 - x1;
|
|
107
|
+
const dy = y2 - y1;
|
|
108
|
+
// maplibre-native's world is 512px wide at zoom 0, so at zoom z it is
|
|
109
|
+
// 512 * 2^z px. The zoom that fits the longer side into `size` px solves
|
|
110
|
+
// 512 * 2^z * max(dx, dy) = size.
|
|
111
|
+
const zoom = Math.log2(size / (512 * Math.max(dx, dy)));
|
|
112
|
+
const width = dx >= dy ? size : Math.ceil((dx / dy) * size);
|
|
113
|
+
const height = dx >= dy ? Math.ceil((dy / dx) * size) : size;
|
|
114
|
+
const center = [
|
|
115
|
+
(bbox[0] + bbox[2]) / 2, // longitude is linear in mercator x
|
|
116
|
+
invMercY((y1 + y2) / 2), // latitude: invert the mercator-space midpoint
|
|
110
117
|
];
|
|
111
|
-
const center = mercator.ll(mercCenter, 25); // latlon
|
|
112
118
|
return { zoom, width, height, center };
|
|
113
119
|
};
|
|
114
120
|
async function getRenderedClip({ stylejson, bbox, size, cache, ext, quality, }) {
|
|
@@ -125,6 +131,8 @@ async function getRenderedClip({ stylejson, bbox, size, cache, ext, quality, })
|
|
|
125
131
|
width,
|
|
126
132
|
height,
|
|
127
133
|
channels: 4,
|
|
134
|
+
// see getRenderedTile: maplibre-native returns premultiplied alpha
|
|
135
|
+
premultiplied: true,
|
|
128
136
|
},
|
|
129
137
|
});
|
|
130
138
|
switch (ext) {
|
|
@@ -152,6 +160,8 @@ async function getRenderedCamera(options) {
|
|
|
152
160
|
width: options.width,
|
|
153
161
|
height: options.height,
|
|
154
162
|
channels: 4,
|
|
163
|
+
// see getRenderedTile: maplibre-native returns premultiplied alpha
|
|
164
|
+
premultiplied: true,
|
|
155
165
|
},
|
|
156
166
|
});
|
|
157
167
|
switch (options.ext) {
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
const DEG2RAD = Math.PI / 180;
|
|
2
|
+
// lon/lat <-> world coords normalized to [0, 1] (Web Mercator).
|
|
3
|
+
// north is small y; antimeridian is x=0/1.
|
|
4
|
+
const mercX = (lon) => (lon + 180) / 360;
|
|
5
|
+
const mercY = (lat) => 0.5 - Math.log(Math.tan(Math.PI / 4 + (lat * DEG2RAD) / 2)) / (2 * Math.PI);
|
|
6
|
+
const invMercX = (x) => x * 360 - 180;
|
|
7
|
+
const invMercY = (y) => (2 * Math.atan(Math.exp((0.5 - y) * 2 * Math.PI)) - Math.PI / 2) / DEG2RAD;
|
|
8
|
+
export { mercX, mercY, invMercX, invMercY };
|
package/dist/render/rasterize.js
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
|
-
import { SphericalMercator } from '@mapbox/sphericalmercator';
|
|
2
1
|
import { getRenderPool } from './pool.js';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
const tileCenter = mercator.ll([px, py], z);
|
|
10
|
-
return tileCenter;
|
|
2
|
+
import { invMercX, invMercY } from './mercator.js';
|
|
3
|
+
function getTileCenter(z, x, y) {
|
|
4
|
+
// tile (x, y) center as a world fraction is (x + 0.5) / 2^z, independent
|
|
5
|
+
// of tile pixel size; convert that back to lon/lat.
|
|
6
|
+
const n = 2 ** z;
|
|
7
|
+
return [invMercX((x + 0.5) / n), invMercY((y + 0.5) / n)];
|
|
11
8
|
}
|
|
12
9
|
async function render(style, renderOptions, cache, mode) {
|
|
13
10
|
const pool = await getRenderPool(style, cache, mode);
|
|
@@ -56,7 +53,7 @@ async function renderTile(style, z, x, y, options) {
|
|
|
56
53
|
zoom: renderingParams.zoom,
|
|
57
54
|
width: renderingParams.width + (options.margin ?? 0),
|
|
58
55
|
height: renderingParams.height + (options.margin ?? 0),
|
|
59
|
-
center: getTileCenter(z, x, y
|
|
56
|
+
center: getTileCenter(z, x, y),
|
|
60
57
|
bearing: 0,
|
|
61
58
|
pitch: 0,
|
|
62
59
|
}, options.cache, tileMode ? 'tile' : 'static');
|
|
@@ -115,8 +115,8 @@ declare function createCameraRouter(options: {
|
|
|
115
115
|
};
|
|
116
116
|
};
|
|
117
117
|
output: "failed to render static image";
|
|
118
|
-
outputFormat: "
|
|
119
|
-
status:
|
|
118
|
+
outputFormat: "text";
|
|
119
|
+
status: 500;
|
|
120
120
|
};
|
|
121
121
|
};
|
|
122
122
|
} & {
|
|
@@ -214,8 +214,8 @@ declare function createCameraRouter(options: {
|
|
|
214
214
|
};
|
|
215
215
|
};
|
|
216
216
|
output: "failed to render static image";
|
|
217
|
-
outputFormat: "
|
|
218
|
-
status:
|
|
217
|
+
outputFormat: "text";
|
|
218
|
+
status: 500;
|
|
219
219
|
} | {
|
|
220
220
|
input: {
|
|
221
221
|
param: {
|
|
@@ -68,7 +68,9 @@ function createCameraRouter(options) {
|
|
|
68
68
|
}
|
|
69
69
|
catch (e) {
|
|
70
70
|
console.error(`render error: ${e}`);
|
|
71
|
-
|
|
71
|
+
// c.text overrides the image/* Content-Type set above; a render
|
|
72
|
+
// failure is a server-side error so respond 500, not 400.
|
|
73
|
+
return c.text('failed to render static image', 500);
|
|
72
74
|
}
|
|
73
75
|
})
|
|
74
76
|
.post('/:zoom/:lat/:lon/:bearing/:pitch/:dimensions_ext', async (c) => {
|
|
@@ -122,7 +124,9 @@ function createCameraRouter(options) {
|
|
|
122
124
|
}
|
|
123
125
|
catch (e) {
|
|
124
126
|
console.error(`render error: ${e}`);
|
|
125
|
-
|
|
127
|
+
// c.text overrides the image/* Content-Type set above; a render
|
|
128
|
+
// failure is a server-side error so respond 500, not 400.
|
|
129
|
+
return c.text('failed to render static image', 500);
|
|
126
130
|
}
|
|
127
131
|
});
|
|
128
132
|
return camera;
|
|
@@ -37,8 +37,8 @@ declare function createClipRouter(options: {
|
|
|
37
37
|
};
|
|
38
38
|
};
|
|
39
39
|
output: "failed to render tile";
|
|
40
|
-
outputFormat: "
|
|
41
|
-
status:
|
|
40
|
+
outputFormat: "text";
|
|
41
|
+
status: 500;
|
|
42
42
|
} | {
|
|
43
43
|
input: {
|
|
44
44
|
param: {
|
|
@@ -95,8 +95,8 @@ declare function createClipRouter(options: {
|
|
|
95
95
|
};
|
|
96
96
|
};
|
|
97
97
|
output: "failed to render tile";
|
|
98
|
-
outputFormat: "
|
|
99
|
-
status:
|
|
98
|
+
outputFormat: "text";
|
|
99
|
+
status: 500;
|
|
100
100
|
} | {
|
|
101
101
|
input: {
|
|
102
102
|
param: {
|
|
@@ -37,7 +37,9 @@ function createClipRouter(options) {
|
|
|
37
37
|
}
|
|
38
38
|
catch (e) {
|
|
39
39
|
console.error(`render error: ${e}`);
|
|
40
|
-
|
|
40
|
+
// c.text overrides the image/* Content-Type set above; a render
|
|
41
|
+
// failure is a server-side error so respond 500, not 400.
|
|
42
|
+
return c.text('failed to render tile', 500);
|
|
41
43
|
}
|
|
42
44
|
})
|
|
43
45
|
.post('/:filename_ext', async (c) => {
|
|
@@ -76,7 +78,9 @@ function createClipRouter(options) {
|
|
|
76
78
|
}
|
|
77
79
|
catch (e) {
|
|
78
80
|
console.error(`render error: ${e}`);
|
|
79
|
-
|
|
81
|
+
// c.text overrides the image/* Content-Type set above; a render
|
|
82
|
+
// failure is a server-side error so respond 500, not 400.
|
|
83
|
+
return c.text('failed to render tile', 500);
|
|
80
84
|
}
|
|
81
85
|
});
|
|
82
86
|
return clip;
|
|
@@ -53,8 +53,8 @@ declare function createTilesRouter(options: {
|
|
|
53
53
|
};
|
|
54
54
|
};
|
|
55
55
|
output: "failed to render tile";
|
|
56
|
-
outputFormat: "
|
|
57
|
-
status:
|
|
56
|
+
outputFormat: "text";
|
|
57
|
+
status: 500;
|
|
58
58
|
} | {
|
|
59
59
|
input: {
|
|
60
60
|
param: {
|
|
@@ -109,8 +109,8 @@ declare function createTilesRouter(options: {
|
|
|
109
109
|
};
|
|
110
110
|
};
|
|
111
111
|
output: "failed to render tile";
|
|
112
|
-
outputFormat: "
|
|
113
|
-
status:
|
|
112
|
+
outputFormat: "text";
|
|
113
|
+
status: 500;
|
|
114
114
|
} | {
|
|
115
115
|
input: {
|
|
116
116
|
param: {
|
|
@@ -45,7 +45,9 @@ function createTilesRouter(options) {
|
|
|
45
45
|
}
|
|
46
46
|
catch (e) {
|
|
47
47
|
console.error(`render error: ${e}`);
|
|
48
|
-
|
|
48
|
+
// c.text overrides the image/* Content-Type set above; a render
|
|
49
|
+
// failure is a server-side error so respond 500, not 400.
|
|
50
|
+
return c.text('failed to render tile', 500);
|
|
49
51
|
}
|
|
50
52
|
})
|
|
51
53
|
.post('/:z/:x/:y_ext', async (c) => {
|
|
@@ -85,7 +87,9 @@ function createTilesRouter(options) {
|
|
|
85
87
|
}
|
|
86
88
|
catch (e) {
|
|
87
89
|
console.error(`render error: ${e}`);
|
|
88
|
-
|
|
90
|
+
// c.text overrides the image/* Content-Type set above; a render
|
|
91
|
+
// failure is a server-side error so respond 500, not 400.
|
|
92
|
+
return c.text('failed to render tile', 500);
|
|
89
93
|
}
|
|
90
94
|
});
|
|
91
95
|
return tiles;
|
package/dist/source/mbtiles.js
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { DatabaseSync } from 'node:sqlite';
|
|
2
2
|
import { unzip } from 'zlib';
|
|
3
3
|
import { LRUCache } from 'lru-cache';
|
|
4
|
+
// A StatementSync is only valid while its DatabaseSync is alive, and node:sqlite
|
|
5
|
+
// does not guarantee the statement keeps the database from being GC'd. Cache the
|
|
6
|
+
// database alongside the statement so both share the entry's lifetime.
|
|
4
7
|
const mbtilesCache = new LRUCache({
|
|
5
8
|
max: 20,
|
|
9
|
+
dispose: ({ db }) => db.close(),
|
|
6
10
|
});
|
|
7
11
|
/**
|
|
8
12
|
* uri = mbtiles://path/to/file.mbtiles/{z}/{x}/{y}
|
|
@@ -11,12 +15,14 @@ async function getMbtilesSource(uri) {
|
|
|
11
15
|
const mbtilesFilepath = uri
|
|
12
16
|
.replace('mbtiles://', '')
|
|
13
17
|
.replace(/\/\d+\/\d+\/\d+$/, '');
|
|
14
|
-
let
|
|
15
|
-
if (
|
|
16
|
-
const db = new
|
|
17
|
-
statement = db.prepare('SELECT tile_data FROM tiles WHERE zoom_level = ? AND tile_column = ? AND tile_row = ?');
|
|
18
|
-
|
|
18
|
+
let entry = mbtilesCache.get(mbtilesFilepath);
|
|
19
|
+
if (entry === undefined) {
|
|
20
|
+
const db = new DatabaseSync(mbtilesFilepath, { readOnly: true });
|
|
21
|
+
const statement = db.prepare('SELECT tile_data FROM tiles WHERE zoom_level = ? AND tile_column = ? AND tile_row = ?');
|
|
22
|
+
entry = { db, statement };
|
|
23
|
+
mbtilesCache.set(mbtilesFilepath, entry);
|
|
19
24
|
}
|
|
25
|
+
const { statement } = entry;
|
|
20
26
|
const [z, x, y] = uri
|
|
21
27
|
.replace(`mbtiles://${mbtilesFilepath}/`, '')
|
|
22
28
|
.split('/');
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "chiitiler",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.21.1",
|
|
5
5
|
"description": "Tiny map rendering server for MapLibre Style Spec",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
@@ -26,7 +26,6 @@
|
|
|
26
26
|
"devDependencies": {
|
|
27
27
|
"@tsconfig/node24": "^24.0.4",
|
|
28
28
|
"@types/autocannon": "^7.12.7",
|
|
29
|
-
"@types/better-sqlite3": "^7.6.13",
|
|
30
29
|
"@types/node": "^25.6.0",
|
|
31
30
|
"@vitest/coverage-v8": "^4.1.4",
|
|
32
31
|
"autocannon": "^8.0.0",
|
|
@@ -40,11 +39,9 @@
|
|
|
40
39
|
"@aws-sdk/client-s3": "^3.1032.0",
|
|
41
40
|
"@google-cloud/storage": "^7.19.0",
|
|
42
41
|
"@hono/node-server": "^2.0.0",
|
|
43
|
-
"@mapbox/sphericalmercator": "^2.0.2",
|
|
44
42
|
"@mapbox/tilebelt": "^2.0.3",
|
|
45
43
|
"@maplibre/maplibre-gl-native": "^6.4.1",
|
|
46
44
|
"@maplibre/maplibre-gl-style-spec": "^24.8.1",
|
|
47
|
-
"better-sqlite3": "12.9.0",
|
|
48
45
|
"commander": "^14.0.3",
|
|
49
46
|
"file-system-cache": "^2.4.7",
|
|
50
47
|
"higuruma": "^0.1.6",
|