chiitiler 1.16.0 → 1.18.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/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # chiitiler – Tiny MapLibre Server
1
+ # chiitiler – Lightweight Raster Tile Server for MapLibre Style
2
2
 
3
3
  ![GitHub Release](https://img.shields.io/github/v/release/Kanahiro/chiitiler?label=ghcr.io/kanahiro/chiitiler)
4
4
  ![Unit Tests](https://img.shields.io/github/actions/workflow/status/Kanahiro/chiitiler/test:unit.yml?label=unit%20tests)
@@ -58,7 +58,7 @@ cdk/ # AWS CDK deployment project
58
58
 
59
59
  ### Requirements
60
60
 
61
- - Node.js 20.10.0 or newer (`.node-version` is provided).
61
+ - Node.js 24.12.0 or newer (`.node-version` is provided).
62
62
  - System dependencies to support `sharp` (see [Dockerfile](./Dockerfile) for reference).
63
63
 
64
64
  ### Run From Source
@@ -104,7 +104,7 @@ Volumes mount `localdata/` and `.cache/` so test assets and cached source data p
104
104
  | ------ | ------------ | ----------- |
105
105
  | GET/POST | `/tiles/{z}/{x}/{y}.{ext}` | Render a raster tile (`png`, `jpeg`, `jpg`, `webp`). |
106
106
  | GET/POST | `/clip.{ext}` | Render a bounding box image (`png`, `jpeg`, `jpg`, `webp`). |
107
- | GET/POST | `/static/{lon},{lat},{zoom}[@{bearing}][,{pitch}]/{width}x{height}.{ext}` | Render a static image (`png`, `jpeg`, `jpg`, `webp`). |
107
+ | GET/POST | `/camera/{zoom}/{lat}/{lon}/{bearing}/{pitch}/{width}x{height}.{ext}` | Render a static image (`png`, `jpeg`, `jpg`, `webp`). |
108
108
  | GET | `/debug` | Style explorer UI (requires debug mode). |
109
109
  | GET | `/editor` | Lightweight style editor (requires debug mode). |
110
110
 
@@ -119,10 +119,6 @@ Volumes mount `localdata/` and `.cache/` so test assets and cached source data p
119
119
 
120
120
  POST requests accept the style object directly in the JSON body (`{ "style": { ... } }`).
121
121
 
122
- ### Streaming Responses
123
-
124
- Enable streaming (Sharp pipeline without buffering) by setting `CHIITILER_STREAM_MODE=true` or passing `--stream`.
125
-
126
122
  ## CLI Reference
127
123
 
128
124
  Chiitiler exposes a single command: `tile-server`.
@@ -138,7 +134,7 @@ npx tsx src/main.ts tile-server --help
138
134
  | `--memory-cache-max-item-count <n>` | Max items in memory cache. | `CHIITILER_MEMORYCACHE_MAXITEMCOUNT` | `1000` |
139
135
  | `--file-cache-dir <dir>` | Disk cache directory. | `CHIITILER_FILECACHE_DIR` | `./.cache` |
140
136
  | `--s3-cache-bucket <name>` | S3 bucket for caching. | `CHIITILER_S3CACHE_BUCKET` | `""` |
141
- | `--s3-region <region>` | S3 region used for requests. | `CHIITILER_S3_REGION` | `us-east1` |
137
+ | `--s3-region <region>` | S3 region used for requests. | `CHIITILER_S3_REGION` | `us-east-1` |
142
138
  | `--s3-endpoint <url>` | S3-compatible endpoint. | `CHIITILER_S3_ENDPOINT` | `""` |
143
139
  | `--s3-force-path-style` | Force path-style requests. | `CHIITILER_S3_FORCE_PATH_STYLE` (`true/false`) | `false` |
144
140
  | `--gcs-cache-bucket <name>` | GCS bucket for caching. | `CHIITILER_GCS_CACHE_BUCKET` | `""` |
@@ -147,7 +143,6 @@ npx tsx src/main.ts tile-server --help
147
143
  | `--gcs-cache-prefix <prefix>` | GCS object prefix. | `CHIITILER_GCS_CACHE_PREFIX` | `""` |
148
144
  | `--gcs-api-endpoint <url>` | Custom GCS endpoint. | `CHIITILER_GCS_API_ENDPOINT` | `""` |
149
145
  | `--port <number>` | HTTP listen port. | `CHIITILER_PORT` | `3000` |
150
- | `--stream` | Enable streaming mode. | `CHIITILER_STREAM_MODE` | `false` |
151
146
  | `--debug` | Enable debug UI routes. | `CHIITILER_DEBUG` | `false` |
152
147
 
153
148
  Set `CHIITILER_PROCESSES` to control clustering (`0` uses all CPUs). When `>1`, the primary process forks workers that all share the same cache adapter.
@@ -200,7 +195,6 @@ Benchmark scenarios and recent measurements live in [BENCHMARK.md](./BENCHMARK.m
200
195
  Chiitiler is also published as an npm library. Core helpers return Sharp instances or encoded buffers so you can integrate the renderer into other pipelines.
201
196
 
202
197
  ```ts
203
- import { createWriteStream } from 'node:fs';
204
198
  import {
205
199
  getRenderedTileBuffer,
206
200
  getRenderedBboxBuffer,
@@ -236,12 +230,12 @@ const clip = await getRenderedBboxBuffer({
236
230
 
237
231
  const image = await getRenderedImageBuffer({
238
232
  stylejson: 'file://localdata/style.json',
239
- lat: 123.45,
240
- lon: 67.89,
233
+ center: [139.69, 35.68],
241
234
  zoom: 10,
242
235
  bearing: 180,
243
236
  pitch: 60,
244
- size: 1024,
237
+ width: 1024,
238
+ height: 1024,
245
239
  ext: 'png',
246
240
  quality: 95,
247
241
  cache,
@@ -271,12 +265,12 @@ const bboxStream = await getRenderedBboxStream({
271
265
 
272
266
  const imageStream = await getRenderedImageStream({
273
267
  stylejson: 'file://localdata/style.json',
274
- lat: 123.4,
275
- lon: 34.5,
268
+ center: [139.69, 35.68],
276
269
  zoom: 10,
277
270
  bearing: 180,
278
271
  pitch: 60,
279
- size: 1024,
272
+ width: 1024,
273
+ height: 1024,
280
274
  ext: 'png',
281
275
  quality: 95,
282
276
  cache,
@@ -285,9 +279,7 @@ const imageStream = await getRenderedImageStream({
285
279
 
286
280
  ## Deployment
287
281
 
288
- - **AWS CDK** – The [`cdk/`](./cdk) directory contains an AWS CDK app for provisioning Chiitiler on ECS/Fargate. See `cdk/README.md` for stack details.
289
- - **Docker** – The provided `Dockerfile` installs runtime dependencies for `sharp` and exposes `tile-server` as the default entrypoint.
290
- - **Bench setups** – `docker-compose.yml` provisions MinIO, Fake GCS, and sample data for local smoke tests.
282
+ The [`cdk/`](./cdk) directory contains an AWS CDK app for running Chiitiler on Lambda with Web Adapter.
291
283
 
292
284
  ## Architecture
293
285
 
@@ -310,5 +302,5 @@ graph LR
310
302
 
311
303
  sources --> cache --> render --> server --/tiles/z/x/y--> png/webp/jpg
312
304
 
313
- cache <--get/set--> memory/file/S3
305
+ cache <--get/set--> memory/file/s3/gcs
314
306
  ```
package/dist/cli.js CHANGED
@@ -44,7 +44,7 @@ function parseCacheStrategy(method, options) {
44
44
  if (cacheEnv === 's3')
45
45
  return caches.s3Cache({
46
46
  bucket: process.env.CHIITILER_S3CACHE_BUCKET ?? '',
47
- region: process.env.CHIITILER_S3_REGION ?? 'us-east1',
47
+ region: process.env.CHIITILER_S3_REGION ?? 'us-east-1',
48
48
  endpoint: process.env.CHIITILER_S3_ENDPOINT,
49
49
  forcePathStyle: process.env.CHIITILER_S3_FORCE_PATH_STYLE === 'true',
50
50
  });
@@ -81,17 +81,6 @@ function parseDebug(debug) {
81
81
  // undefined or invalid
82
82
  return false;
83
83
  }
84
- function parseStream(stream) {
85
- // command-line option
86
- if (stream)
87
- return true;
88
- // command-line is not specified or false -> try to read from env
89
- const streamEnv = process.env.CHIITILER_STREAM_MODE;
90
- if (streamEnv !== undefined)
91
- return streamEnv === 'true';
92
- // undefined or invalid
93
- return false;
94
- }
95
84
  export function createProgram() {
96
85
  const program = new Command();
97
86
  program
@@ -100,7 +89,7 @@ export function createProgram() {
100
89
  .option('-ctl --cache-ttl', 'cache ttl', '3600')
101
90
  .option('-mci --memory-cache-max-item-count', 'memory cache max item count', '1000')
102
91
  .option('-fcd --file-cache-dir <dir>', 'file cache directory', './.cache')
103
- .option('-s3r --s3-region <region-name>', 's3 bucket region for get/put', 'us-east1')
92
+ .option('-s3r --s3-region <region-name>', 's3 bucket region for get/put', 'us-east-1')
104
93
  .option('-s3b --s3-cache-bucket <bucket-name>', 's3 cache bucket name', '')
105
94
  .option('-s3e --s3-endpoint <url>', 's3 endpoint url', '')
106
95
  .option('-3p --s3-force-path-style', 's3 force path style', '')
@@ -110,7 +99,6 @@ export function createProgram() {
110
99
  .option('-gcsp --gcs-cache-prefix <prefix>', 'gcs cache prefix', '')
111
100
  .option('-gcse --gcs-api-endpoint <api-endpoint>', 'gcs api endpoint', '')
112
101
  .option('-p --port <port>', 'port number')
113
- .option('-r --stream', 'stream mode')
114
102
  .option('-D --debug', 'debug mode')
115
103
  .action((options) => {
116
104
  const serverOptions = {
@@ -130,14 +118,12 @@ export function createProgram() {
130
118
  }),
131
119
  port: parsePort(options.port),
132
120
  debug: parseDebug(options.debug),
133
- stream: parseStream(options.stream),
134
121
  };
135
122
  if (serverOptions.debug) {
136
123
  console.log(`running server: http://localhost:${serverOptions.port}`);
137
124
  console.log(`cache method: ${serverOptions.cache.name}`);
138
125
  console.log(`debug page: http://localhost:${serverOptions.port}/debug`);
139
126
  console.log(`editor page: http://localhost:${serverOptions.port}/editor`);
140
- console.log('stream mode:', serverOptions.stream ? 'enabled' : 'disabled');
141
127
  }
142
128
  const { start } = initServer(serverOptions);
143
129
  start();
@@ -1,4 +1,4 @@
1
1
  import { Context } from 'hono';
2
2
  declare function getDebugPage(c: Context): Response;
3
- declare function getEditorgPage(c: Context): Response;
4
- export { getDebugPage, getEditorgPage };
3
+ declare function getEditorPage(c: Context): Response;
4
+ export { getDebugPage, getEditorPage };
@@ -64,7 +64,7 @@ function getDebugPage(c) {
64
64
  </body>
65
65
  </html>`);
66
66
  }
67
- function getEditorgPage(c) {
67
+ function getEditorPage(c) {
68
68
  return c.html(`<!DOCTYPE html>
69
69
  <html>
70
70
  <head>
@@ -198,4 +198,4 @@ function getEditorgPage(c) {
198
198
  </body>
199
199
  </html>`);
200
200
  }
201
- export { getDebugPage, getEditorgPage };
201
+ export { getDebugPage, getEditorPage };
@@ -4,7 +4,6 @@ type InitServerOptions = {
4
4
  cache: Cache;
5
5
  port: number;
6
6
  debug: boolean;
7
- stream: boolean;
8
7
  };
9
8
  type InitializedServer = {
10
9
  app: Hono;
@@ -1,6 +1,6 @@
1
1
  import { Hono } from 'hono/quick';
2
2
  import { serve } from '@hono/node-server';
3
- import { getDebugPage, getEditorgPage } from './debug.js';
3
+ import { getDebugPage, getEditorPage } from './debug.js';
4
4
  import { createClipRouter } from './routes/clip.js';
5
5
  import { createTilesRouter } from './routes/tiles.js';
6
6
  import { createCameraRouter } from './routes/camera.js';
@@ -8,20 +8,17 @@ function initServer(options) {
8
8
  const hono = new Hono();
9
9
  if (options.debug) {
10
10
  hono.get('/debug', getDebugPage);
11
- hono.get('/editor', getEditorgPage);
11
+ hono.get('/editor', getEditorPage);
12
12
  }
13
13
  hono.get('/health', (c) => c.text('OK'));
14
- hono.route('/static', createCameraRouter({
14
+ hono.route('/camera', createCameraRouter({
15
15
  cache: options.cache,
16
- stream: options.stream,
17
16
  }));
18
17
  hono.route('/tiles', createTilesRouter({
19
18
  cache: options.cache,
20
- stream: options.stream,
21
19
  }));
22
20
  hono.route('/', createClipRouter({
23
21
  cache: options.cache,
24
- stream: options.stream,
25
22
  }));
26
23
  return {
27
24
  app: hono,
@@ -1,36 +1,241 @@
1
1
  import { Cache } from '../../cache/index.js';
2
2
  declare function createCameraRouter(options: {
3
3
  cache: Cache;
4
- stream: boolean;
5
4
  }): import("hono/hono-base").HonoBase<import("hono/types").BlankEnv, {
6
- "/:camera/:dimensions_ext": {
5
+ "/:zoom/:lat/:lon/:bearing/:pitch/:dimensions_ext": {
7
6
  $get: {
8
7
  input: {
9
8
  param: {
10
- camera: string;
9
+ zoom: string;
10
+ } & {
11
+ lat: string;
12
+ } & {
13
+ lon: string;
14
+ } & {
15
+ bearing: string;
16
+ } & {
17
+ pitch: string;
18
+ } & {
19
+ dimensions_ext: string;
20
+ };
21
+ };
22
+ output: "invalid format";
23
+ outputFormat: "body";
24
+ status: 400;
25
+ } | {
26
+ input: {
27
+ param: {
28
+ zoom: string;
29
+ } & {
30
+ lat: string;
31
+ } & {
32
+ lon: string;
33
+ } & {
34
+ bearing: string;
35
+ } & {
36
+ pitch: string;
37
+ } & {
38
+ dimensions_ext: string;
39
+ };
40
+ };
41
+ output: "url is required";
42
+ outputFormat: "body";
43
+ status: 400;
44
+ } | {
45
+ input: {
46
+ param: {
47
+ zoom: string;
48
+ } & {
49
+ lat: string;
50
+ } & {
51
+ lon: string;
52
+ } & {
53
+ bearing: string;
54
+ } & {
55
+ pitch: string;
56
+ } & {
57
+ dimensions_ext: string;
58
+ };
59
+ };
60
+ output: Uint8Array<ArrayBuffer>;
61
+ outputFormat: "body";
62
+ status: import("hono/utils/http-status").ContentfulStatusCode;
63
+ } | {
64
+ input: {
65
+ param: {
66
+ zoom: string;
67
+ } & {
68
+ lat: string;
69
+ } & {
70
+ lon: string;
71
+ } & {
72
+ bearing: string;
73
+ } & {
74
+ pitch: string;
75
+ } & {
76
+ dimensions_ext: string;
77
+ };
78
+ };
79
+ output: "invalid dimensions";
80
+ outputFormat: "body";
81
+ status: 400;
82
+ } | {
83
+ input: {
84
+ param: {
85
+ zoom: string;
86
+ } & {
87
+ lat: string;
88
+ } & {
89
+ lon: string;
90
+ } & {
91
+ bearing: string;
92
+ } & {
93
+ pitch: string;
94
+ } & {
95
+ dimensions_ext: string;
96
+ };
97
+ };
98
+ output: "invalid camera";
99
+ outputFormat: "body";
100
+ status: 400;
101
+ } | {
102
+ input: {
103
+ param: {
104
+ zoom: string;
105
+ } & {
106
+ lat: string;
107
+ } & {
108
+ lon: string;
109
+ } & {
110
+ bearing: string;
111
+ } & {
112
+ pitch: string;
11
113
  } & {
12
114
  dimensions_ext: string;
13
115
  };
14
116
  };
15
- output: {};
16
- outputFormat: string;
17
- status: import("hono/utils/http-status").StatusCode;
117
+ output: "failed to render static image";
118
+ outputFormat: "body";
119
+ status: 400;
18
120
  };
19
121
  };
20
122
  } & {
21
- "/:camera/:dimensions_ext": {
123
+ "/:zoom/:lat/:lon/:bearing/:pitch/:dimensions_ext": {
22
124
  $post: {
23
125
  input: {
24
126
  param: {
25
- camera: string;
127
+ zoom: string;
128
+ } & {
129
+ lat: string;
130
+ } & {
131
+ lon: string;
132
+ } & {
133
+ bearing: string;
134
+ } & {
135
+ pitch: string;
136
+ } & {
137
+ dimensions_ext: string;
138
+ };
139
+ };
140
+ output: "invalid format";
141
+ outputFormat: "body";
142
+ status: 400;
143
+ } | {
144
+ input: {
145
+ param: {
146
+ zoom: string;
147
+ } & {
148
+ lat: string;
149
+ } & {
150
+ lon: string;
151
+ } & {
152
+ bearing: string;
153
+ } & {
154
+ pitch: string;
155
+ } & {
156
+ dimensions_ext: string;
157
+ };
158
+ };
159
+ output: Uint8Array<ArrayBuffer>;
160
+ outputFormat: "body";
161
+ status: import("hono/utils/http-status").ContentfulStatusCode;
162
+ } | {
163
+ input: {
164
+ param: {
165
+ zoom: string;
166
+ } & {
167
+ lat: string;
168
+ } & {
169
+ lon: string;
170
+ } & {
171
+ bearing: string;
172
+ } & {
173
+ pitch: string;
174
+ } & {
175
+ dimensions_ext: string;
176
+ };
177
+ };
178
+ output: "invalid dimensions";
179
+ outputFormat: "body";
180
+ status: 400;
181
+ } | {
182
+ input: {
183
+ param: {
184
+ zoom: string;
185
+ } & {
186
+ lat: string;
187
+ } & {
188
+ lon: string;
189
+ } & {
190
+ bearing: string;
191
+ } & {
192
+ pitch: string;
193
+ } & {
194
+ dimensions_ext: string;
195
+ };
196
+ };
197
+ output: "invalid camera";
198
+ outputFormat: "body";
199
+ status: 400;
200
+ } | {
201
+ input: {
202
+ param: {
203
+ zoom: string;
204
+ } & {
205
+ lat: string;
206
+ } & {
207
+ lon: string;
208
+ } & {
209
+ bearing: string;
210
+ } & {
211
+ pitch: string;
212
+ } & {
213
+ dimensions_ext: string;
214
+ };
215
+ };
216
+ output: "failed to render static image";
217
+ outputFormat: "body";
218
+ status: 400;
219
+ } | {
220
+ input: {
221
+ param: {
222
+ zoom: string;
223
+ } & {
224
+ lat: string;
225
+ } & {
226
+ lon: string;
227
+ } & {
228
+ bearing: string;
229
+ } & {
230
+ pitch: string;
26
231
  } & {
27
232
  dimensions_ext: string;
28
233
  };
29
234
  };
30
- output: {};
31
- outputFormat: string;
32
- status: import("hono/utils/http-status").StatusCode;
235
+ output: "invalid stylejson";
236
+ outputFormat: "body";
237
+ status: 400;
33
238
  };
34
239
  };
35
- }, "/", "/:camera/:dimensions_ext">;
240
+ }, "/", "/:zoom/:lat/:lon/:bearing/:pitch/:dimensions_ext">;
36
241
  export { createCameraRouter };
@@ -1,31 +1,49 @@
1
1
  import { Hono } from 'hono';
2
- import { stream } from 'hono/streaming';
3
- import { isSupportedFormat, isValidCamera, isValidDimensions, isValidStylejson, } from '../utils.js';
2
+ import { isSupportedFormat, isValidStylejson } from '../utils.js';
4
3
  import { getRenderedImage } from '../../render/index.js';
4
+ function isValidCamera({ zoom, lat, lon, bearing, pitch, }) {
5
+ if (Number.isNaN(lat) || lat < -90 || lat > 90)
6
+ return false;
7
+ if (Number.isNaN(lon) || lon < -180 || lon > 180)
8
+ return false;
9
+ if (Number.isNaN(zoom) || zoom < 0 || zoom > 24)
10
+ return false;
11
+ if (Number.isNaN(bearing) || bearing < 0 || bearing > 360)
12
+ return false;
13
+ if (Number.isNaN(pitch) || pitch < 0 || pitch > 180)
14
+ return false;
15
+ return true;
16
+ }
17
+ function isValidDimensions({ width, height, }) {
18
+ return !Number.isNaN(width) && !Number.isNaN(height);
19
+ }
5
20
  function createCameraRouter(options) {
6
21
  const camera = new Hono()
7
- .get('/:camera/:dimensions_ext', async (c) => {
22
+ .get('/:zoom/:lat/:lon/:bearing/:pitch/:dimensions_ext', async (c) => {
8
23
  // path params
9
- const camera = c.req
10
- .param('camera')
11
- .match(/([\d.]+),([\d.]+),([\d.]+)(?:@(\d+)(?:,(\d+))?)?/);
24
+ const { zoom, lat, lon, bearing, pitch } = c.req.param();
12
25
  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
26
  if (!isSupportedFormat(ext))
19
27
  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);
28
+ const dimensions = _dimensions.split('x').map(Number);
29
+ const [width, height] = dimensions;
30
+ const _lat = Number(lat);
31
+ const _lon = Number(lon);
32
+ const _zoom = Number(zoom);
33
+ const _bearing = Number(bearing);
34
+ const _pitch = Number(pitch);
35
+ const _height = Number(height);
36
+ const _width = Number(width);
37
+ if (!isValidDimensions({ width: _width, height: _height }))
38
+ return c.body('invalid dimensions', 400);
39
+ if (!isValidCamera({
40
+ zoom: _zoom,
41
+ lat: _lat,
42
+ lon: _lon,
43
+ bearing: _bearing,
44
+ pitch: _pitch,
45
+ }))
46
+ return c.body('invalid camera', 400);
29
47
  // query params
30
48
  const url = c.req.query('url');
31
49
  if (url === undefined)
@@ -38,57 +56,50 @@ function createCameraRouter(options) {
38
56
  cache: options.cache,
39
57
  ext,
40
58
  quality,
41
- center: [lon, lat],
42
- zoom,
43
- bearing,
44
- pitch,
45
- height,
46
- width,
59
+ center: [_lon, _lat],
60
+ zoom: _zoom,
61
+ bearing: _bearing,
62
+ pitch: _pitch,
63
+ height: _height,
64
+ width: _width,
47
65
  });
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
- }
66
+ const buf = await sharp.toBuffer();
67
+ return c.body(buf);
60
68
  }
61
69
  catch (e) {
62
70
  console.error(`render error: ${e}`);
63
71
  return c.body('failed to render static image', 400);
64
72
  }
65
73
  })
66
- .post('/:camera/:dimensions_ext', async (c) => {
74
+ .post('/:zoom/:lat/:lon/:bearing/:pitch/:dimensions_ext', async (c) => {
67
75
  // body
68
76
  const { style } = await c.req.json();
69
77
  if (!isValidStylejson(style))
70
78
  return c.body('invalid stylejson', 400);
71
79
  // path params
72
- const camera = c.req
73
- .param('camera')
74
- .match(/([\d.]+),([\d.]+),([\d.]+)(?:@(\d+)(?:,(\d+))?)?/);
80
+ const { zoom, lat, lon, bearing, pitch } = c.req.param();
75
81
  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
82
  if (!isSupportedFormat(ext))
82
83
  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);
84
+ const dimensions = _dimensions.split('x').map(Number);
85
+ const [width, height] = dimensions;
86
+ const _lat = Number(lat);
87
+ const _lon = Number(lon);
88
+ const _zoom = Number(zoom);
89
+ const _bearing = Number(bearing);
90
+ const _pitch = Number(pitch);
91
+ const _height = Number(height);
92
+ const _width = Number(width);
93
+ if (!isValidDimensions({ width: _width, height: _height }))
94
+ return c.body('invalid dimensions', 400);
95
+ if (!isValidCamera({
96
+ zoom: _zoom,
97
+ lat: _lat,
98
+ lon: _lon,
99
+ bearing: _bearing,
100
+ pitch: _pitch,
101
+ }))
102
+ return c.body('invalid camera', 400);
92
103
  // query params
93
104
  const quality = Number(c.req.query('quality') ?? 100);
94
105
  c.header('Content-Type', `image/${ext}`);
@@ -98,25 +109,15 @@ function createCameraRouter(options) {
98
109
  cache: options.cache,
99
110
  ext,
100
111
  quality,
101
- center: [lon, lat],
102
- zoom,
103
- bearing,
104
- pitch,
105
- height,
106
- width,
112
+ center: [_lon, _lat],
113
+ zoom: _zoom,
114
+ bearing: _bearing,
115
+ pitch: _pitch,
116
+ height: _height,
117
+ width: _width,
107
118
  });
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
- }
119
+ const buf = await sharp.toBuffer();
120
+ return c.body(buf);
120
121
  }
121
122
  catch (e) {
122
123
  console.error(`render error: ${e}`);
@@ -1,7 +1,6 @@
1
1
  import { Cache } from '../../cache/index.js';
2
2
  declare function createClipRouter(options: {
3
3
  cache: Cache;
4
- stream: boolean;
5
4
  }): import("hono/hono-base").HonoBase<import("hono/types").BlankEnv, {
6
5
  "/:filename_ext": {
7
6
  $get: {
@@ -10,9 +9,63 @@ declare function createClipRouter(options: {
10
9
  filename_ext: string;
11
10
  };
12
11
  };
13
- output: {};
14
- outputFormat: string;
15
- status: import("hono/utils/http-status").StatusCode;
12
+ output: "invalid format";
13
+ outputFormat: "body";
14
+ status: 400;
15
+ } | {
16
+ input: {
17
+ param: {
18
+ filename_ext: string;
19
+ };
20
+ };
21
+ output: "url is required";
22
+ outputFormat: "body";
23
+ status: 400;
24
+ } | {
25
+ input: {
26
+ param: {
27
+ filename_ext: string;
28
+ };
29
+ };
30
+ output: Uint8Array<ArrayBuffer>;
31
+ outputFormat: "body";
32
+ status: import("hono/utils/http-status").ContentfulStatusCode;
33
+ } | {
34
+ input: {
35
+ param: {
36
+ filename_ext: string;
37
+ };
38
+ };
39
+ output: "failed to render tile";
40
+ outputFormat: "body";
41
+ status: 400;
42
+ } | {
43
+ input: {
44
+ param: {
45
+ filename_ext: string;
46
+ };
47
+ };
48
+ output: "not found";
49
+ outputFormat: "body";
50
+ status: 404;
51
+ } | {
52
+ input: {
53
+ param: {
54
+ filename_ext: string;
55
+ };
56
+ };
57
+ output: "bbox is required";
58
+ outputFormat: "body";
59
+ status: 400;
60
+ } | {
61
+ input: {
62
+ param: {
63
+ filename_ext: string;
64
+ };
65
+ };
66
+ output: "invalid bbox";
67
+ outputFormat: "body";
68
+ status: 400;
16
69
  };
17
70
  };
18
71
  } & {
@@ -23,9 +76,63 @@ declare function createClipRouter(options: {
23
76
  filename_ext: string;
24
77
  };
25
78
  };
26
- output: {};
27
- outputFormat: string;
28
- status: import("hono/utils/http-status").StatusCode;
79
+ output: "invalid format";
80
+ outputFormat: "body";
81
+ status: 400;
82
+ } | {
83
+ input: {
84
+ param: {
85
+ filename_ext: string;
86
+ };
87
+ };
88
+ output: Uint8Array<ArrayBuffer>;
89
+ outputFormat: "body";
90
+ status: import("hono/utils/http-status").ContentfulStatusCode;
91
+ } | {
92
+ input: {
93
+ param: {
94
+ filename_ext: string;
95
+ };
96
+ };
97
+ output: "failed to render tile";
98
+ outputFormat: "body";
99
+ status: 400;
100
+ } | {
101
+ input: {
102
+ param: {
103
+ filename_ext: string;
104
+ };
105
+ };
106
+ output: "not found";
107
+ outputFormat: "body";
108
+ status: 404;
109
+ } | {
110
+ input: {
111
+ param: {
112
+ filename_ext: string;
113
+ };
114
+ };
115
+ output: "bbox is required";
116
+ outputFormat: "body";
117
+ status: 400;
118
+ } | {
119
+ input: {
120
+ param: {
121
+ filename_ext: string;
122
+ };
123
+ };
124
+ output: "invalid bbox";
125
+ outputFormat: "body";
126
+ status: 400;
127
+ } | {
128
+ input: {
129
+ param: {
130
+ filename_ext: string;
131
+ };
132
+ };
133
+ output: "invalid stylejson";
134
+ outputFormat: "body";
135
+ status: 400;
29
136
  };
30
137
  };
31
138
  }, "/", "/:filename_ext">;
@@ -1,5 +1,4 @@
1
1
  import { Hono } from 'hono';
2
- import { stream } from 'hono/streaming';
3
2
  import { isSupportedFormat, isValidStylejson } from '../utils.js';
4
3
  import { getRenderedBbox } from '../../render/index.js';
5
4
  function createClipRouter(options) {
@@ -33,18 +32,8 @@ function createClipRouter(options) {
33
32
  ext,
34
33
  quality,
35
34
  });
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
- }
35
+ const buf = await sharp.toBuffer();
36
+ return c.body(buf);
48
37
  }
49
38
  catch (e) {
50
39
  console.error(`render error: ${e}`);
@@ -81,18 +70,8 @@ function createClipRouter(options) {
81
70
  ext,
82
71
  quality,
83
72
  });
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
- }
73
+ const buf = await sharp.toBuffer();
74
+ return c.body(buf);
96
75
  }
97
76
  catch (e) {
98
77
  console.error(`render error: ${e}`);
@@ -1,7 +1,6 @@
1
1
  import { Cache } from '../../cache/index.js';
2
2
  declare function createTilesRouter(options: {
3
3
  cache: Cache;
4
- stream: boolean;
5
4
  }): import("hono/hono-base").HonoBase<import("hono/types").BlankEnv, {
6
5
  "/:z/:x/:y_ext": {
7
6
  $get: {
@@ -14,9 +13,61 @@ declare function createTilesRouter(options: {
14
13
  y_ext: string;
15
14
  };
16
15
  };
17
- output: {};
18
- outputFormat: string;
19
- status: import("hono/utils/http-status").StatusCode;
16
+ output: "invalid format";
17
+ outputFormat: "body";
18
+ status: 400;
19
+ } | {
20
+ input: {
21
+ param: {
22
+ z: string;
23
+ } & {
24
+ x: string;
25
+ } & {
26
+ y_ext: string;
27
+ };
28
+ };
29
+ output: "url is required";
30
+ outputFormat: "body";
31
+ status: 400;
32
+ } | {
33
+ input: {
34
+ param: {
35
+ z: string;
36
+ } & {
37
+ x: string;
38
+ } & {
39
+ y_ext: string;
40
+ };
41
+ };
42
+ output: Uint8Array<ArrayBuffer>;
43
+ outputFormat: "body";
44
+ status: import("hono/utils/http-status").ContentfulStatusCode;
45
+ } | {
46
+ input: {
47
+ param: {
48
+ z: string;
49
+ } & {
50
+ x: string;
51
+ } & {
52
+ y_ext: string;
53
+ };
54
+ };
55
+ output: "failed to render tile";
56
+ outputFormat: "body";
57
+ status: 400;
58
+ } | {
59
+ input: {
60
+ param: {
61
+ z: string;
62
+ } & {
63
+ x: string;
64
+ } & {
65
+ y_ext: string;
66
+ };
67
+ };
68
+ output: "invalid xyz";
69
+ outputFormat: "body";
70
+ status: 400;
20
71
  };
21
72
  };
22
73
  } & {
@@ -31,9 +82,61 @@ declare function createTilesRouter(options: {
31
82
  y_ext: string;
32
83
  };
33
84
  };
34
- output: {};
35
- outputFormat: string;
36
- status: import("hono/utils/http-status").StatusCode;
85
+ output: "invalid format";
86
+ outputFormat: "body";
87
+ status: 400;
88
+ } | {
89
+ input: {
90
+ param: {
91
+ z: string;
92
+ } & {
93
+ x: string;
94
+ } & {
95
+ y_ext: string;
96
+ };
97
+ };
98
+ output: Uint8Array<ArrayBuffer>;
99
+ outputFormat: "body";
100
+ status: import("hono/utils/http-status").ContentfulStatusCode;
101
+ } | {
102
+ input: {
103
+ param: {
104
+ z: string;
105
+ } & {
106
+ x: string;
107
+ } & {
108
+ y_ext: string;
109
+ };
110
+ };
111
+ output: "failed to render tile";
112
+ outputFormat: "body";
113
+ status: 400;
114
+ } | {
115
+ input: {
116
+ param: {
117
+ z: string;
118
+ } & {
119
+ x: string;
120
+ } & {
121
+ y_ext: string;
122
+ };
123
+ };
124
+ output: "invalid xyz";
125
+ outputFormat: "body";
126
+ status: 400;
127
+ } | {
128
+ input: {
129
+ param: {
130
+ z: string;
131
+ } & {
132
+ x: string;
133
+ } & {
134
+ y_ext: string;
135
+ };
136
+ };
137
+ output: "invalid stylejson";
138
+ outputFormat: "body";
139
+ status: 400;
37
140
  };
38
141
  };
39
142
  }, "/", "/:z/:x/:y_ext">;
@@ -1,5 +1,4 @@
1
1
  import { Hono } from 'hono';
2
- import { stream } from 'hono/streaming';
3
2
  import { isSupportedFormat, isValidStylejson } from '../utils.js';
4
3
  import { getRenderedTile } from '../../render/index.js';
5
4
  function isValidXyz(x, y, z) {
@@ -41,18 +40,8 @@ function createTilesRouter(options) {
41
40
  ext,
42
41
  quality,
43
42
  });
44
- if (options.stream) {
45
- // stream mode
46
- return stream(c, async (stream) => {
47
- for await (const chunk of sharp) {
48
- stream.write(chunk);
49
- }
50
- });
51
- }
52
- else {
53
- const buf = await sharp.toBuffer();
54
- return c.body(buf);
55
- }
43
+ const buf = await sharp.toBuffer();
44
+ return c.body(buf);
56
45
  }
57
46
  catch (e) {
58
47
  console.error(`render error: ${e}`);
@@ -90,18 +79,8 @@ function createTilesRouter(options) {
90
79
  ext,
91
80
  quality,
92
81
  });
93
- if (options.stream) {
94
- // stream mode
95
- return stream(c, async (stream) => {
96
- for await (const chunk of sharp) {
97
- stream.write(chunk);
98
- }
99
- });
100
- }
101
- else {
102
- const buf = await sharp.toBuffer();
103
- return c.body(buf);
104
- }
82
+ const buf = await sharp.toBuffer();
83
+ return c.body(buf);
105
84
  }
106
85
  catch (e) {
107
86
  console.error(`render error: ${e}`);
@@ -1,7 +1,5 @@
1
1
  import { type StyleSpecification } from '@maplibre/maplibre-gl-style-spec';
2
2
  import type { SupportedFormat } from '../render/index.js';
3
3
  declare function isValidStylejson(stylejson: any): stylejson is StyleSpecification;
4
- declare function isValidCamera([, lon, lat, zoom, bearing, pitch]: string[]): boolean;
5
- declare function isValidDimensions([, width, height]: string[]): boolean;
6
4
  declare function isSupportedFormat(ext: string | undefined): ext is SupportedFormat;
7
- export { isValidStylejson, isValidCamera, isValidDimensions, isSupportedFormat, };
5
+ export { isValidStylejson, isSupportedFormat };
@@ -2,27 +2,7 @@ import { validateStyleMin, } from '@maplibre/maplibre-gl-style-spec';
2
2
  function isValidStylejson(stylejson) {
3
3
  return validateStyleMin(stylejson).length === 0;
4
4
  }
5
- function isValidCamera([, lon, lat, zoom, bearing, pitch]) {
6
- if (Number.isNaN(Number(lat)) || Number(lat) < -90 || Number(lat) > 90)
7
- return false;
8
- if (Number.isNaN(Number(lon)) || Number(lon) < -180 || Number(lon) > 180)
9
- return false;
10
- if (Number.isNaN(Number(zoom)) || Number(zoom) < 0 || Number(zoom) > 24)
11
- return false;
12
- if (bearing &&
13
- (Number.isNaN(Number(bearing)) ||
14
- Number(bearing) < 0 ||
15
- Number(bearing) > 360))
16
- return false;
17
- if (pitch &&
18
- (Number.isNaN(Number(pitch)) || Number(pitch) < 0 || Number(pitch) > 180))
19
- return false;
20
- return true;
21
- }
22
- function isValidDimensions([, width, height]) {
23
- return !Number.isNaN(Number(width)) && !Number.isNaN(Number(height));
24
- }
25
5
  function isSupportedFormat(ext) {
26
6
  return Boolean(ext && ['png', 'jpeg', 'jpg', 'webp'].includes(ext));
27
7
  }
28
- export { isValidStylejson, isValidCamera, isValidDimensions, isSupportedFormat, };
8
+ export { isValidStylejson, isSupportedFormat };
@@ -31,7 +31,7 @@ class S3Source {
31
31
  this.bucket = bucket;
32
32
  this.key = key;
33
33
  this.s3Client = getS3Client({
34
- region: process.env.CHIITILER_S3_REGION ?? 'us-east1',
34
+ region: process.env.CHIITILER_S3_REGION ?? 'us-east-1',
35
35
  endpoint: process.env.CHIITILER_S3_ENDPOINT,
36
36
  forcePathStyle: process.env.CHIITILER_S3_FORCE_PATH_STYLE === 'true',
37
37
  });
package/dist/source/s3.js CHANGED
@@ -2,7 +2,7 @@ import { GetObjectCommand } from '@aws-sdk/client-s3';
2
2
  import { getS3Client } from '../s3.js';
3
3
  async function getS3Source(uri) {
4
4
  const s3Client = getS3Client({
5
- region: process.env.CHIITILER_S3_REGION ?? 'us-east1',
5
+ region: process.env.CHIITILER_S3_REGION ?? 'us-east-1',
6
6
  endpoint: process.env.CHIITILER_S3_ENDPOINT,
7
7
  forcePathStyle: process.env.CHIITILER_S3_FORCE_PATH_STYLE === 'true',
8
8
  });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "chiitiler",
4
- "version": "1.16.0",
4
+ "version": "1.18.0",
5
5
  "description": "Tiny map rendering server for MapLibre Style Spec",
6
6
  "main": "./dist/index.js",
7
7
  "types": "./dist/index.d.ts",