chiitiler 1.15.0 → 1.16.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.
@@ -8,8 +8,6 @@ type InitServerOptions = {
8
8
  };
9
9
  type InitializedServer = {
10
10
  app: Hono;
11
- tiles: Hono;
12
- clip: Hono;
13
11
  start: () => void;
14
12
  };
15
13
  declare function initServer(options: InitServerOptions): InitializedServer;
@@ -1,230 +1,43 @@
1
1
  import { Hono } from 'hono/quick';
2
- import { stream } from 'hono/streaming';
3
2
  import { serve } from '@hono/node-server';
4
- import { validateStyleMin, } from '@maplibre/maplibre-gl-style-spec';
5
3
  import { getDebugPage, getEditorgPage } from './debug.js';
6
- import { getRenderedTile, getRenderedBbox, } from '../render/index.js';
7
- function isValidStylejson(stylejson) {
8
- return validateStyleMin(stylejson).length === 0;
9
- }
10
- function isValidXyz(x, y, z) {
11
- if (x < 0 || y < 0 || z < 0)
12
- return false;
13
- if (x >= 2 ** z || y >= 2 ** z)
14
- return false;
15
- return true;
16
- }
17
- function isSupportedFormat(ext) {
18
- return ['png', 'jpeg', 'jpg', 'webp'].includes(ext);
19
- }
4
+ import { createClipRouter } from './routes/clip.js';
5
+ import { createTilesRouter } from './routes/tiles.js';
6
+ import { createCameraRouter } from './routes/camera.js';
20
7
  function initServer(options) {
21
- const tiles = new Hono()
22
- .get('/:z/:x/:y_ext', async (c) => {
23
- const url = c.req.query('url');
24
- if (url === undefined)
25
- return c.body('url is required', 400);
26
- // path params
27
- const z = Number(c.req.param('z'));
28
- const x = Number(c.req.param('x'));
29
- let [_y, ext] = c.req.param('y_ext').split('.');
30
- const y = Number(_y);
31
- if (!isValidXyz(x, y, z))
32
- return c.body('invalid xyz', 400);
33
- if (!isSupportedFormat(ext))
34
- return c.body('invalid format', 400);
35
- // query params
36
- const tileSize = Number(c.req.query('tileSize') ?? 512);
37
- const quality = Number(c.req.query('quality') ?? 100);
38
- const margin = Number(c.req.query('margin') ?? 0);
39
- c.header('Content-Type', `image/${ext}`);
40
- try {
41
- const sharp = await getRenderedTile({
42
- stylejson: url,
43
- z,
44
- x,
45
- y,
46
- tileSize,
47
- cache: options.cache,
48
- margin,
49
- ext,
50
- quality,
51
- });
52
- if (options.stream) {
53
- // stream mode
54
- return stream(c, async (stream) => {
55
- for await (const chunk of sharp) {
56
- stream.write(chunk);
57
- }
58
- });
59
- }
60
- else {
61
- const buf = await sharp.toBuffer();
62
- return c.body(buf);
63
- }
64
- }
65
- catch (e) {
66
- console.error(`render error: ${e}`);
67
- return c.body('failed to render tile', 400);
68
- }
69
- })
70
- .post('/:z/:x/:y_ext', async (c) => {
71
- // body
72
- const { style } = await c.req.json();
73
- if (!isValidStylejson(style))
74
- return c.body('invalid stylejson', 400);
75
- // path params
76
- const z = Number(c.req.param('z'));
77
- const x = Number(c.req.param('x'));
78
- let [_y, ext] = c.req.param('y_ext').split('.');
79
- const y = Number(_y);
80
- if (!isValidXyz(x, y, z))
81
- return c.body('invalid xyz', 400);
82
- if (!isSupportedFormat(ext))
83
- return c.body('invalid format', 400);
84
- // query params
85
- const tileSize = Number(c.req.query('tileSize') ?? 512);
86
- const quality = Number(c.req.query('quality') ?? 100);
87
- const margin = Number(c.req.query('margin') ?? 0);
88
- c.header('Content-Type', `image/${ext}`);
89
- try {
90
- const sharp = await getRenderedTile({
91
- stylejson: style,
92
- z,
93
- x,
94
- y,
95
- tileSize,
96
- cache: options.cache,
97
- margin,
98
- ext,
99
- quality,
100
- });
101
- if (options.stream) {
102
- // stream mode
103
- return stream(c, async (stream) => {
104
- for await (const chunk of sharp) {
105
- stream.write(chunk);
106
- }
107
- });
108
- }
109
- else {
110
- const buf = await sharp.toBuffer();
111
- return c.body(buf);
112
- }
113
- }
114
- catch (e) {
115
- console.error(`render error: ${e}`);
116
- return c.body('failed to render tile', 400);
117
- }
118
- });
119
- const clip = new Hono()
120
- .get('/:filename_ext', async (c) => {
121
- // path params
122
- const [filename, ext] = c.req.param('filename_ext').split('.');
123
- if (filename !== 'clip')
124
- return c.body('not found', 404);
125
- if (!isSupportedFormat(ext))
126
- return c.body('invalid format', 400);
127
- // query params
128
- const bbox = c.req.query('bbox'); // ?bbox=minx,miny,maxx,maxy
129
- if (bbox === undefined)
130
- return c.body('bbox is required', 400);
131
- const [minx, miny, maxx, maxy] = bbox.split(',').map(Number);
132
- if (minx >= maxx || miny >= maxy)
133
- return c.body('invalid bbox', 400);
134
- const url = c.req.query('url');
135
- if (url === undefined)
136
- return c.body('url is required', 400);
137
- const quality = Number(c.req.query('quality') ?? 100);
138
- const size = Number(c.req.query('size') ?? 1024);
139
- c.header('Content-Type', `image/${ext}`);
140
- try {
141
- const sharp = await getRenderedBbox({
142
- stylejson: url,
143
- bbox: [minx, miny, maxx, maxy],
144
- size,
145
- cache: options.cache,
146
- ext,
147
- quality,
148
- });
149
- if (options.stream) {
150
- // stream mode
151
- return stream(c, async (stream) => {
152
- for await (const chunk of sharp) {
153
- stream.write(chunk);
154
- }
155
- });
156
- }
157
- else {
158
- const buf = await sharp.toBuffer();
159
- return c.body(buf);
160
- }
161
- }
162
- catch (e) {
163
- console.error(`render error: ${e}`);
164
- return c.body('failed to render tile', 400);
165
- }
166
- })
167
- .post('/:filename_ext', async (c) => {
168
- // body
169
- const { style } = await c.req.json();
170
- if (!isValidStylejson(style))
171
- return c.body('invalid stylejson', 400);
172
- // path params
173
- const [filename, ext] = c.req.param('filename_ext').split('.');
174
- if (filename !== 'clip')
175
- return c.body('not found', 404);
176
- if (!isSupportedFormat(ext))
177
- return c.body('invalid format', 400);
178
- // query params
179
- const bbox = c.req.query('bbox'); // ?bbox=minx,miny,maxx,maxy
180
- if (bbox === undefined)
181
- return c.body('bbox is required', 400);
182
- const [minx, miny, maxx, maxy] = bbox.split(',').map(Number);
183
- if (minx >= maxx || miny >= maxy)
184
- return c.body('invalid bbox', 400);
185
- const quality = Number(c.req.query('quality') ?? 100);
186
- const size = Number(c.req.query('size') ?? 1024);
187
- c.header('Content-Type', `image/${ext}`);
188
- try {
189
- const sharp = await getRenderedBbox({
190
- stylejson: style,
191
- bbox: [minx, miny, maxx, maxy],
192
- size,
193
- cache: options.cache,
194
- ext,
195
- quality,
196
- });
197
- if (options.stream) {
198
- // stream mode
199
- return stream(c, async (stream) => {
200
- for await (const chunk of sharp) {
201
- stream.write(chunk);
202
- }
203
- });
204
- }
205
- else {
206
- const buf = await sharp.toBuffer();
207
- return c.body(buf);
208
- }
209
- }
210
- catch (e) {
211
- console.error(`render error: ${e}`);
212
- return c.body('failed to render tile', 400);
213
- }
214
- });
215
8
  const hono = new Hono();
216
9
  if (options.debug) {
217
10
  hono.get('/debug', getDebugPage);
218
11
  hono.get('/editor', getEditorgPage);
219
12
  }
220
13
  hono.get('/health', (c) => c.text('OK'));
221
- hono.route('/tiles', tiles);
222
- hono.route('/', clip);
14
+ hono.route('/static', createCameraRouter({
15
+ cache: options.cache,
16
+ stream: options.stream,
17
+ }));
18
+ hono.route('/tiles', createTilesRouter({
19
+ cache: options.cache,
20
+ stream: options.stream,
21
+ }));
22
+ hono.route('/', createClipRouter({
23
+ cache: options.cache,
24
+ stream: options.stream,
25
+ }));
223
26
  return {
224
27
  app: hono,
225
- tiles,
226
- clip,
227
- start: () => serve({ port: options.port, fetch: hono.fetch }),
28
+ start: () => {
29
+ const server = serve({ port: options.port, fetch: hono.fetch });
30
+ process.on('SIGINT', () => {
31
+ console.log('shutting down server...');
32
+ server.close();
33
+ process.exit(0);
34
+ });
35
+ process.on('SIGTERM', () => {
36
+ console.log('shutting down server...');
37
+ server.close();
38
+ process.exit(0);
39
+ });
40
+ },
228
41
  };
229
42
  }
