chiitiler 1.12.7 → 1.14.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
@@ -95,6 +95,7 @@ you can pass server options via environment variables
95
95
  | CHIITILER_PORT | 3000 | port number |
96
96
  | CHIITILER_PROCESSES | 1 | num of chiitiler processes. 0 means all-CPUs |
97
97
  | CHIITILER_DEBUG | false | debug mode |
98
+ | CHIITILER_STREAM_MODE | false | stream mode |
98
99
  | CHIITILER_CACHE_METHOD | none | cache method, `none`, `memory`, `file` or `s3` |
99
100
  | CHIITILER_CACHE_TTL_SEC | 3600 | cache ttl, effect to `memory` and `file` |
100
101
  | CHIITILER_MEMORYCACHE_MAXITEMCOUNT | 1000 | max items for memorycache |
@@ -190,6 +191,13 @@ node dist/main.js tile-server -c s3 -s3b chiitiler -s3r ap-northeast-1
190
191
  "s3://tiles/{z}/{x}/{y}.pbf"
191
192
  ],
192
193
  "maxzoom": 6
194
+ },
195
+ "cog": {
196
+ "type": "raster",
197
+ "tiles": [
198
+ "cog://https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/54/T/WN/2024/9/S2A_54TWN_20240908_0_L2A/TCI.tif/{z}/{x}/{y}"
199
+ ],
200
+ "tileSize": 256
193
201
  }
194
202
  },
195
203
  "layers": [
@@ -232,6 +240,14 @@ node dist/main.js tile-server -c s3 -s3b chiitiler -s3r ap-northeast-1
232
240
  "circle-radius": 3,
233
241
  "circle-color": "green"
234
242
  }
243
+ },
244
+ {
245
+ "id": "cog",
246
+ "source": "cog",
247
+ "type": "raster",
248
+ "paint": {
249
+ "raster-opacity": 0.5
250
+ }
235
251
  }
236
252
  ]
237
253
  }
@@ -1,4 +1,3 @@
1
- /// <reference types="node" resolution-mode="require"/>
2
1
  import { memoryCache } from './memory.js';
3
2
  import { s3Cache } from './s3.js';
4
3
  import { fileCache } from './file.js';
package/dist/cli.js CHANGED
@@ -65,6 +65,17 @@ function parseDebug(debug) {
65
65
  // undefined or invalid
66
66
  return false;
67
67
  }
