@versatiles/container 1.0.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/LICENSE.md +24 -0
- package/README.md +245 -0
- package/dist/decompress.d.ts +14 -0
- package/dist/decompress.js +45 -0
- package/dist/index.d.ts +82 -0
- package/dist/index.js +292 -0
- package/dist/interfaces.d.ts +103 -0
- package/dist/interfaces.js +1 -0
- package/dist/reader_file.d.ts +19 -0
- package/dist/reader_file.js +54 -0
- package/dist/reader_http.d.ts +10 -0
- package/dist/reader_http.js +86 -0
- package/package.json +52 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
This is free and unencumbered software released into the public domain.
|
|
2
|
+
|
|
3
|
+
Anyone is free to copy, modify, publish, use, compile, sell, or
|
|
4
|
+
distribute this software, either in source code form or as a compiled
|
|
5
|
+
binary, for any purpose, commercial or non-commercial, and by any
|
|
6
|
+
means.
|
|
7
|
+
|
|
8
|
+
In jurisdictions that recognize copyright laws, the author or authors
|
|
9
|
+
of this software dedicate any and all copyright interest in the
|
|
10
|
+
software to the public domain. We make this dedication for the benefit
|
|
11
|
+
of the public at large and to the detriment of our heirs and
|
|
12
|
+
successors. We intend this dedication to be an overt act of
|
|
13
|
+
relinquishment in perpetuity of all present and future rights to this
|
|
14
|
+
software under copyright law.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
19
|
+
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
|
20
|
+
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
|
21
|
+
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
|
23
|
+
|
|
24
|
+
For more information, please refer to [unlicense.org](https://unlicense.org/)
|
package/README.md
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
A client library for [VersaTiles containers](https://github.com/versatiles-org/versatiles-spec).
|
|
2
|
+
|
|
3
|
+
# Table of Content
|
|
4
|
+
|
|
5
|
+
* [Install](#install)
|
|
6
|
+
* [Usage Example](#usage-example)
|
|
7
|
+
* [API](#api)
|
|
8
|
+
* [Class: VersaTiles](#class_versatiles)
|
|
9
|
+
* [constructor: new VersaTiles(source, options)](#constructor-new-versatilessource-options)
|
|
10
|
+
* [async getHeader()](#async-getheader)
|
|
11
|
+
* [async getMetadata()](#async-getmetadata)
|
|
12
|
+
* [async getTileFormat()](#async-gettileformat)
|
|
13
|
+
* [async getBlockIndex()](#async-getblockindex)
|
|
14
|
+
* [async getTileIndex(block)](#async-gettileindexblock)
|
|
15
|
+
* [async getTile(z, x, y)](#async-gettilez-x-y)
|
|
16
|
+
* [async getTileUncompressed(z, x, y)](#async-gettileuncompressedz-x-y)
|
|
17
|
+
* [Interface: Block](#interface_block)
|
|
18
|
+
* [Interface: Header](#interface_header)
|
|
19
|
+
* [Interface: Options](#interface_options)
|
|
20
|
+
* [Interface: TileIndex](#interface_tileindex)
|
|
21
|
+
* [Type: Compression](#type_compression)
|
|
22
|
+
* [Type: Format](#type_format)
|
|
23
|
+
* [Type: Reader](#type_reader)
|
|
24
|
+
* [License](#license)
|
|
25
|
+
* [Future work](#future-work)
|
|
26
|
+
|
|
27
|
+
# Install
|
|
28
|
+
|
|
29
|
+
`npm i versatiles`
|
|
30
|
+
|
|
31
|
+
# Usage Example
|
|
32
|
+
|
|
33
|
+
```js
|
|
34
|
+
import VersaTiles from 'versatiles';
|
|
35
|
+
import fs from 'fs';
|
|
36
|
+
|
|
37
|
+
const container = new VersaTiles('https://example.org/planet.versatiles');
|
|
38
|
+
const header = await container.getHeader();
|
|
39
|
+
const tile = await container.getTileUncompressed(z,x,y);
|
|
40
|
+
fs.writeFileSync('tile.' + header.tile_format, tile);
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
# API
|
|
44
|
+
|
|
45
|
+
## Class: `VersaTiles`<a id="class_versatiles"></a>
|
|
46
|
+
|
|
47
|
+
The `VersaTiles` class is a wrapper around a `.versatiles` container file. It provides methods\
|
|
48
|
+
to access tile data, metadata, and other properties within the container. <sup><a href="https://github.com/versatiles-org/node-versatiles/blob/575beea/container/src/index.ts#L27">\[src]</a></sup>
|
|
49
|
+
|
|
50
|
+
### constructor: `new VersaTiles(source, options)`
|
|
51
|
+
|
|
52
|
+
Constructs a new instance of the VersaTiles class. <sup><a href="https://github.com/versatiles-org/node-versatiles/blob/575beea/container/src/index.ts#L50">\[src]</a></sup>
|
|
53
|
+
|
|
54
|
+
**Parameters:**
|
|
55
|
+
|
|
56
|
+
* `source: string | `[`Reader`](#type_reader)\
|
|
57
|
+
The data source for the tiles. This can be a URL starting with `http://` or `https://`,
|
|
58
|
+
a path to a local file, or a custom `Reader` function that reads data chunks based on offset and length.
|
|
59
|
+
* `options: `[`Options`](#interface_options) (optional)\
|
|
60
|
+
Optional settings that configure tile handling.
|
|
61
|
+
|
|
62
|
+
**Returns:** [`VersaTiles`](#class_versatiles)
|
|
63
|
+
**Methods**
|
|
64
|
+
|
|
65
|
+
### `async getHeader()`
|
|
66
|
+
|
|
67
|
+
Asynchronously retrieves the header information from the `.versatiles` container.\
|
|
68
|
+
This method is primarily for internal use. <sup><a href="https://github.com/versatiles-org/node-versatiles/blob/575beea/container/src/index.ts#L75">\[src]</a></sup>
|
|
69
|
+
|
|
70
|
+
**Returns:** [`Header`](#interface_header)
|
|
71
|
+
|
|
72
|
+
### `async getMetadata()`
|
|
73
|
+
|
|
74
|
+
Asynchronously retrieves the metadata associated with the `.versatiles` container.\
|
|
75
|
+
Metadata typically includes information about `vector_layers` for vector tiles.
|
|
76
|
+
If the container does not include metadata, this method returns `null`. <sup><a href="https://github.com/versatiles-org/node-versatiles/blob/575beea/container/src/index.ts#L133">\[src]</a></sup>
|
|
77
|
+
|
|
78
|
+
**Returns:** `null | object`
|
|
79
|
+
|
|
80
|
+
### `async getTileFormat()`
|
|
81
|
+
|
|
82
|
+
Asynchronously determines the tile format, such as "png" or "pbf", based on the header information. <sup><a href="https://github.com/versatiles-org/node-versatiles/blob/575beea/container/src/index.ts#L153">\[src]</a></sup>
|
|
83
|
+
|
|
84
|
+
**Returns:** [`Format`](#type_format)
|
|
85
|
+
|
|
86
|
+
### `async getBlockIndex()`
|
|
87
|
+
|
|
88
|
+
Asynchronously retrieves a mapping of tile block indices. The map's keys are formatted as "{z},{x},{y}".\
|
|
89
|
+
This method is for internal use to manage tile lookup within the container. <sup><a href="https://github.com/versatiles-org/node-versatiles/blob/575beea/container/src/index.ts#L165">\[src]</a></sup>
|
|
90
|
+
|
|
91
|
+
**Returns:** `Map<string,`[`Block`](#interface_block)`>`
|
|
92
|
+
|
|
93
|
+
### `async getTileIndex(block)`
|
|
94
|
+
|
|
95
|
+
Asynchronously retrieves the tile index for a specified block. This is an internal method used to\
|
|
96
|
+
maintain a lookup for every tile within a block. <sup><a href="https://github.com/versatiles-org/node-versatiles/blob/575beea/container/src/index.ts#L244">\[src]</a></sup>
|
|
97
|
+
|
|
98
|
+
**Parameters:**
|
|
99
|
+
|
|
100
|
+
* `block: `[`Block`](#interface_block)\
|
|
101
|
+
The block for which to retrieve the tile index.
|
|
102
|
+
|
|
103
|
+
**Returns:** [`TileIndex`](#interface_tileindex)
|
|
104
|
+
|
|
105
|
+
### `async getTile(z, x, y)`
|
|
106
|
+
|
|
107
|
+
Asynchronously retrieves a specific tile's data as a Buffer. If the tile data is compressed as\
|
|
108
|
+
defined in the container header, the returned Buffer will contain the compressed data.
|
|
109
|
+
To obtain uncompressed data, use the `getTileUncompressed` method.
|
|
110
|
+
If the specified tile does not exist, the method returns `null`. <sup><a href="https://github.com/versatiles-org/node-versatiles/blob/575beea/container/src/index.ts#L274">\[src]</a></sup>
|
|
111
|
+
|
|
112
|
+
**Parameters:**
|
|
113
|
+
|
|
114
|
+
* `z: number`\
|
|
115
|
+
The zoom level of the tile.
|
|
116
|
+
* `x: number`\
|
|
117
|
+
The x coordinate of the tile within its zoom level.
|
|
118
|
+
* `y: number`\
|
|
119
|
+
The y coordinate of the tile within its zoom level.
|
|
120
|
+
|
|
121
|
+
**Returns:** `null | Buffer`
|
|
122
|
+
|
|
123
|
+
### `async getTileUncompressed(z, x, y)`
|
|
124
|
+
|
|
125
|
+
Asynchronously retrieves a specific tile's uncompressed data as a Buffer. This method first\
|
|
126
|
+
retrieves the compressed tile data using `getTile` and then decompresses it based on the
|
|
127
|
+
compression setting in the container header.
|
|
128
|
+
If the specified tile does not exist, the method returns `null`. <sup><a href="https://github.com/versatiles-org/node-versatiles/blob/575beea/container/src/index.ts#L322">\[src]</a></sup>
|
|
129
|
+
|
|
130
|
+
**Parameters:**
|
|
131
|
+
|
|
132
|
+
* `z: number`\
|
|
133
|
+
The zoom level of the tile.
|
|
134
|
+
* `x: number`\
|
|
135
|
+
The x coordinate of the tile within its zoom level.
|
|
136
|
+
* `y: number`\
|
|
137
|
+
The y coordinate of the tile within its zoom level.
|
|
138
|
+
|
|
139
|
+
**Returns:** `null | Buffer`
|
|
140
|
+
|
|
141
|
+
## Interface: `Block`<a id="interface_block"></a>
|
|
142
|
+
|
|
143
|
+
Interface for a block of tiles including necessary metadata.\
|
|
144
|
+
For more details, refer to [spec v02](https://github.com/versatiles-org/versatiles-spec/blob/main/v02/readme.md#block_index). <sup><a href="https://github.com/versatiles-org/node-versatiles/blob/575beea/container/src/interfaces.ts#L85">\[src]</a></sup>
|
|
145
|
+
**Properties**
|
|
146
|
+
|
|
147
|
+
* `level: number`\
|
|
148
|
+
The zoom level for this block.
|
|
149
|
+
* `column: number`\
|
|
150
|
+
The column position of this block at the current zoom level.
|
|
151
|
+
* `row: number`\
|
|
152
|
+
The row position of this block at the current zoom level.
|
|
153
|
+
* `colMin: number`\
|
|
154
|
+
Minimum column index for tiles stored in this block (range: 0-255).
|
|
155
|
+
* `rowMin: number`\
|
|
156
|
+
Minimum row index for tiles stored in this block (range: 0-255).
|
|
157
|
+
* `colMax: number`\
|
|
158
|
+
Maximum column index for tiles stored in this block (range: 0-255).
|
|
159
|
+
* `rowMax: number`\
|
|
160
|
+
Maximum row index for tiles stored in this block (range: 0-255).
|
|
161
|
+
* `blockOffset: number`\
|
|
162
|
+
Byte position where this block starts in the file container.
|
|
163
|
+
* `tileIndexOffset: number`\
|
|
164
|
+
Byte position where the tile index starts within the container.
|
|
165
|
+
* `tileIndexLength: number`\
|
|
166
|
+
Length of the tile index in bytes.
|
|
167
|
+
* `tileCount: number`\
|
|
168
|
+
The number of tiles contained in this block.
|
|
169
|
+
* `tileIndex: `[`TileIndex`](#interface_tileindex) (optional)\
|
|
170
|
+
Optional tile index if it has been fetched.
|
|
171
|
+
|
|
172
|
+
## Interface: `Header`<a id="interface_header"></a>
|
|
173
|
+
|
|
174
|
+
Interface for the metadata header of a `*.Versatiles` container. <sup><a href="https://github.com/versatiles-org/node-versatiles/blob/575beea/container/src/interfaces.ts#L52">\[src]</a></sup>
|
|
175
|
+
**Properties**
|
|
176
|
+
|
|
177
|
+
* `magic: string`\
|
|
178
|
+
Identifier for the container format, usually "versatiles\_v02".
|
|
179
|
+
* `version: string`\
|
|
180
|
+
Version of the container format, typically "v02".
|
|
181
|
+
* `tileFormat: `[`Format`](#type_format)\
|
|
182
|
+
The format used for storing tiles.
|
|
183
|
+
* `tileCompression: `[`Compression`](#type_compression)\
|
|
184
|
+
The type of compression applied to tiles.
|
|
185
|
+
* `zoomMin: number`\
|
|
186
|
+
The minimum zoom level.
|
|
187
|
+
* `zoomMax: number`\
|
|
188
|
+
The maximum zoom level.
|
|
189
|
+
* `bbox: [number, number, number, number]`\
|
|
190
|
+
Bounding box coordinates as \[lon\_min, lat\_min, lon\_max, lat\_max].
|
|
191
|
+
* `metaOffset: number`\
|
|
192
|
+
The byte offset for metadata within the container.
|
|
193
|
+
* `metaLength: number`\
|
|
194
|
+
The byte size of the metadata. A value of 0 means no metadata.
|
|
195
|
+
* `blockIndexOffset: number`\
|
|
196
|
+
The byte offset for the block index within the container.
|
|
197
|
+
* `blockIndexLength: number`\
|
|
198
|
+
The byte size of the block index. A value of 0 indicates no tiles in the container.
|
|
199
|
+
|
|
200
|
+
## Interface: `Options`<a id="interface_options"></a>
|
|
201
|
+
|
|
202
|
+
Interface for defining the options available for reading a container. <sup><a href="https://github.com/versatiles-org/node-versatiles/blob/575beea/container/src/interfaces.ts#L119">\[src]</a></sup>
|
|
203
|
+
**Properties**
|
|
204
|
+
|
|
205
|
+
* `tms: boolean`\
|
|
206
|
+
If set to true, uses the [TMS (Tile Map Service) tile ordering](https://wiki.openstreetmap.org/wiki/TMS) where y=0 is the southernmost point.
|
|
207
|
+
|
|
208
|
+
## Interface: `TileIndex`<a id="interface_tileindex"></a>
|
|
209
|
+
|
|
210
|
+
Interface for the index structure used for tiles within a block. <sup><a href="https://github.com/versatiles-org/node-versatiles/blob/575beea/container/src/interfaces.ts#L108">\[src]</a></sup>
|
|
211
|
+
**Properties**
|
|
212
|
+
|
|
213
|
+
* `offsets: Float64Array`\
|
|
214
|
+
Array indicating the start byte positions of tiles within the block.
|
|
215
|
+
* `lengths: Float64Array`\
|
|
216
|
+
Array specifying the byte lengths of the tiles. A length of 0 means the tile is not stored.
|
|
217
|
+
|
|
218
|
+
## Type: `Compression`<a id="type_compression"></a>
|
|
219
|
+
|
|
220
|
+
Supported compression.\
|
|
221
|
+
`null` signifies that the data is uncompressed. <sup><a href="https://github.com/versatiles-org/node-versatiles/blob/575beea/container/src/interfaces.ts#L5">\[src]</a></sup>
|
|
222
|
+
|
|
223
|
+
**Type:** `"br" | "gzip" | null`
|
|
224
|
+
|
|
225
|
+
## Type: `Format`<a id="type_format"></a>
|
|
226
|
+
|
|
227
|
+
Supported tile formats. <sup><a href="https://github.com/versatiles-org/node-versatiles/blob/575beea/container/src/interfaces.ts#L11">\[src]</a></sup>
|
|
228
|
+
|
|
229
|
+
**Type:** `"avif" | "bin" | "geojson" | "jpeg" | "json" | "pbf" | "png" | "svg" | "topojson" | "webp" | null`
|
|
230
|
+
|
|
231
|
+
## Type: `Reader`<a id="type_reader"></a>
|
|
232
|
+
|
|
233
|
+
Type definition for an asynchronous function to read a container's content.
|
|
234
|
+
|
|
235
|
+
This can be useful for accessing data stored in various storage mediums. <sup><a href="https://github.com/versatiles-org/node-versatiles/blob/575beea/container/src/interfaces.ts#L33">\[src]</a></sup>
|
|
236
|
+
|
|
237
|
+
**Type:** `(position: number, length: number) => Promise<Buffer>`
|
|
238
|
+
|
|
239
|
+
# License
|
|
240
|
+
|
|
241
|
+
[Unlicense](./LICENSE.md)
|
|
242
|
+
|
|
243
|
+
# Future work
|
|
244
|
+
|
|
245
|
+
This library could be extended to run in a web browser to read VersaTiles containers via `fetch`.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/// <reference types="node" resolution-mode="require"/>
|
|
2
|
+
import type { Compression } from './interfaces.js';
|
|
3
|
+
/**
|
|
4
|
+
* Decompresses a buffer using the specified compression algorithm. Currently supports 'br' for Brotli and 'gzip' for GZIP.
|
|
5
|
+
* If the specified algorithm is not supported, the function resolves the promise with the original buffer.
|
|
6
|
+
*
|
|
7
|
+
* @param {Buffer} buffer - The buffer to be decompressed.
|
|
8
|
+
* @param {Compression} compression - The compression algorithm to use. Supported values are 'br' for Brotli and 'gzip' for GZIP.
|
|
9
|
+
* @returns {Promise<Buffer>} A promise that, when resolved, provides the decompressed buffer. If the compression type is not recognized,
|
|
10
|
+
* the promise will resolve with the original buffer. If decompression fails, the promise will be rejected with an error message.
|
|
11
|
+
* @throws {Error} Throws an error if the decompression process encounters an error. The error includes the buffer's length and
|
|
12
|
+
* the compression algorithm that was attempted.
|
|
13
|
+
*/
|
|
14
|
+
export declare function decompress(buffer: Buffer, compression: Compression): Promise<Buffer>;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import zlib from 'zlib';
|
|
2
|
+
/**
|
|
3
|
+
* Decompresses a buffer using the specified compression algorithm. Currently supports 'br' for Brotli and 'gzip' for GZIP.
|
|
4
|
+
* If the specified algorithm is not supported, the function resolves the promise with the original buffer.
|
|
5
|
+
*
|
|
6
|
+
* @param {Buffer} buffer - The buffer to be decompressed.
|
|
7
|
+
* @param {Compression} compression - The compression algorithm to use. Supported values are 'br' for Brotli and 'gzip' for GZIP.
|
|
8
|
+
* @returns {Promise<Buffer>} A promise that, when resolved, provides the decompressed buffer. If the compression type is not recognized,
|
|
9
|
+
* the promise will resolve with the original buffer. If decompression fails, the promise will be rejected with an error message.
|
|
10
|
+
* @throws {Error} Throws an error if the decompression process encounters an error. The error includes the buffer's length and
|
|
11
|
+
* the compression algorithm that was attempted.
|
|
12
|
+
*/
|
|
13
|
+
export async function decompress(buffer, compression) {
|
|
14
|
+
return new Promise((resolve, reject) => {
|
|
15
|
+
switch (compression) {
|
|
16
|
+
case 'br':
|
|
17
|
+
zlib.brotliDecompress(buffer, handle);
|
|
18
|
+
break;
|
|
19
|
+
case 'gzip':
|
|
20
|
+
zlib.gunzip(buffer, handle);
|
|
21
|
+
break;
|
|
22
|
+
default:
|
|
23
|
+
resolve(buffer);
|
|
24
|
+
break;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Handles the result of the decompression operation. If decompression is successful, the promise is resolved
|
|
28
|
+
* with the decompressed buffer. If an error occurs during decompression, the promise is rejected with a descriptive
|
|
29
|
+
* error message.
|
|
30
|
+
*
|
|
31
|
+
* @param {Error | null} error - The error object, if any, returned from the decompression operation.
|
|
32
|
+
* @param {Buffer} result - The decompressed buffer returned from the decompression operation.
|
|
33
|
+
* @throws {Error} Throws an error if decompression fails, including the buffer length and the attempted compression method.
|
|
34
|
+
*/
|
|
35
|
+
function handle(error, result) {
|
|
36
|
+
if (error) {
|
|
37
|
+
reject(`Can not decompress buffer (length=${buffer.length}) with "${compression}"`);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
resolve(result);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/// <reference types="node" resolution-mode="require"/>
|
|
2
|
+
import type { Block, Format, Header, Options, Reader, TileIndex } from './interfaces.js';
|
|
3
|
+
export type { Block, Compression, Format, Header, Options, Reader, TileIndex } from './interfaces.js';
|
|
4
|
+
/**
|
|
5
|
+
* The `VersaTiles` class is a wrapper around a `.versatiles` container file. It provides methods
|
|
6
|
+
* to access tile data, metadata, and other properties within the container.
|
|
7
|
+
*/
|
|
8
|
+
export declare class VersaTiles {
|
|
9
|
+
#private;
|
|
10
|
+
/**
|
|
11
|
+
* Constructs a new instance of the VersaTiles class.
|
|
12
|
+
*
|
|
13
|
+
* @param source - The data source for the tiles. This can be a URL starting with `http://` or `https://`,
|
|
14
|
+
* a path to a local file, or a custom `Reader` function that reads data chunks based on offset and length.
|
|
15
|
+
* @param options - Optional settings that configure tile handling.
|
|
16
|
+
* @throws Will throw an error if the provided source is neither a string nor a Reader function.
|
|
17
|
+
*/
|
|
18
|
+
constructor(source: Reader | string, options?: Options);
|
|
19
|
+
/**
|
|
20
|
+
* Asynchronously retrieves the header information from the `.versatiles` container.
|
|
21
|
+
* This method is primarily for internal use.
|
|
22
|
+
*
|
|
23
|
+
* @returns A promise that resolves with the header object.
|
|
24
|
+
* @throws Will throw an error if the container does not start with expected magic bytes indicating a valid format.
|
|
25
|
+
*/
|
|
26
|
+
getHeader(): Promise<Header>;
|
|
27
|
+
/**
|
|
28
|
+
* Asynchronously retrieves the metadata associated with the `.versatiles` container.
|
|
29
|
+
* Metadata typically includes information about `vector_layers` for vector tiles.
|
|
30
|
+
* If the container does not include metadata, this method returns `null`.
|
|
31
|
+
*
|
|
32
|
+
* @returns A promise that resolves with an object representing the metadata, or null if no metadata is present.
|
|
33
|
+
*/
|
|
34
|
+
getMetadata(): Promise<object | null>;
|
|
35
|
+
/**
|
|
36
|
+
* Asynchronously determines the tile format, such as "png" or "pbf", based on the header information.
|
|
37
|
+
*
|
|
38
|
+
* @returns A promise that resolves with the tile format string, or null if the format is undefined.
|
|
39
|
+
*/
|
|
40
|
+
getTileFormat(): Promise<Format | null>;
|
|
41
|
+
/**
|
|
42
|
+
* Asynchronously retrieves a mapping of tile block indices. The map's keys are formatted as "{z},{x},{y}".
|
|
43
|
+
* This method is for internal use to manage tile lookup within the container.
|
|
44
|
+
*
|
|
45
|
+
* @returns A promise that resolves with the block index map.
|
|
46
|
+
* @internal
|
|
47
|
+
*/
|
|
48
|
+
getBlockIndex(): Promise<Map<string, Block>>;
|
|
49
|
+
/**
|
|
50
|
+
* Asynchronously retrieves the tile index for a specified block. This is an internal method used to
|
|
51
|
+
* maintain a lookup for every tile within a block.
|
|
52
|
+
*
|
|
53
|
+
* @param block - The block for which to retrieve the tile index.
|
|
54
|
+
* @returns A promise that resolves with the tile index.
|
|
55
|
+
* @internal
|
|
56
|
+
*/
|
|
57
|
+
getTileIndex(block: Block): Promise<TileIndex>;
|
|
58
|
+
/**
|
|
59
|
+
* Asynchronously retrieves a specific tile's data as a Buffer. If the tile data is compressed as
|
|
60
|
+
* defined in the container header, the returned Buffer will contain the compressed data.
|
|
61
|
+
* To obtain uncompressed data, use the `getTileUncompressed` method.
|
|
62
|
+
* If the specified tile does not exist, the method returns `null`.
|
|
63
|
+
*
|
|
64
|
+
* @param z - The zoom level of the tile.
|
|
65
|
+
* @param x - The x coordinate of the tile within its zoom level.
|
|
66
|
+
* @param y - The y coordinate of the tile within its zoom level.
|
|
67
|
+
* @returns A promise that resolves with the tile data as a Buffer, or null if the tile cannot be found.
|
|
68
|
+
*/
|
|
69
|
+
getTile(z: number, x: number, y: number): Promise<Buffer | null>;
|
|
70
|
+
/**
|
|
71
|
+
* Asynchronously retrieves a specific tile's uncompressed data as a Buffer. This method first
|
|
72
|
+
* retrieves the compressed tile data using `getTile` and then decompresses it based on the
|
|
73
|
+
* compression setting in the container header.
|
|
74
|
+
* If the specified tile does not exist, the method returns `null`.
|
|
75
|
+
*
|
|
76
|
+
* @param z - The zoom level of the tile.
|
|
77
|
+
* @param x - The x coordinate of the tile within its zoom level.
|
|
78
|
+
* @param y - The y coordinate of the tile within its zoom level.
|
|
79
|
+
* @returns A promise that resolves with the uncompressed tile data as a Buffer, or null if the tile cannot be found.
|
|
80
|
+
*/
|
|
81
|
+
getTileUncompressed(z: number, x: number, y: number): Promise<Buffer | null>;
|
|
82
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import HttpReader from './reader_http.js';
|
|
2
|
+
import FileReader from './reader_file.js';
|
|
3
|
+
import { decompress } from './decompress.js';
|
|
4
|
+
const FORMATS = {
|
|
5
|
+
'c01': ['png', 'jpeg', 'webp', null, null, null, null, null, null, null, null, null, null, null, null, null, 'pbf'],
|
|
6
|
+
'v01': ['png', 'jpeg', 'webp', 'pbf'],
|
|
7
|
+
'v02': [
|
|
8
|
+
'bin', null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
|
|
9
|
+
'png', 'jpeg', 'webp', 'avif', 'svg', null, null, null, null, null, null, null, null, null, null, null,
|
|
10
|
+
'pbf', 'geojson', 'topojson', 'json',
|
|
11
|
+
],
|
|
12
|
+
};
|
|
13
|
+
const COMPRESSIONS = [null, 'gzip', 'br'];
|
|
14
|
+
/**
|
|
15
|
+
* The `VersaTiles` class is a wrapper around a `.versatiles` container file. It provides methods
|
|
16
|
+
* to access tile data, metadata, and other properties within the container.
|
|
17
|
+
*/
|
|
18
|
+
export class VersaTiles {
|
|
19
|
+
#options = {
|
|
20
|
+
tms: false,
|
|
21
|
+
};
|
|
22
|
+
#reader;
|
|
23
|
+
#decompress;
|
|
24
|
+
#header;
|
|
25
|
+
#metadata;
|
|
26
|
+
#blockIndex;
|
|
27
|
+
/**
|
|
28
|
+
* Constructs a new instance of the VersaTiles class.
|
|
29
|
+
*
|
|
30
|
+
* @param source - The data source for the tiles. This can be a URL starting with `http://` or `https://`,
|
|
31
|
+
* a path to a local file, or a custom `Reader` function that reads data chunks based on offset and length.
|
|
32
|
+
* @param options - Optional settings that configure tile handling.
|
|
33
|
+
* @throws Will throw an error if the provided source is neither a string nor a Reader function.
|
|
34
|
+
*/
|
|
35
|
+
constructor(source, options) {
|
|
36
|
+
if (options)
|
|
37
|
+
Object.assign(this.#options, options);
|
|
38
|
+
if (typeof source === 'string') {
|
|
39
|
+
if (source.startsWith('https://') || source.startsWith('http://')) {
|
|
40
|
+
this.#reader = HttpReader(source);
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
this.#reader = FileReader(source);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
else if (typeof source === 'function') {
|
|
47
|
+
this.#reader = source;
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
throw new Error('source must be a string or a Reader');
|
|
51
|
+
}
|
|
52
|
+
this.#decompress = decompress;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Asynchronously retrieves the header information from the `.versatiles` container.
|
|
56
|
+
* This method is primarily for internal use.
|
|
57
|
+
*
|
|
58
|
+
* @returns A promise that resolves with the header object.
|
|
59
|
+
* @throws Will throw an error if the container does not start with expected magic bytes indicating a valid format.
|
|
60
|
+
*/
|
|
61
|
+
async getHeader() {
|
|
62
|
+
// deliver if known
|
|
63
|
+
if (this.#header)
|
|
64
|
+
return this.#header;
|
|
65
|
+
const data = await this.#read(0, 66);
|
|
66
|
+
// check magic bytes
|
|
67
|
+
if (!/^versatiles_v0[12]$/.test(data.toString('utf8', 0, 14))) {
|
|
68
|
+
throw new Error('Invalid Container');
|
|
69
|
+
}
|
|
70
|
+
const version = data.toString('utf8', 11, 14);
|
|
71
|
+
switch (version) {
|
|
72
|
+
case 'v01':
|
|
73
|
+
this.#header = {
|
|
74
|
+
magic: data.toString('utf8', 0, 14),
|
|
75
|
+
version: version,
|
|
76
|
+
tileFormat: FORMATS[version][data.readUInt8(14)] ?? 'bin',
|
|
77
|
+
tileCompression: COMPRESSIONS[data.readUInt8(15)] ?? null,
|
|
78
|
+
zoomMin: data.readUInt8(16),
|
|
79
|
+
zoomMax: data.readUInt8(17),
|
|
80
|
+
bbox: [data.readFloatBE(18), data.readFloatBE(22), data.readFloatBE(26), data.readFloatBE(30)],
|
|
81
|
+
metaOffset: Number(data.readBigUInt64BE(34)),
|
|
82
|
+
metaLength: Number(data.readBigUInt64BE(42)),
|
|
83
|
+
blockIndexOffset: Number(data.readBigUInt64BE(50)),
|
|
84
|
+
blockIndexLength: Number(data.readBigUInt64BE(58)),
|
|
85
|
+
};
|
|
86
|
+
break;
|
|
87
|
+
case 'v02':
|
|
88
|
+
this.#header = {
|
|
89
|
+
magic: data.toString('utf8', 0, 14),
|
|
90
|
+
version: version,
|
|
91
|
+
tileFormat: FORMATS[version][data.readUInt8(14)] ?? 'bin',
|
|
92
|
+
tileCompression: COMPRESSIONS[data.readUInt8(15)] ?? null,
|
|
93
|
+
zoomMin: data.readUInt8(16),
|
|
94
|
+
zoomMax: data.readUInt8(17),
|
|
95
|
+
bbox: [data.readInt32BE(18) / 1e7, data.readInt32BE(22) / 1e7, data.readInt32BE(26) / 1e7, data.readInt32BE(30) / 1e7],
|
|
96
|
+
metaOffset: Number(data.readBigUInt64BE(34)),
|
|
97
|
+
metaLength: Number(data.readBigUInt64BE(42)),
|
|
98
|
+
blockIndexOffset: Number(data.readBigUInt64BE(50)),
|
|
99
|
+
blockIndexLength: Number(data.readBigUInt64BE(58)),
|
|
100
|
+
};
|
|
101
|
+
break;
|
|
102
|
+
default:
|
|
103
|
+
throw new Error('Invalid Container');
|
|
104
|
+
}
|
|
105
|
+
return this.#header;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Asynchronously retrieves the metadata associated with the `.versatiles` container.
|
|
109
|
+
* Metadata typically includes information about `vector_layers` for vector tiles.
|
|
110
|
+
* If the container does not include metadata, this method returns `null`.
|
|
111
|
+
*
|
|
112
|
+
* @returns A promise that resolves with an object representing the metadata, or null if no metadata is present.
|
|
113
|
+
*/
|
|
114
|
+
async getMetadata() {
|
|
115
|
+
if (this.#metadata !== undefined)
|
|
116
|
+
return this.#metadata;
|
|
117
|
+
const header = await this.getHeader();
|
|
118
|
+
if (header.metaLength === 0) {
|
|
119
|
+
this.#metadata = null;
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
let buffer = await this.#read(header.metaOffset, header.metaLength);
|
|
123
|
+
buffer = await this.#decompress(buffer, header.tileCompression);
|
|
124
|
+
this.#metadata = JSON.parse(buffer.toString());
|
|
125
|
+
}
|
|
126
|
+
return this.#metadata;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Asynchronously determines the tile format, such as "png" or "pbf", based on the header information.
|
|
130
|
+
*
|
|
131
|
+
* @returns A promise that resolves with the tile format string, or null if the format is undefined.
|
|
132
|
+
*/
|
|
133
|
+
async getTileFormat() {
|
|
134
|
+
return (await this.getHeader()).tileFormat;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Asynchronously retrieves a mapping of tile block indices. The map's keys are formatted as "{z},{x},{y}".
|
|
138
|
+
* This method is for internal use to manage tile lookup within the container.
|
|
139
|
+
*
|
|
140
|
+
* @returns A promise that resolves with the block index map.
|
|
141
|
+
* @internal
|
|
142
|
+
*/
|
|
143
|
+
async getBlockIndex() {
|
|
144
|
+
if (this.#blockIndex)
|
|
145
|
+
return this.#blockIndex;
|
|
146
|
+
const header = await this.getHeader();
|
|
147
|
+
// read block_index buffer
|
|
148
|
+
let data = await this.#read(header.blockIndexOffset, header.blockIndexLength);
|
|
149
|
+
data = await this.#decompress(data, 'br');
|
|
150
|
+
// read index from buffer
|
|
151
|
+
const blocks = [];
|
|
152
|
+
switch (header.version) {
|
|
153
|
+
case 'c01':
|
|
154
|
+
case 'v01':
|
|
155
|
+
// check blog index length
|
|
156
|
+
if (data.length % 29 !== 0)
|
|
157
|
+
throw new Error('invalid block index');
|
|
158
|
+
for (let i = 0; i < data.length; i += 29) {
|
|
159
|
+
const slice = data.subarray(i, i + 29);
|
|
160
|
+
addBlock(slice, 0n, slice.readBigUInt64BE(13), slice.readBigUInt64BE(21));
|
|
161
|
+
}
|
|
162
|
+
break;
|
|
163
|
+
case 'v02':
|
|
164
|
+
// check blog index length
|
|
165
|
+
if (data.length % 33 !== 0)
|
|
166
|
+
throw new Error('invalid block index');
|
|
167
|
+
for (let i = 0; i < data.length; i += 33) {
|
|
168
|
+
const slice = data.subarray(i, i + 33);
|
|
169
|
+
addBlock(slice, slice.readBigUInt64BE(13), slice.readBigUInt64BE(13) + slice.readBigUInt64BE(21), // block_offset + tile_blobs_length
|
|
170
|
+
slice.readUInt32BE(29));
|
|
171
|
+
}
|
|
172
|
+
break;
|
|
173
|
+
}
|
|
174
|
+
// build map
|
|
175
|
+
this.#blockIndex = new Map(blocks.map(b => [`${b.level},${b.column},${b.row}`, b]));
|
|
176
|
+
return this.#blockIndex;
|
|
177
|
+
// eslint-disable-next-line @typescript-eslint/max-params
|
|
178
|
+
function addBlock(buffer, blockOffset, tileIndexOffset, tileIndexLength) {
|
|
179
|
+
const block = {
|
|
180
|
+
level: buffer.readUInt8(0),
|
|
181
|
+
column: buffer.readUInt32BE(1),
|
|
182
|
+
row: buffer.readUInt32BE(5),
|
|
183
|
+
colMin: buffer.readUInt8(9),
|
|
184
|
+
rowMin: buffer.readUInt8(10),
|
|
185
|
+
colMax: buffer.readUInt8(11),
|
|
186
|
+
rowMax: buffer.readUInt8(12),
|
|
187
|
+
blockOffset: Number(blockOffset),
|
|
188
|
+
tileIndexOffset: Number(tileIndexOffset),
|
|
189
|
+
tileIndexLength: Number(tileIndexLength),
|
|
190
|
+
tileCount: 0,
|
|
191
|
+
};
|
|
192
|
+
block.tileCount = (block.colMax - block.colMin + 1) * (block.rowMax - block.rowMin + 1);
|
|
193
|
+
blocks.push(block);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Asynchronously retrieves the tile index for a specified block. This is an internal method used to
|
|
198
|
+
* maintain a lookup for every tile within a block.
|
|
199
|
+
*
|
|
200
|
+
* @param block - The block for which to retrieve the tile index.
|
|
201
|
+
* @returns A promise that resolves with the tile index.
|
|
202
|
+
* @internal
|
|
203
|
+
*/
|
|
204
|
+
async getTileIndex(block) {
|
|
205
|
+
if (block.tileIndex)
|
|
206
|
+
return block.tileIndex;
|
|
207
|
+
let buffer = await this.#read(block.tileIndexOffset, block.tileIndexLength);
|
|
208
|
+
buffer = await this.#decompress(buffer, 'br');
|
|
209
|
+
const offsets = new Float64Array(block.tileCount);
|
|
210
|
+
const lengths = new Float64Array(block.tileCount);
|
|
211
|
+
for (let i = 0; i < block.tileCount; i++) {
|
|
212
|
+
offsets[i] = Number(buffer.readBigUInt64BE(12 * i)) + block.blockOffset;
|
|
213
|
+
lengths[i] = buffer.readUInt32BE(12 * i + 8);
|
|
214
|
+
}
|
|
215
|
+
block.tileIndex = { offsets, lengths };
|
|
216
|
+
return block.tileIndex;
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Asynchronously retrieves a specific tile's data as a Buffer. If the tile data is compressed as
|
|
220
|
+
* defined in the container header, the returned Buffer will contain the compressed data.
|
|
221
|
+
* To obtain uncompressed data, use the `getTileUncompressed` method.
|
|
222
|
+
* If the specified tile does not exist, the method returns `null`.
|
|
223
|
+
*
|
|
224
|
+
* @param z - The zoom level of the tile.
|
|
225
|
+
* @param x - The x coordinate of the tile within its zoom level.
|
|
226
|
+
* @param y - The y coordinate of the tile within its zoom level.
|
|
227
|
+
* @returns A promise that resolves with the tile data as a Buffer, or null if the tile cannot be found.
|
|
228
|
+
*/
|
|
229
|
+
async getTile(z, x, y) {
|
|
230
|
+
// when y index is inverted
|
|
231
|
+
if (this.#options.tms)
|
|
232
|
+
y = Math.pow(2, z) - y - 1;
|
|
233
|
+
// ensure block index is loaded
|
|
234
|
+
const blockIndex = await this.getBlockIndex();
|
|
235
|
+
// block xy
|
|
236
|
+
const bx = x >> 8;
|
|
237
|
+
const by = y >> 8;
|
|
238
|
+
// tile xy (within block)
|
|
239
|
+
const tx = x & 0xFF;
|
|
240
|
+
const ty = y & 0xFF;
|
|
241
|
+
// check if block containing tile is within bounds
|
|
242
|
+
const blockKey = `${z},${bx},${by}`;
|
|
243
|
+
const block = blockIndex.get(blockKey);
|
|
244
|
+
if (!block)
|
|
245
|
+
return null;
|
|
246
|
+
// check if block contains tile
|
|
247
|
+
if (tx < block.colMin || tx > block.colMax)
|
|
248
|
+
return null;
|
|
249
|
+
if (ty < block.rowMin || ty > block.rowMax)
|
|
250
|
+
return null;
|
|
251
|
+
// calculate sequential tile number
|
|
252
|
+
const j = (ty - block.rowMin) * (block.colMax - block.colMin + 1) + (tx - block.colMin);
|
|
253
|
+
// get tile index
|
|
254
|
+
const tileIndex = await this.getTileIndex(block);
|
|
255
|
+
const offset = tileIndex.offsets[j];
|
|
256
|
+
const length = tileIndex.lengths[j];
|
|
257
|
+
if (length === 0)
|
|
258
|
+
return null;
|
|
259
|
+
return this.#read(offset, length);
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Asynchronously retrieves a specific tile's uncompressed data as a Buffer. This method first
|
|
263
|
+
* retrieves the compressed tile data using `getTile` and then decompresses it based on the
|
|
264
|
+
* compression setting in the container header.
|
|
265
|
+
* If the specified tile does not exist, the method returns `null`.
|
|
266
|
+
*
|
|
267
|
+
* @param z - The zoom level of the tile.
|
|
268
|
+
* @param x - The x coordinate of the tile within its zoom level.
|
|
269
|
+
* @param y - The y coordinate of the tile within its zoom level.
|
|
270
|
+
* @returns A promise that resolves with the uncompressed tile data as a Buffer, or null if the tile cannot be found.
|
|
271
|
+
*/
|
|
272
|
+
async getTileUncompressed(z, x, y) {
|
|
273
|
+
const tile = await this.getTile(z, x, y);
|
|
274
|
+
if (!tile)
|
|
275
|
+
return null;
|
|
276
|
+
const header = await this.getHeader();
|
|
277
|
+
return this.#decompress(tile, header.tileCompression);
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* A private method to read a chunk of data from the source based on the specified offset and length.
|
|
281
|
+
*
|
|
282
|
+
* @param offset - The offset from the start of the source data to begin reading.
|
|
283
|
+
* @param length - The number of bytes to read from the source.
|
|
284
|
+
* @returns A promise that resolves with the read data as a Buffer.
|
|
285
|
+
* @private
|
|
286
|
+
*/
|
|
287
|
+
async #read(offset, length) {
|
|
288
|
+
if (length === 0)
|
|
289
|
+
return Buffer.allocUnsafe(0);
|
|
290
|
+
return this.#reader(offset, length);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/// <reference types="node" resolution-mode="require"/>
|
|
2
|
+
/**
|
|
3
|
+
* Supported compression.
|
|
4
|
+
* `null` signifies that the data is uncompressed.
|
|
5
|
+
*/
|
|
6
|
+
export type Compression = 'br' | 'gzip' | null;
|
|
7
|
+
/**
|
|
8
|
+
* Supported tile formats.
|
|
9
|
+
*/
|
|
10
|
+
export type Format = 'avif' | 'bin' | 'geojson' | 'jpeg' | 'json' | 'pbf' | 'png' | 'svg' | 'topojson' | 'webp' | null;
|
|
11
|
+
/**
|
|
12
|
+
* Type definition for a function that decompresses data.
|
|
13
|
+
*
|
|
14
|
+
* @param {Buffer} data - The compressed data.
|
|
15
|
+
* @param {Compression} compression - The type of compression used.
|
|
16
|
+
* @returns {Promise<Buffer>} - The decompressed data.
|
|
17
|
+
*/
|
|
18
|
+
export type Decompressor = (data: Buffer, compression: Compression) => Promise<Buffer>;
|
|
19
|
+
/**
|
|
20
|
+
* Type definition for an asynchronous function to read a container's content.
|
|
21
|
+
*
|
|
22
|
+
* This can be useful for accessing data stored in various storage mediums.
|
|
23
|
+
* @param {number} position - The byte offset at which to start reading.
|
|
24
|
+
* @param {number} length - The number of bytes to read.
|
|
25
|
+
* @returns {Promise<Buffer>} - The read data.
|
|
26
|
+
*/
|
|
27
|
+
export type Reader = (position: number, length: number) => Promise<Buffer>;
|
|
28
|
+
/**
|
|
29
|
+
* Interface for the metadata header of a `*.Versatiles` container.
|
|
30
|
+
*
|
|
31
|
+
* @property {string} magic - Identifier for the container format, usually "versatiles_v02".
|
|
32
|
+
* @property {string} version - Version of the container format, typically "v02".
|
|
33
|
+
* @property {Format} tileFormat - The format used for storing tiles.
|
|
34
|
+
* @property {Compression} tileCompression - The type of compression applied to tiles.
|
|
35
|
+
* @property {number} zoomMin - The minimum zoom level.
|
|
36
|
+
* @property {number} zoomMax - The maximum zoom level.
|
|
37
|
+
* @property {[number, number, number, number]} bbox - Bounding box coordinates as [lon_min, lat_min, lon_max, lat_max].
|
|
38
|
+
* @property {number} metaOffset - The byte offset for metadata within the container.
|
|
39
|
+
* @property {number} metaLength - The byte size of the metadata. A value of 0 means no metadata.
|
|
40
|
+
* @property {number} blockIndexOffset - The byte offset for the block index within the container.
|
|
41
|
+
* @property {number} blockIndexLength - The byte size of the block index. A value of 0 indicates no tiles in the container.
|
|
42
|
+
*/
|
|
43
|
+
export interface Header {
|
|
44
|
+
magic: string;
|
|
45
|
+
version: string;
|
|
46
|
+
tileFormat: Format;
|
|
47
|
+
tileCompression: Compression;
|
|
48
|
+
zoomMin: number;
|
|
49
|
+
zoomMax: number;
|
|
50
|
+
bbox: [number, number, number, number];
|
|
51
|
+
metaOffset: number;
|
|
52
|
+
metaLength: number;
|
|
53
|
+
blockIndexOffset: number;
|
|
54
|
+
blockIndexLength: number;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Interface for a block of tiles including necessary metadata.
|
|
58
|
+
* For more details, refer to [spec v02](https://github.com/versatiles-org/versatiles-spec/blob/main/v02/readme.md#block_index).
|
|
59
|
+
*
|
|
60
|
+
* @property {number} level - The zoom level for this block.
|
|
61
|
+
* @property {number} column - The column position of this block at the current zoom level.
|
|
62
|
+
* @property {number} row - The row position of this block at the current zoom level.
|
|
63
|
+
* @property {number} colMin - Minimum column index for tiles stored in this block (range: 0-255).
|
|
64
|
+
* @property {number} rowMin - Minimum row index for tiles stored in this block (range: 0-255).
|
|
65
|
+
* @property {number} colMax - Maximum column index for tiles stored in this block (range: 0-255).
|
|
66
|
+
* @property {number} rowMax - Maximum row index for tiles stored in this block (range: 0-255).
|
|
67
|
+
* @property {number} blockOffset - Byte position where this block starts in the file container.
|
|
68
|
+
* @property {number} tileIndexOffset - Byte position where the tile index starts within the container.
|
|
69
|
+
* @property {number} tileIndexLength - Length of the tile index in bytes.
|
|
70
|
+
* @property {number} tileCount - The number of tiles contained in this block.
|
|
71
|
+
* @property {TileIndex=} tileIndex - Optional tile index if it has been fetched.
|
|
72
|
+
*/
|
|
73
|
+
export interface Block {
|
|
74
|
+
level: number;
|
|
75
|
+
column: number;
|
|
76
|
+
row: number;
|
|
77
|
+
colMin: number;
|
|
78
|
+
rowMin: number;
|
|
79
|
+
colMax: number;
|
|
80
|
+
rowMax: number;
|
|
81
|
+
blockOffset: number;
|
|
82
|
+
tileIndexOffset: number;
|
|
83
|
+
tileIndexLength: number;
|
|
84
|
+
tileCount: number;
|
|
85
|
+
tileIndex?: TileIndex;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Interface for the index structure used for tiles within a block.
|
|
89
|
+
*
|
|
90
|
+
* @property {Float64Array} offsets - Array indicating the start byte positions of tiles within the block.
|
|
91
|
+
* @property {Float64Array} lengths - Array specifying the byte lengths of the tiles. A length of 0 means the tile is not stored.
|
|
92
|
+
*/
|
|
93
|
+
export interface TileIndex {
|
|
94
|
+
offsets: Float64Array;
|
|
95
|
+
lengths: Float64Array;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Interface for defining the options available for reading a container.
|
|
99
|
+
* @property {boolean} tms - If set to true, uses the [TMS (Tile Map Service) tile ordering](https://wiki.openstreetmap.org/wiki/TMS) where y=0 is the southernmost point.
|
|
100
|
+
*/
|
|
101
|
+
export interface Options {
|
|
102
|
+
tms: boolean;
|
|
103
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Reader } from './interfaces.js';
|
|
2
|
+
/**
|
|
3
|
+
* Creates a file reader function for reading chunks of data from a file. This reader abstracts
|
|
4
|
+
* the file reading process and provides a simple async interface to read data from the specified file.
|
|
5
|
+
* The reader keeps the file open, hence it should be used in contexts where the file is not
|
|
6
|
+
* expected to be modified by other processes. It's important to manage the lifecycle of the file
|
|
7
|
+
* descriptor properly and ensure it's closed when no longer needed.
|
|
8
|
+
*
|
|
9
|
+
* @param {string} filename - The name of the file to read from. This file must exist and be readable,
|
|
10
|
+
* otherwise the function will throw an error when trying to open it.
|
|
11
|
+
* @returns {Reader} A reader function that when called, returns a promise. This promise resolves with a
|
|
12
|
+
* Buffer containing the read bytes from the file. The function reads `length` bytes of data
|
|
13
|
+
* from the file starting at `position`. If an error occurs during the read operation,
|
|
14
|
+
* the promise is rejected with the error. If `position` is beyond the end of the file,
|
|
15
|
+
* the promise resolves to an empty buffer. If the read operation attempts to read beyond
|
|
16
|
+
* the end of the file, the promise resolves with a buffer that contains only the bytes
|
|
17
|
+
* that could be read.
|
|
18
|
+
*/
|
|
19
|
+
export default function getFileReader(filename: string): Reader;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
/**
|
|
3
|
+
* Creates a file reader function for reading chunks of data from a file. This reader abstracts
|
|
4
|
+
* the file reading process and provides a simple async interface to read data from the specified file.
|
|
5
|
+
* The reader keeps the file open, hence it should be used in contexts where the file is not
|
|
6
|
+
* expected to be modified by other processes. It's important to manage the lifecycle of the file
|
|
7
|
+
* descriptor properly and ensure it's closed when no longer needed.
|
|
8
|
+
*
|
|
9
|
+
* @param {string} filename - The name of the file to read from. This file must exist and be readable,
|
|
10
|
+
* otherwise the function will throw an error when trying to open it.
|
|
11
|
+
* @returns {Reader} A reader function that when called, returns a promise. This promise resolves with a
|
|
12
|
+
* Buffer containing the read bytes from the file. The function reads `length` bytes of data
|
|
13
|
+
* from the file starting at `position`. If an error occurs during the read operation,
|
|
14
|
+
* the promise is rejected with the error. If `position` is beyond the end of the file,
|
|
15
|
+
* the promise resolves to an empty buffer. If the read operation attempts to read beyond
|
|
16
|
+
* the end of the file, the promise resolves with a buffer that contains only the bytes
|
|
17
|
+
* that could be read.
|
|
18
|
+
*/
|
|
19
|
+
export default function getFileReader(filename) {
|
|
20
|
+
const fd = fs.openSync(filename, 'r');
|
|
21
|
+
/**
|
|
22
|
+
* Reads a chunk of data from the file at the specified position and length.
|
|
23
|
+
* It creates a buffer of the specified `length`, seeks to the `position` in the file,
|
|
24
|
+
* and reads `length` bytes into the buffer. This function is intended to be used with files
|
|
25
|
+
* that are not being concurrently modified, as it relies on consistent file state to
|
|
26
|
+
* function correctly.
|
|
27
|
+
*
|
|
28
|
+
* @param {number} position - The starting position in the file to read from. If this position
|
|
29
|
+
* is beyond the end of the file, the function will resolve to an
|
|
30
|
+
* empty buffer.
|
|
31
|
+
* @param {number} length - The number of bytes to read from the file starting at `position`.
|
|
32
|
+
* If `length` extends beyond the end of the file, the returned buffer
|
|
33
|
+
* will only include the bytes that could be read up to the end of the file.
|
|
34
|
+
* @returns {Promise<Buffer>} A promise that resolves with the buffer containing the read bytes. If
|
|
35
|
+
* an error occurs during the read, such as if the position is invalid or
|
|
36
|
+
* the file descriptor becomes invalid due to the file being closed, the
|
|
37
|
+
* promise will be rejected with an error.
|
|
38
|
+
*/
|
|
39
|
+
return async function read(position, length) {
|
|
40
|
+
return new Promise((resolve, reject) => {
|
|
41
|
+
fs.read(fd, {
|
|
42
|
+
buffer: Buffer.alloc(length),
|
|
43
|
+
position,
|
|
44
|
+
length,
|
|
45
|
+
}, (err, _, buf) => {
|
|
46
|
+
if (err) {
|
|
47
|
+
reject(err);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
resolve(buf);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
};
|
|
54
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Reader } from './interfaces.js';
|
|
2
|
+
/**
|
|
3
|
+
* Creates a function capable of reading data from a specified URL, which can be used
|
|
4
|
+
* to read data chunks in an HTTP GET request. This is particularly useful for
|
|
5
|
+
* operations such as streaming or handling large data in segments.
|
|
6
|
+
*
|
|
7
|
+
* @param url - The URL from which data will be read.
|
|
8
|
+
* @returns A `Reader` function that asynchronously reads a specified chunk of data from the URL.
|
|
9
|
+
*/
|
|
10
|
+
export default function getHTTPReader(url: string): Reader;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import https from 'https';
|
|
2
|
+
import http from 'http';
|
|
3
|
+
const DEFAULT_TIMEOUT = 10000;
|
|
4
|
+
/**
|
|
5
|
+
* A collection mapping protocol names to their respective `ClientInfo`.
|
|
6
|
+
*/
|
|
7
|
+
const clients = {
|
|
8
|
+
https: { client: https, agent: new https.Agent({ keepAlive: true }) },
|
|
9
|
+
http: { client: http, agent: new http.Agent({ keepAlive: true }) },
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Creates a function capable of reading data from a specified URL, which can be used
|
|
13
|
+
* to read data chunks in an HTTP GET request. This is particularly useful for
|
|
14
|
+
* operations such as streaming or handling large data in segments.
|
|
15
|
+
*
|
|
16
|
+
* @param url - The URL from which data will be read.
|
|
17
|
+
* @returns A `Reader` function that asynchronously reads a specified chunk of data from the URL.
|
|
18
|
+
*/
|
|
19
|
+
export default function getHTTPReader(url) {
|
|
20
|
+
/**
|
|
21
|
+
* Asynchronously reads a data chunk from the provided URL based on the specified range.
|
|
22
|
+
*
|
|
23
|
+
* @param position - The starting byte position of the data chunk to read.
|
|
24
|
+
* @param length - The number of bytes to read from the starting position.
|
|
25
|
+
* @returns A promise that resolves with a `Buffer` containing the data chunk.
|
|
26
|
+
* If the request fails or the server responds with a non-successful status code,
|
|
27
|
+
* the promise is rejected with an error.
|
|
28
|
+
*/
|
|
29
|
+
return async function read(position, length) {
|
|
30
|
+
const headers = {
|
|
31
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
32
|
+
'user-agent': 'Mozilla/5.0 (compatible; versatiles; +https://www.npmjs.com/package/versatiles)',
|
|
33
|
+
'range': `bytes=${position}-${position + length - 1}`,
|
|
34
|
+
};
|
|
35
|
+
const protocol = new URL(url).protocol.slice(0, -1);
|
|
36
|
+
if (!(protocol in clients)) {
|
|
37
|
+
throw new Error(`Unsupported protocol: ${protocol}`);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Performs the HTTP request and retrieves the response.
|
|
41
|
+
* @type {IncomingMessage}
|
|
42
|
+
*/
|
|
43
|
+
const message = await new Promise((resolve, reject) => {
|
|
44
|
+
const watchdog = setTimeout(() => {
|
|
45
|
+
reject(new Error('Request timed out'));
|
|
46
|
+
}, DEFAULT_TIMEOUT);
|
|
47
|
+
clients[protocol].client
|
|
48
|
+
.request(url, {
|
|
49
|
+
method: 'GET',
|
|
50
|
+
agent: clients[protocol].agent,
|
|
51
|
+
headers,
|
|
52
|
+
timeout: DEFAULT_TIMEOUT,
|
|
53
|
+
})
|
|
54
|
+
.on('response', response => {
|
|
55
|
+
clearTimeout(watchdog);
|
|
56
|
+
resolve(response);
|
|
57
|
+
})
|
|
58
|
+
.on('error', err => {
|
|
59
|
+
clearTimeout(watchdog);
|
|
60
|
+
reject(err);
|
|
61
|
+
})
|
|
62
|
+
.end();
|
|
63
|
+
});
|
|
64
|
+
if ((message.statusCode == null) || Math.floor(message.statusCode / 100) !== 2) {
|
|
65
|
+
message.destroy();
|
|
66
|
+
throw new Error(`Server responded with status code: ${message.statusCode}`);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Collects and concatenates response data chunks into a buffer.
|
|
70
|
+
* @type {Buffer}
|
|
71
|
+
*/
|
|
72
|
+
const body = await new Promise((resolve, reject) => {
|
|
73
|
+
const buffers = [];
|
|
74
|
+
message
|
|
75
|
+
.on('data', (chunk) => buffers.push(chunk))
|
|
76
|
+
.on('error', err => {
|
|
77
|
+
message.destroy();
|
|
78
|
+
reject(err);
|
|
79
|
+
})
|
|
80
|
+
.once('end', () => {
|
|
81
|
+
resolve(Buffer.concat(buffers));
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
return body;
|
|
85
|
+
};
|
|
86
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@versatiles/container",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A client library for reading VersaTiles containers",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"check": "npm run lint && npm run build && npm run test",
|
|
7
|
+
"build": "rm -rf dist && tsc -p tsconfig.build.json",
|
|
8
|
+
"doc": "npm run -w tsdoc2readme update_readme container/src/index.ts '# API'",
|
|
9
|
+
"lint": "eslint .",
|
|
10
|
+
"test": "jest"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"dist/**/*.js",
|
|
14
|
+
"dist/**/*.d.ts"
|
|
15
|
+
],
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/versatiles-org/node-versatiles.git"
|
|
19
|
+
},
|
|
20
|
+
"homepage": "https://github.com/versatiles-org/node-versatiles/blob/main/container/README.md",
|
|
21
|
+
"main": "dist/index.js",
|
|
22
|
+
"types": "dist/index.d.ts",
|
|
23
|
+
"type": "module",
|
|
24
|
+
"author": "yetzt <node@yetzt.me>, Michael Kreil <versatiles@michael-kreil.de>",
|
|
25
|
+
"license": "Unlicense",
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": ">= 18"
|
|
28
|
+
},
|
|
29
|
+
"keywords": [
|
|
30
|
+
"cloudtiles",
|
|
31
|
+
"geo",
|
|
32
|
+
"gis",
|
|
33
|
+
"map",
|
|
34
|
+
"maps",
|
|
35
|
+
"mapserver",
|
|
36
|
+
"maptiles",
|
|
37
|
+
"mvt",
|
|
38
|
+
"opencloudtiles",
|
|
39
|
+
"osm",
|
|
40
|
+
"pbf",
|
|
41
|
+
"tile",
|
|
42
|
+
"tiles",
|
|
43
|
+
"tileserver",
|
|
44
|
+
"vectortiles",
|
|
45
|
+
"versatiles"
|
|
46
|
+
],
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@types/node": "^20.9.0",
|
|
49
|
+
"jest": "^29.7.0",
|
|
50
|
+
"typescript": "^5.2.2"
|
|
51
|
+
}
|
|
52
|
+
}
|