230
43
  export { initServer };
@@ -0,0 +1,36 @@
1
+ import { Cache } from '../../cache/index.js';
2
+ declare function createCameraRouter(options: {
3
+ cache: Cache;
4
+ stream: boolean;
5
+ }): import("hono/hono-base").HonoBase<import("hono/types").BlankEnv, {
6
+ "/:camera/:dimensions_ext": {
7
+ $get: {
8
+ input: {
9
+ param: {
10
+ camera: string;
11
+ } & {
12
+ dimensions_ext: string;
13
+ };
14
+ };
15
+ output: {};
16
+ outputFormat: string;
17
+ status: import("hono/utils/http-status").StatusCode;
18
+ };
19
+ };
20
+ } & {
21
+ "/:camera/:dimensions_ext": {
22
+ $post: {
23
+ input: {
24
+ param: {
25
+ camera: string;
26
+ } & {
27
+ dimensions_ext: string;
28
+ };
29
+ };
30
+ output: {};
31
+ outputFormat: string;
32
+ status: import("hono/utils/http-status").StatusCode;
33
+ };
34
+ };
35
+ }, "/", "/:camera/:dimensions_ext">;
36
+ export { createCameraRouter };
@@ -0,0 +1,128 @@
1
+ import { Hono } from 'hono';
2
+ import { stream } from 'hono/streaming';
3
+ import { isSupportedFormat, isValidCamera, isValidDimensions, isValidStylejson, } from '../utils.js';
4
+ import { getRenderedImage } from '../../render/index.js';
5
+ function createCameraRouter(options) {
6
+ const camera = new Hono()
7
+ .get('/:camera/:dimensions_ext', async (c) => {
8
+ // path params
9
+ const camera = c.req
10
+ .param('camera')
11
+ .match(/([\d.]+),([\d.]+),([\d.]+)(?:@(\d+)(?:,(\d+))?)?/);
12
+ const [_dimensions, ext] = c.req.param('dimensions_ext').split('.');
13
+ const dimensions = _dimensions.match(/(\d+)x(\d+)?/);
14
+ if (!camera || !isValidCamera(camera))
15
+ return c.body('invalid camera', 400);
16
+ if (!dimensions || !isValidDimensions(dimensions))
17
+ return c.body('invalid dimensions', 400);
18
+ if (!isSupportedFormat(ext))
19
+ return c.body('invalid format', 400);
20
+ const [, _lon, _lat, _zoom, _bearing, _pitch] = camera;
21
+ const [, _width, _height] = dimensions;
22
+ const lat = Number(_lat);
23
+ const lon = Number(_lon);
24
+ const zoom = Number(_zoom);
25
+ const bearing = Number(_bearing ?? 0);
26
+ const pitch = Number(_pitch ?? 0);
27
+ const height = Number(_height);
28
+ const width = Number(_width);
29
+ // query params
30
+ const url = c.req.query('url');
31
+ if (url === undefined)
32
+ return c.body('url is required', 400);
33
+ const quality = Number(c.req.query('quality') ?? 100);
34
+ c.header('Content-Type', `image/${ext}`);
35
+ try {
36
+ const sharp = await getRenderedImage({
37
+ stylejson: url,
38
+ cache: options.cache,
39
+ ext,
40
+ quality,
41
+ center: [lon, lat],
42
+ zoom,
43
+ bearing,
44
+ pitch,
45
+ height,
46
+ width,
47
+ });
48
+ if (options.stream) {
49
+ // stream mode
50
+ return stream(c, async (stream) => {
51
+ for await (const chunk of sharp) {
52
+ stream.write(chunk);
53
+ }
54
+ });
55
+ }
56
+ else {
57
+ const buf = await sharp.toBuffer();
58
+ return c.body(buf);
59
+ }
60
+ }
61
+ catch (e) {
62
+ console.error(`render error: ${e}`);
63
+ return c.body('failed to render static image', 400);
64
+ }
65
+ })
66
+ .post('/:camera/:dimensions_ext', async (c) => {
67
+ // body
68
+ const { style } = await c.req.json();
69
+ if (!isValidStylejson(style))
70
+ return c.body('invalid stylejson', 400);
71
+ // path params
72
+ const camera = c.req
73
+ .param('camera')
74
+ .match(/([\d.]+),([\d.]+),([\d.]+)(?:@(\d+)(?:,(\d+))?)?/);
75
+ const [_dimensions, ext] = c.req.param('dimensions_ext').split('.');
76
+ const dimensions = _dimensions.match(/(\d+)x(\d+)?/);
77
+ if (!camera || !isValidCamera(camera))
78
+ return c.body('invalid camera', 400);
79
+ if (!dimensions || !isValidDimensions(dimensions))
80
+ return c.body('invalid dimensions', 400);
81
+ if (!isSupportedFormat(ext))
82
+ return c.body('invalid format', 400);
83
+ const [, _lon, _lat, _zoom, _bearing, _pitch] = camera;
84
+ const [, _width, _height] = dimensions;
85
+ const lat = Number(_lat);
86
+ const lon = Number(_lon);
87
+ const zoom = Number(_zoom);
88
+ const bearing = Number(_bearing ?? 0);
89
+ const pitch = Number(_pitch ?? 0);
90
+ const height = Number(_height);
91
+ const width = Number(_width);
92
+ // query params
93
+ const quality = Number(c.req.query('quality') ?? 100);
94
+ c.header('Content-Type', `image/${ext}`);
95
+ try {
96
+ const sharp = await getRenderedImage({
97
+ stylejson: style,
98
+ cache: options.cache,
99
+ ext,
100
+ quality,
101
+ center: [lon, lat],
102
+ zoom,
103
+ bearing,
104
+ pitch,
105
+ height,
106
+ width,
107
+ });
108
+ if (options.stream) {
109
+ // stream mode
110
+ return stream(c, async (stream) => {
111
+ for await (const chunk of sharp) {
112
+ stream.write(chunk);
113
+ }
114
+ });
115
+ }
116
+ else {
117
+ const buf = await sharp.toBuffer();
118
+ return c.body(buf);
119
+ }
120
+ }
121
+ catch (e) {
122
+ console.error(`render error: ${e}`);
123
+ return c.body('failed to render static image', 400);
124
+ }
125
+ });
126
+ return camera;
127
+ }
128
+ export { createCameraRouter };
@@ -0,0 +1,32 @@
1
+ import { Cache } from '../../cache/index.js';
2
+ declare function createClipRouter(options: {
3
+ cache: Cache;
4
+ stream: boolean;
5
+ }): import("hono/hono-base").HonoBase<import("hono/types").BlankEnv, {
6
+ "/:filename_ext": {
7
+ $get: {
8
+ input: {
9
+ param: {
10
+ filename_ext: string;
11
+ };
12
+ };
13
+ output: {};
14
+ outputFormat: string;
15
+ status: import("hono/utils/http-status").StatusCode;
16
+ };
17
+ };
18
+ } & {
19
+ "/:filename_ext": {
20
+ $post: {
21
+ input: {
22
+ param: {
23
+ filename_ext: string;
24
+ };
25
+ };
26
+ output: {};
27
+ outputFormat: string;
28
+ status: import("hono/utils/http-status").StatusCode;
29
+ };
30
+ };
31
+ }, "/", "/:filename_ext">;
32
+ export { createClipRouter };
@@ -0,0 +1,104 @@
1
+ import { Hono } from 'hono';
2
+ import { stream } from 'hono/streaming';
3
+ import { isSupportedFormat, isValidStylejson } from '../utils.js';
4
+ import { getRenderedBbox } from '../../render/index.js';
5
+ function createClipRouter(options) {
6
+ const clip = new Hono()
7
+ .get('/:filename_ext', async (c) => {
8
+ // path params
9
+ const [filename, ext] = c.req.param('filename_ext').split('.');
10
+ if (filename !== 'clip')
11
+ return c.body('not found', 404);
12
+ if (!isSupportedFormat(ext))
13
+ return c.body('invalid format', 400);
14
+ // query params
15
+ const bbox = c.req.query('bbox'); // ?bbox=minx,miny,maxx,maxy
16
+ if (bbox === undefined)
17
+ return c.body('bbox is required', 400);
18
+ const [minx, miny, maxx, maxy] = bbox.split(',').map(Number);
19
+ if (minx >= maxx || miny >= maxy)
20
+ return c.body('invalid bbox', 400);
21
+ const url = c.req.query('url');
22
+ if (url === undefined)
23
+ return c.body('url is required', 400);
24
+ const quality = Number(c.req.query('quality') ?? 100);
25
+ const size = Number(c.req.query('size') ?? 1024);
26
+ c.header('Content-Type', `image/${ext}`);
27
+ try {
28
+ const sharp = await getRenderedBbox({
29
+ stylejson: url,
30
+ bbox: [minx, miny, maxx, maxy],
31
+ size,
32
+ cache: options.cache,
33
+ ext,
34
+ quality,
35
+ });
36
+ if (options.stream) {
37
+ // stream mode
38
+ return stream(c, async (stream) => {
39
+ for await (const chunk of sharp) {
40
+ stream.write(chunk);
41
+ }
42
+ });
43
+ }
44
+ else {
45
+ const buf = await sharp.toBuffer();
46
+ return c.body(buf);
47
+ }
48
+ }
49
+ catch (e) {
50
+ console.error(`render error: ${e}`);
51
+ return c.body('failed to render tile', 400);
52
+ }
53
+ })
54
+ .post('/:filename_ext', async (c) => {
55
+ // body
56
+ const { style } = await c.req.json();
57
+ if (!isValidStylejson(style))
58
+ return c.body('invalid stylejson', 400);
59
+ // path params
60
+ const [filename, ext] = c.req.param('filename_ext').split('.');
61
+ if (filename !== 'clip')
62
+ return c.body('not found', 404);
63
+ if (!isSupportedFormat(ext))
64
+ return c.body('invalid format', 400);
65
+ // query params
66
+ const bbox = c.req.query('bbox'); // ?bbox=minx,miny,maxx,maxy
67
+ if (bbox === undefined)
68
+ return c.body('bbox is required', 400);
69
+ const [minx, miny, maxx, maxy] = bbox.split(',').map(Number);
70
+ if (minx >= maxx || miny >= maxy)
71
+ return c.body('invalid bbox', 400);
72
+ const quality = Number(c.req.query('quality') ?? 100);
73
+ const size = Number(c.req.query('size') ?? 1024);
74
+ c.header('Content-Type', `image/${ext}`);
75
+ try {
76
+ const sharp = await getRenderedBbox({
77
+ stylejson: style,
78
+ bbox: [minx, miny, maxx, maxy],
79
+ size,
80
+ cache: options.cache,
81
+ ext,
82
+ quality,
83
+ });
84
+ if (options.stream) {
85
+ // stream mode
86
+ return stream(c, async (stream) => {
87
+ for await (const chunk of sharp) {
88
+ stream.write(chunk);
89
+ }
90
+ });
91
+ }
92
+ else {
93
+ const buf = await sharp.toBuffer();
94
+ return c.body(buf);
95
+ }
96
+ }
97
+ catch (e) {
98
+ console.error(`render error: ${e}`);
99
+ return c.body('failed to render tile', 400);
100
+ }
101
+ });
102
+ return clip;
103
+ }
104
+ export { createClipRouter };
@@ -0,0 +1,40 @@
1
+ import { Cache } from '../../cache/index.js';
2
+ declare function createTilesRouter(options: {
3
+ cache: Cache;
4
+ stream: boolean;
5
+ }): import("hono/hono-base").HonoBase<import("hono/types").BlankEnv, {
6
+ "/:z/:x/:y_ext": {
7
+ $get: {
8
+ input: {
9
+ param: {
10
+ z: string;
11
+ } & {
12
+ x: string;
13
+ } & {
14
+ y_ext: string;
15
+ };
16
+ };
17
+ output: {};
18
+ outputFormat: string;
19
+ status: import("hono/utils/http-status").StatusCode;
20
+ };
21
+ };
22
+ } & {
23
+ "/:z/:x/:y_ext": {
24
+ $post: {
25
+ input: {
26
+ param: {
27
+ z: string;
28
+ } & {
29
+ x: string;
30
+ } & {
31
+ y_ext: string;
32
+ };
33
+ };
34
+ output: {};
35
+ outputFormat: string;
36
+ status: import("hono/utils/http-status").StatusCode;
37
+ };
38
+ };
39
+ }, "/", "/:z/:x/:y_ext">;
40
+ export { createTilesRouter };