68
+ function parseStream(stream) {
69
+ // command-line option
70
+ if (stream)
71
+ return true;
72
+ // command-line is not specified or false -> try to read from env
73
+ const streamEnv = process.env.CHIITILER_STREAM_MODE;
74
+ if (streamEnv !== undefined)
75
+ return streamEnv === 'true';
76
+ // undefined or invalid
77
+ return false;
78
+ }
68
79
  export function createProgram() {
69
80
  const program = new Command();
70
81
  program
@@ -78,6 +89,7 @@ export function createProgram() {
78
89
  .option('-s3e --s3-endpoint <url>', 's3 endpoint url', '')
79
90
  .option('-3p --s3-force-path-style', 's3 force path style', '')
80
91
  .option('-p --port <port>', 'port number')
92
+ .option('-r --stream', 'stream mode')
81
93
  .option('-D --debug', 'debug mode')
82
94
  .action((options) => {
83
95
  const serverOptions = {
@@ -92,11 +104,14 @@ export function createProgram() {
92
104
  }),
93
105
  port: parsePort(options.port),
94
106
  debug: parseDebug(options.debug),
107
+ stream: parseStream(options.stream),
95
108
  };
96
109
  if (serverOptions.debug) {
97
110
  console.log(`running server: http://localhost:${serverOptions.port}`);
98
111
  console.log(`cache method: ${serverOptions.cache.name}`);
99
112
  console.log(`debug page: http://localhost:${serverOptions.port}/debug`);
113
+ console.log(`editor page: http://localhost:${serverOptions.port}/editor`);
114
+ console.log('stream mode:', serverOptions.stream ? 'enabled' : 'disabled');
100
115
  }
101
116
  const { start } = initServer(serverOptions);
102
117
  start();
package/dist/index.d.ts CHANGED
@@ -1,2 +1,6 @@
1
- export { getRenderedBboxBuffer, getRenderedTileBuffer, } from './render/index.js';
1
+ import { getRenderedBbox, getRenderedTile, GetRenderedBboxOptions, GetRenderedTileOptions } from './render/index.js';
2
+ export declare function getRenderedBboxBuffer(options: GetRenderedBboxOptions): Promise<Buffer>;
3
+ export { getRenderedBbox as getRenderedBboxStream };
4
+ export declare function getRenderedTileBuffer(options: GetRenderedTileOptions): Promise<Buffer>;
5
+ export { getRenderedTile as getRenderedTileStream };
2
6
  export * as ChiitilerCache from './cache/index.js';
package/dist/index.js CHANGED
@@ -1,2 +1,12 @@
1
- export { getRenderedBboxBuffer, getRenderedTileBuffer, } from './render/index.js';
1
+ import { getRenderedBbox, getRenderedTile, } from './render/index.js';
2
+ export async function getRenderedBboxBuffer(options) {
3
+ const sharp = await getRenderedBbox(options);
4
+ return sharp.toBuffer();
5
+ }
6
+ export { getRenderedBbox as getRenderedBboxStream };
7
+ export async function getRenderedTileBuffer(options) {
8
+ const sharp = await getRenderedTile(options);
9
+ return sharp.toBuffer();
10
+ }
11
+ export { getRenderedTile as getRenderedTileStream };
2
12
  export * as ChiitilerCache from './cache/index.js';
@@ -1,7 +1,7 @@
1
- /// <reference types="node" resolution-mode="require"/>
1
+ import sharp from 'sharp';
2
2
  import type { StyleSpecification } from '@maplibre/maplibre-gl-style-spec';
3
3
  import type { Cache } from '../cache/index.js';
4
- type RenderTilePipelineOptions = {
4
+ type GetRenderedTileOptions = {
5
5
  stylejson: string | StyleSpecification;
6
6
  z: number;
7
7
  x: number;
@@ -13,13 +13,14 @@ type RenderTilePipelineOptions = {
13
13
  quality: number;
14
14
  };
15
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, }: {
16
+ declare function getRenderedTile({ stylejson, z, x, y, tileSize, cache, margin, ext, quality, }: GetRenderedTileOptions): Promise<sharp.Sharp>;
17
+ type GetRenderedBboxOptions = {
18
18
  stylejson: string | StyleSpecification;
19
19
  bbox: [number, number, number, number];
20
20
  size: number;
21
21
  cache: Cache;
22
22
  ext: SupportedFormat;
23
23
  quality: number;
24
- }): Promise<Buffer>;
25
- export { getRenderedTileBuffer, getRenderedBboxBuffer, type SupportedFormat };
24
+ };
25
+ declare function getRenderedBbox({ stylejson, bbox, size, cache, ext, quality, }: GetRenderedBboxOptions): Promise<sharp.Sharp>;
26
+ export { getRenderedTile, getRenderedBbox, type GetRenderedBboxOptions, type GetRenderedTileOptions, type SupportedFormat, };
@@ -32,7 +32,7 @@ async function loadStyle(stylejson, cache) {
32
32
  }
33
33
  return style;
34
34
  }
35
- async function getRenderedTileBuffer({ stylejson, z, x, y, tileSize, cache, margin, ext, quality, }) {
35
+ async function getRenderedTile({ stylejson, z, x, y, tileSize, cache, margin, ext, quality, }) {
36
36
  const style = await loadStyle(stylejson, cache);
37
37
  let pixels;
38
38
  pixels = await renderTile(style, z, x, y, {
@@ -83,20 +83,15 @@ async function getRenderedTileBuffer({ stylejson, z, x, y, tileSize, cache, marg
83
83
  })
84
84
  .resize(tileSize, tileSize);
85
85
  }
86
- let buf;
87
86
  switch (ext) {
88
87
  case 'png':
89
- buf = await _sharp.png().toBuffer();
90
- break;
88
+ return _sharp.png();
91
89
  case 'jpeg':
92
90
  case 'jpg':
93
- buf = await _sharp.jpeg({ quality }).toBuffer();
94
- break;
91
+ return _sharp.jpeg({ quality });
95
92
  case 'webp':
96
- buf = await _sharp.webp({ quality, effort: 0 }).toBuffer();
97
- break;
93
+ return _sharp.webp({ quality, effort: 0 });
98
94
  }
99
- return buf;
100
95
  }
101
96
  const calcRenderingParams = (bbox, size) => {
102
97
  // reference: https://github.com/maptiler/tileserver-gl/blob/cc4b8f7954069fd0e1db731ff07f5349f7b9c8cd/src/serve_rendered.js#L346
@@ -117,7 +112,7 @@ const calcRenderingParams = (bbox, size) => {
117
112
  const center = mercator.ll(mercCenter, 25); // latlon
118
113
  return { zoom, width, height, center };
119
114
  };
120
- async function getRenderedBboxBuffer({ stylejson, bbox, size, cache, ext, quality, }) {
115
+ async function getRenderedBbox({ stylejson, bbox, size, cache, ext, quality, }) {
121
116
  const style = await loadStyle(stylejson, cache);
122
117
  const { zoom, width, height, center } = calcRenderingParams(bbox, size);
123
118
  const pixels = await render(style, {
@@ -135,12 +130,12 @@ async function getRenderedBboxBuffer({ stylejson, bbox, size, cache, ext, qualit
135
130
  });
136
131
  switch (ext) {
137
132
  case 'png':
138
- return await _sharp.png().toBuffer();
133
+ return _sharp.png();
139
134
  case 'jpeg':
140
135
  case 'jpg':
141
- return await _sharp.jpeg({ quality }).toBuffer();
136
+ return _sharp.jpeg({ quality });
142
137
  case 'webp':
143
- return await _sharp.webp({ quality, effort: 0 }).toBuffer();
138
+ return _sharp.webp({ quality, effort: 0 });
144
139
  }
145
140
  }
146
- export { getRenderedTileBuffer, getRenderedBboxBuffer };
141
+ export { getRenderedTile, getRenderedBbox, };
@@ -11,6 +11,8 @@ const TRANSPARENT_BUFFER = {
11
11
  jpeg: Buffer.from('/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBDAQkJCQwLDBgNDRgyIRwhMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjL/wAARCAABAAEDASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD3+iiigD//2Q=='),
12
12
  };
13
13
  function handleFileExt(uri) {
14
+ if (uri.startsWith('cog://'))
15
+ return 'png'; // cog:// always returns as png
14
16
  // extract extension only, take into account query string or hash
15
17
  const basename = path.basename(uri).split(/[?#]/)[0];
16
18
  const ext = basename.split('.').pop();
package/dist/s3.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { S3Client } from '@aws-sdk/client-s3';
2
2
  declare const getS3Client: ({ region, endpoint, forcePathStyle, }: {
3
3
  region: string;
4
- endpoint?: string | undefined;
5
- forcePathStyle?: boolean | undefined;
4
+ endpoint?: string;
5
+ forcePathStyle?: boolean;
6
6
  }) => S3Client;
7
7
  export { getS3Client };
@@ -4,6 +4,7 @@ type InitServerOptions = {
4
4
  cache: Cache;
5
5
  port: number;
6
6
  debug: boolean;
7
+ stream: boolean;
7
8
  };
8
9
  type InitializedServer = {
9
10
  app: Hono;
@@ -1,8 +1,9 @@
1
1
  import { Hono } from 'hono/quick';
2
+ import { stream } from 'hono/streaming';
2
3
  import { serve } from '@hono/node-server';
3
4
  import { validateStyleMin, } from '@maplibre/maplibre-gl-style-spec';
4
5
  import { getDebugPage, getEditorgPage } from './debug.js';
5
- import { getRenderedTileBuffer, getRenderedBboxBuffer, } from '../render/index.js';
6
+ import { getRenderedTile, getRenderedBbox, } from '../render/index.js';
6
7
  function isValidStylejson(stylejson) {
7
8
  return validateStyleMin(stylejson).length === 0;
8
9
  }
@@ -35,9 +36,9 @@ function initServer(options) {
35
36
  const tileSize = Number(c.req.query('tileSize') ?? 512);
36
37
  const quality = Number(c.req.query('quality') ?? 100);
37
38
  const margin = Number(c.req.query('margin') ?? 0);
38
- let buf;
39
+ c.header('Content-Type', `image/${ext}`);
39
40
  try {
40
- buf = await getRenderedTileBuffer({
41
+ const sharp = await getRenderedTile({
41
42
  stylejson: url,
42
43
  z,
43
44
  x,
@@ -48,13 +49,23 @@ function initServer(options) {
48
49
  ext,
49
50
  quality,
50
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
+ }
51
64
  }
52
65
  catch (e) {
53
66
  console.error(`render error: ${e}`);
54
67
  return c.body('failed to render tile', 400);
55
68
  }
56
- c.header('Content-Type', `image/${ext}`);
57
- return c.body(buf);
58
69
  })
59
70
  .post('/:z/:x/:y_ext', async (c) => {
60
71
  // body
@@ -74,9 +85,9 @@ function initServer(options) {
74
85
  const tileSize = Number(c.req.query('tileSize') ?? 512);
75
86
  const quality = Number(c.req.query('quality') ?? 100);
76
87
  const margin = Number(c.req.query('margin') ?? 0);
77
- let buf;
88
+ c.header('Content-Type', `image/${ext}`);
78
89
  try {
79
- buf = await getRenderedTileBuffer({
90
+ const sharp = await getRenderedTile({
80
91
  stylejson: style,
81
92
  z,
82
93
  x,
@@ -87,13 +98,23 @@ function initServer(options) {
87
98
  ext,
88
99
  quality,
89
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
+ }
90
113
  }
91
114
  catch (e) {
92
115
  console.error(`render error: ${e}`);
93
116
  return c.body('failed to render tile', 400);
94
117
  }
95
- c.header('Content-Type', `image/${ext}`);
96
- return c.body(buf);
97
118
  });
98
119
  const clip = new Hono()
99
120
  .get('/:filename_ext', async (c) => {
@@ -115,8 +136,9 @@ function initServer(options) {
115
136
  return c.body('url is required', 400);
116
137
  const quality = Number(c.req.query('quality') ?? 100);
117
138
  const size = Number(c.req.query('size') ?? 1024);
139
+ c.header('Content-Type', `image/${ext}`);
118
140
  try {
119
- const buf = await getRenderedBboxBuffer({
141
+ const sharp = await getRenderedBbox({
120
142
  stylejson: url,
121
143
  bbox: [minx, miny, maxx, maxy],
122
144
  size,
@@ -124,12 +146,22 @@ function initServer(options) {
124
146
  ext,
125
147
  quality,
126
148
  });
127
- c.header('Content-Type', `image/${ext}`);
128
- return c.body(buf);
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
+ }
129
161
  }
130
162
  catch (e) {
131
163
  console.error(`render error: ${e}`);
132
- return c.body('failed to render bbox', 400);
164
+ return c.body('failed to render tile', 400);
133
165
  }
134
166
  })
135
167
  .post('/:filename_ext', async (c) => {
@@ -152,8 +184,9 @@ function initServer(options) {
152
184
  return c.body('invalid bbox', 400);
153
185
  const quality = Number(c.req.query('quality') ?? 100);
154
186
  const size = Number(c.req.query('size') ?? 1024);
187
+ c.header('Content-Type', `image/${ext}`);
155
188
  try {
156
- const buf = await getRenderedBboxBuffer({
189
+ const sharp = await getRenderedBbox({
157
190
  stylejson: style,
158
191
  bbox: [minx, miny, maxx, maxy],
159
192
  size,
@@ -161,12 +194,22 @@ function initServer(options) {
161
194
  ext,
162
195
  quality,
163
196
  });
164
- c.header('Content-Type', `image/${ext}`);
165
- return c.body(buf);
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
+ }
166
209
  }
167
210
  catch (e) {
168
211
  console.error(`render error: ${e}`);
169
- return c.body('failed to render bbox', 400);
212
+ return c.body('failed to render tile', 400);
170
213
  }
171
214
  });
172
215
  const hono = new Hono();
@@ -0,0 +1,2 @@
1
+ declare function getCogSource(uri: string): Promise<Buffer | null>;
2
+ export { getCogSource };
@@ -0,0 +1,19 @@
1
+ /// <reference lib="dom" />
2
+ // for using native fetch in TypeScript
3
+ import { renderTile } from 'higuruma';
4
+ async function getCogSource(uri) {
5
+ const cogPath = uri.replace('cog://', '').replace(/\/\d+\/\d+\/\d+$/, '');
6
+ const [z, x, y] = uri.replace(`cog://${cogPath}/`, '').split('/');
7
+ try {
8
+ const tile = await renderTile(cogPath, Number(z), Number(x), Number(y));
9
+ const buf = Buffer.from(tile);
10
+ if (buf.byteLength === 129)
11
+ return null; // empty tile
12
+ return buf;
13
+ }
14
+ catch (e) {
15
+ console.error(`[ERROR] ${e}`);
16
+ return null;
17
+ }
18
+ }
19
+ export { getCogSource };
@@ -1,3 +1,2 @@
1
- /// <reference types="node" resolution-mode="require"/>
2
1
  declare function getFilesystemSource(uri: string): Promise<Buffer | null>;
3
2
  export { getFilesystemSource };
@@ -1,5 +1,3 @@
1
- /// <reference types="node" resolution-mode="require"/>
2
- /// <reference lib="dom" />
3
1
  import { type Cache } from '../cache/index.js';
4
2
  declare function getHttpSource(uri: string, cache?: Cache): Promise<Buffer | null>;
5
3
  export { getHttpSource };
@@ -1,4 +1,3 @@
1
- /// <reference types="node" resolution-mode="require"/>
2
1
  import { Cache } from '../cache/index.js';
3
2
  /**
4
3
  * retrieve sources from the uri
@@ -3,6 +3,7 @@ import { getHttpSource } from './http.js';
3
3
  import { getPmtilesSoruce } from './pmtiles.js';
4
4
  import { getMbtilesSource } from './mbtiles.js';
5
5
  import { getS3Source } from './s3.js';
6
+ import { getCogSource } from './cog.js';
6
7
  import { noneCache } from '../cache/index.js';
7
8
  /**
8
9
  * retrieve sources from the uri
@@ -22,6 +23,10 @@ async function getSource(uri, cache = noneCache()) {
22
23
  data = await getMbtilesSource(uri);
23
24
  else if (uri.startsWith('pmtiles://'))
24
25
  data = await getPmtilesSoruce(uri, cache);
26
+ else if (uri.startsWith('cog://'))
27
+ data = await getCogSource(uri);
28
+ else
29
+ return null;
25
30
  return data;
26
31
  }
27
32
  export { getSource };
@@ -1,4 +1,3 @@
1
- /// <reference types="node" resolution-mode="require"/>
2
1
  /**
3
2
  * uri = mbtiles://path/to/file.mbtiles/{z}/{x}/{y}
4
3
  */
@@ -1,4 +1,3 @@
1
- /// <reference types="node" resolution-mode="require"/>
2
1
  import { type Cache } from '../cache/index.js';
3
2
  /**
4
3
  * uri = pmtiles://path/to/file.pmtiles/{z}/{x}/{y}
@@ -1,3 +1,2 @@
1
- /// <reference types="node" resolution-mode="require"/>
2
1
  declare function getS3Source(uri: string): Promise<Buffer | null>;
3
2
  export { getS3Source };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "chiitiler",
4
- "version": "1.12.7",
4
+ "version": "1.14.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",
@@ -24,9 +24,7 @@
24
24
  "devDependencies": {
25
25
  "@tsconfig/node20": "^20.1.4",
26
26
  "@types/better-sqlite3": "^7.6.10",
27
- "@types/generic-pool": "^3.8.1",
28
27
  "@types/node": "^20.6.0",
29
- "@types/object-hash": "^3.0.6",
30
28
  "@vitest/coverage-v8": "^1.6.0",
31
29
  "image-size": "^1.1.1",
32
30
  "maplibre-gl": "^3.3.1",
@@ -45,6 +43,7 @@
45
43
  "better-sqlite3": "^9.6.0",
46
44
  "commander": "^11.0.0",
47
45
  "file-system-cache": "^2.4.4",
46
+ "higuruma": "^0.1.6",
48
47
  "hono": "^4.5.4",
49
48
  "lightning-pool": "^4.2.2",
50
49
  "lru-cache": "^11.0.0",