├── .gitignore ├── .github ├── FUNDING.yml └── workflows │ └── check.yaml ├── readme.md ├── LICENSE ├── v01 ├── readme.md ├── level_blocks.svg ├── file_format.svg └── block_tiles.svg └── v02 ├── level_blocks.svg ├── readme.md ├── file_format.svg └── block_tiles.svg /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | *.bkp 3 | *.dtmp 4 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [michaelkreil] 2 | patreon: [MichaelKreil] 3 | open_collective: [versatiles] -------------------------------------------------------------------------------- /.github/workflows/check.yaml: -------------------------------------------------------------------------------- 1 | name: Check Markdown links 2 | 3 | on: 4 | push: 5 | workflow_dispatch: 6 | schedule: 7 | - cron: "0 3 1 * *" 8 | 9 | jobs: 10 | markdown-link-check: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@main 14 | - uses: michaelkreil/check-markdown-links@main 15 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Specification of the `versatiles` container. 2 | 3 | ## Versions 4 | 5 | - [**v02**](v02/readme.md) ← current version 6 | - [v01](v01/readme.md) 7 | 8 | ## Implementations 9 | 10 | - VersaTiles (Rust) [Repo](https://github.com/versatiles-org/versatiles-rs), [library crate](https://crates.io/crates/versatiles-lib) 11 | - VersaTiles (Node ESM) [Repo](https://github.com/versatiles-org/node-versatiles-container), [npm package](https://www.npmjs.com/package/@versatiles/container) 12 | - VersaTiles (Node CJS) [Repo](https://github.com/yetzt/node-versatiles) 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 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 25 | -------------------------------------------------------------------------------- /v01/readme.md: -------------------------------------------------------------------------------- 1 | 2 | # format 3 | 4 | - all numbers are stored in big endian byte order 5 | 6 | The file is composed of several parts: 7 | 1. A **header** with 66 bytes 8 | 2. compressed **metadata** (tiles.json) 9 | 3. several **blocks**, where each block consists of: 10 | - several **tiles** 11 | - **index** of these tiles 12 | 4. **index** of all blocks 13 | 14 |

15 | 16 | ## file 17 | 18 | ### `file_header` (66 bytes) 19 | 20 | - all `offset`s are relative to start of the file 21 | 22 | | offset | length | type | description | 23 | |--------|--------|--------|---------------------------| 24 | | 0 | 14 | string | `"versatiles_v01"` | 25 | | 14 | 1 | u8 | `tile_format` | 26 | | 15 | 1 | u8 | `tile_precompression` | 27 | | 16 | 1 | u8 | min zoom level | 28 | | 17 | 1 | u8 | max zoom level | 29 | | 18 | 4 | f32 | bbox min x (longitude) | 30 | | 22 | 4 | f32 | bbox min y (latitude) | 31 | | 26 | 4 | f32 | bbox max x (longitude) | 32 | | 30 | 4 | f32 | bbox max y (latitude) | 33 | | 34 | 8 | u64 | `offset` of `meta` | 34 | | 42 | 8 | u64 | `length` of `meta` | 35 | | 50 | 8 | u64 | `offset` of `block_index` | 36 | | 58 | 8 | u64 | `length` of `block_index` | 37 | 38 | ### `tile_format` values: 39 | - `0`: png 40 | - `1`: jpg 41 | - `2`: webp 42 | - `3`: pbf 43 | 44 | ### `tile_precompression` values: 45 | - `0`: uncompressed 46 | - `1`: gzip compressed 47 | - `2`: brotli compressed 48 | 49 | ### `meta` 50 | 51 | - Content of `tiles.json` 52 | - UTF-8 53 | - compressed with `$tile_precompression` 54 | 55 | ### `block` 56 | 57 | - Each `block` is like a "super tile" and contains data of up to 256x256 (= 65536) `tile`s. 58 | 59 | ### `block_index` (29 bytes per block) 60 | 61 | - Brotli compressed data structure 62 | - Empty `block`s are not stored 63 | - For each block `block_index` contains a 29 bytes long record: 64 | 65 | | offset | length | type | description | 66 | |-----------|--------|------|--------------------------| 67 | | 0 + 29*i | 1 | u8 | `level` | 68 | | 1 + 29*i | 4 | u32 | `column`/256 | 69 | | 5 + 29*i | 4 | u32 | `row`/256 | 70 | | 9 + 29*i | 1 | u8 | `col_min` (0..255) | 71 | | 10 + 29*i | 1 | u8 | `row_min` (0..255) | 72 | | 11 + 29*i | 1 | u8 | `col_max` (0..255) | 73 | | 12 + 29*i | 1 | u8 | `row_max` (0..255) | 74 | | 13 + 29*i | 8 | u64 | `offset` of `tile_index` | 75 | | 21 + 29*i | 8 | u64 | `length` of `tile_index` | 76 | 77 | ## `block` 78 | 79 | - Each `block` contains data of up to 256x256 (= 65536) `tile`s. 80 | - Levels 0-8 can be stored with one `block` each. level 9 might contain 512x512 `tile`s so 4 `block`s are necessary. 81 | 82 |

83 | 84 | - Each `block` contains the concatenated `tile` blobs and ends with a `tile_index`. 85 | - Neither the order of `block`s in the `file` nor the order of `tile`s in a `block` matters as long as their indexes are correct. 86 | - Note: To efficiently find the `block` that contains the `tile` you are looking for, use a data structure such as a "map", "dictionary", or "associative array" and fill it with the data from the `block_index`. 87 | 88 | ### `tile` 89 | 90 | - each tile is a PNG/PBF/… file as data blob 91 | - compressed with `$tile_precompression` 92 | 93 | ### `tile_index` 94 | 95 | - brotli compressed data structure 96 | - `tile`s are read horizontally then vertically 97 | - `j = (row - row_min)*(col_max - col_min + 1) + (col - col_min)` 98 | 99 |

100 | 101 | - identical `tile`s can be stored once and referenced multiple times to save storage space 102 | - if a `tile` does not exist, the length of `tile` is zero 103 | 104 | | offset | length | type | description | 105 | |--------|--------|------|---------------------------| 106 | | 12*j | 8 | u64 | `offset` of `tile_blob` j | 107 | | 12*j+8 | 4 | u32 | `length` of `tile_blob` j | 108 | -------------------------------------------------------------------------------- /v01/level_blocks.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
level 9
level 9
level 6
level 6
level 8
level 8
level 7
level 7
Text is not SVG - cannot display
-------------------------------------------------------------------------------- /v02/level_blocks.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
level 9
level 9
level 6
level 6
level 8
level 8
level 7
level 7
Text is not SVG - cannot display
-------------------------------------------------------------------------------- /v02/readme.md: -------------------------------------------------------------------------------- 1 | # Versatiles Container Format Specification v2.0 2 | 3 | ## 1. Introduction 4 | 5 | This document defines the Versatiles Container Format v2.0, which describes the structure and encoding mechanisms for efficiently storing large numbers of map tiles. 6 | 7 | ## 2. General Guidelines 8 | 9 | - **Byte order:** All numeric values are encoded in big-endian byte order. 10 | - **Tile organization:** Tiles are organised according to the XYZ scheme, with the origin (x=0, y=0) located at the top-left (northwest) corner. 11 | 12 | ## 3. File structure 13 | 14 | The Versatiles Container format consists of four main components: 15 | 16 | 1. **[File Header](#31-file-header):** Introduces the container file, details global properties, and indicates the locations of [Metadata](#32-metadata-chunk) and [Block Index](#34-block-index). 17 | 2. **[Metadata](#32-metadata-chunk):** Provides detailed information about the tileset, including attribution and layer definitions. 18 | 3. **[Blocks](#33-blocks):** Aggregates tiles into larger units (Blocks) for efficient storage and access, each containing [**Tile Blobs**](#331-tile-blobs) and [**Tile Index**](#332-tile-index). 19 | 4. **[Block Index](#34-block-index):** Acts as a parent directory for all blocks within the file. 20 | 21 | | File Format | 22 | |:-------------------------------:| 23 | | ![File Format](file_format.svg) | 24 | 25 | 26 | 27 | ### 3.1. File Header 28 | 29 | - **Length:** 66 bytes. 30 | - **Location:** At the start of the file. 31 | - **Purpose:** Outlines essential file properties and indicates subsequent section locations. 32 | - all offsets are relative to start of the file 33 | 34 | | Offset | Length | Type | Description | 35 | |-------:|-------:|:------:|------------------------------------------| 36 | | 0 | 14 | string | File identifier (`"versatiles_v02"`) | 37 | | 14 | 1 | u8 | `tile_format` value | 38 | | 15 | 1 | u8 | `precompression` value | 39 | | 16 | 1 | u8 | minimum zoom level | 40 | | 17 | 1 | u8 | maximum zoom level | 41 | | 18 | 4 | i32 | bbox min x (10⁷ × lon) | 42 | | 22 | 4 | i32 | bbox min y (10⁷ × lat) | 43 | | 26 | 4 | i32 | bbox max x (10⁷ × lon) | 44 | | 30 | 4 | i32 | bbox max y (10⁷ × lat) | 45 | | 34 | 8 | u64 | offset of [Metadata](#32-metadata-chunk) | 46 | | 42 | 8 | u64 | length of [Metadata](#32-metadata-chunk) | 47 | | 50 | 8 | u64 | offset of [Block Index](#34-block-index) | 48 | | 58 | 8 | u64 | length of [Block Index](#34-block-index) | 49 | 50 | 51 | 52 | #### 3.1.1. Value `tile_format` 53 | 54 | | Value hex | Value dec | Type | Mime | 55 | |----------:|----------:|:--------:|----------------------------| 56 | | `0x00` | `0` | bin | *application/octet-stream* | 57 | | `0x10` | `16` | png | *image/png* | 58 | | `0x11` | `17` | jpg | *image/jpeg* | 59 | | `0x12` | `18` | webp | *image/webp* | 60 | | `0x13` | `19` | avif | *image/avif* | 61 | | `0x14` | `20` | svg | *image/svg+xml* | 62 | | `0x20` | `32` | pbf | *application/x-protobuf* | 63 | | `0x21` | `33` | geojson | *application/geo+json* | 64 | | `0x22` | `34` | topojson | *application/topo+json* | 65 | | `0x23` | `35` | json | *application/json* | 66 | 67 | 68 | 69 | #### 3.1.2. Value `precompression` 70 | 71 | [Metadata](#32-metadata-chunk) and all [Tile Blobs](#331-tile-blobs) are pre-compressed with: 72 | 73 | | Value | Method | 74 | |-------|--------------| 75 | | `0` | Uncompressed | 76 | | `1` | gzip | 77 | | `2` | Brotli | 78 | 79 | 80 | 81 | ### 3.2. Metadata Chunk 82 | 83 | - **Content:** Encapsulates `tiles.json`, detailing tileset metadata. 84 | - **Encoding:** UTF-8. 85 | - **Compression:** Defined by the [`precompression`](#312-value-precompression) flag in the [File Header](#31-file-header). 86 | - **Note:** The absence of Metadata is indicated by zero offsets and lengths in the [File Header](#31-file-header). 87 | 88 | 89 | 90 | ### 3.3. Blocks 91 | 92 | - **Structure:** Blocks act as aggregators for up to 256×256 tiles. 93 | - **Zoom Levels:** Single Blocks can span entire zoom levels (0-8). Higher zoom levels (>8) may require multiple Blocks. 94 | - Maximum number of Blocks per zoom level: `pow(4, max(0, level - 8))`. 95 | 96 | | Blocks per level | 97 | |:---------------------------------:| 98 | | ![Level Blocks](level_blocks.svg) | 99 | 100 | - Each Block contains concatenated [Tile Blobs](#331-tile-blobs) and ends with a [Tile Index](#332-tile-index). 101 | - Neither the [Tile Blobs](#331-tile-blobs) in a Block nor Blocks in the file need to follow any particular order. 102 | 103 | 104 | 105 | #### 3.3.1. Tile Blobs 106 | 107 | - Tile Blobs are concatenated binary data, each containing one tile. All tiles have the same format and are pre-compressed. 108 | - **Format:** Each Tile Blob has the same file format, determined by the [`tile_format`](#311-value-tile_format) code in the [File Header](#31-file-header). 109 | - **Compression:** Each Tile Blob is compressed as specified by the [`precompression`](#312-value-precompression) flag in the [File Header](#31-file-header). 110 | 111 | 112 | 113 | #### 3.3.2. Tile Index 114 | 115 | - **Compression:** Brotli. 116 | - **Purpose:** Maps coordinates of tiles within a block to their respective binary position and length. 117 | - Tiles are ordered horizontally then vertically 118 | - `index = (row - row_min) * (col_max - col_min + 1) + (col - col_min)` 119 | - (`col_min`, `row_min`, `col_max`, `row_max` are specified in [Block Index](#34-block-index)) 120 | - identical [Tile Blobs](#331-tile-blobs) can be stored once and referenced multiple times to save storage space 121 | - if a tile does not exist, the length of Tile Blob is `0` 122 | - offsets of [Tile Blobs](#331-tile-blobs) are relative to the beginning of the Block. So the offset of the first Tile Blob should always be `0`. 123 | 124 | | Offset | Length | Type | Description | 125 | |--------|-------:|:----:|------------------------------| 126 | | 12*i | 8 | u64 | offset of Tile Blob in Block | 127 | | 12*i+8 | 4 | u32 | length of Tile Blob | 128 | 129 | | index of Tile Blobs | 130 | |:-------------------------------:| 131 | | ![Block Tiles](block_tiles.svg) | 132 | 133 | 134 | 135 | ### 3.4. Block Index 136 | 137 | - **Compression:** Brotli. 138 | - **Function:** Provides a directory for locating [Blocks](#33-blocks) within the container file. 139 | - Empty Blocks are not stored. 140 | - Each 33-byte entry within the Block Index is structured as follows: 141 | 142 | | Offset | Length | Type | Description | 143 | |----------:|-------:|:----:|-----------------------------------------| 144 | | 0 + 33*i | 1 | u8 | `level` | 145 | | 1 + 33*i | 4 | u32 | `column`/256 | 146 | | 5 + 33*i | 4 | u32 | `row`/256 | 147 | | 9 + 33*i | 1 | u8 | `col_min` (0..255) | 148 | | 10 + 33*i | 1 | u8 | `row_min` (0..255) | 149 | | 11 + 33*i | 1 | u8 | `col_max` (0..255) | 150 | | 12 + 33*i | 1 | u8 | `row_max` (0..255) | 151 | | 13 + 33*i | 8 | u64 | offset of Block in file | 152 | | 21 + 33*i | 8 | u64 | length of [Tile Blobs](#331-tile-blobs) | 153 | | 29 + 33*i | 4 | u32 | length of [Tile Index](#332-tile-index) | 154 | 155 | - Since a Block consists only of [Tile Blobs](#331-tile-blobs) appended by the [Tile Index](#332-tile-index), the length of Block must be the sum of the lengths of the [Tile Blobs](#331-tile-blobs) and the [Tile Index](#332-tile-index). 156 | - Note: To efficiently find the Block containing the tile you are looking for, use a data structure such as a "map", "dictionary" or "associative array" and fill it with the data from the Block Index. 157 | 158 | 159 | 160 | ## 4. Glossary 161 | 162 | - **Blob:** A chunk of binary data. [Object storage on Wikipedia](https://en.wikipedia.org/wiki/Object_storage) 163 | - **Block:** A composite unit containing up to 256×256 tiles. 164 | - **Brotli:** A compression algorithm known for its efficiency and performance. It offers better compression than gzip. [Brotli on Wikipedia](https://en.wikipedia.org/wiki/Brotli) 165 | - **Tile:** A square geographic area at a specified zoom level, containing map information as an image or as vector data. 166 | -------------------------------------------------------------------------------- /v01/file_format.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
FILE
FILE
meta
meta
file_header
file_header
block_index
block_index
block
block
...
...
block
block
block
block
BLOCK
BLOCK
tile_index
tile_index
tile
tile
...
...
tile
tile
tile
tile
Text is not SVG - cannot display
-------------------------------------------------------------------------------- /v02/file_format.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
FILE
FILE
metadata
metadata
file_header
file_header
block_index
block_index
block
block
...
...
block
block
block
block
BLOCK
BLOCK
tile_index
tile_index
tile
tile
...
...
tile
tile
tile
tile
Text is not SVG - cannot display
-------------------------------------------------------------------------------- /v01/block_tiles.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
min_col
min_col
max_col
max_col
min_row
min_row
max_row
max_row
BLOCK
BLOCK
TILES
TILES
0
0
1
1
2
2
3
3
4
4
5
5
6
6
7
7
8
8
9
9
11
11
10
10
Text is not SVG - cannot display
-------------------------------------------------------------------------------- /v02/block_tiles.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
min_col
min_col
max_col
max_col
min_row
min_row
max_row
max_row
BLOCK
BLOCK
TILES
TILES
0
0
1
1
2
2
3
3
4
4
5
5
6
6
7
7
8
8
9
9
11
11
10
10
Text is not SVG - cannot display
--------------------------------------------------------------------------------