Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add basic cog support #1590

Open
wants to merge 39 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
8c32d83
Add basic cog support
sharkAndshark Oct 21, 2024
00ae4e8
add test to rga_u8
sharkAndshark Nov 30, 2024
d72f933
just bless
sharkAndshark Dec 2, 2024
c14e57f
add test
sharkAndshark Dec 2, 2024
a58e8b9
cleanup
sharkAndshark Dec 3, 2024
767d431
wip
sharkAndshark Dec 9, 2024
84ded8f
wip
sharkAndshark Dec 10, 2024
a3f704d
wip
sharkAndshark Dec 10, 2024
7602c2f
wip
sharkAndshark Dec 10, 2024
8a44863
wip
sharkAndshark Dec 10, 2024
d035ff4
clippy
sharkAndshark Dec 10, 2024
3fd79d5
Verify whether it's tiled
sharkAndshark Dec 10, 2024
7ae9049
bypass ci
sharkAndshark Dec 10, 2024
2d727cc
remove result.png
sharkAndshark Dec 10, 2024
15d4656
Merge remote-tracking branch 'maplibre/main' into cog
sharkAndshark Dec 12, 2024
f9f2d07
Merge remote-tracking branch 'maplibre/main' into cog
sharkAndshark Dec 12, 2024
27de84f
remove not used crate
sharkAndshark Dec 12, 2024
1f6c01a
cleanup
sharkAndshark Dec 12, 2024
4cd9fe7
update document
sharkAndshark Dec 12, 2024
e0fd84b
add min_zoom and max_zoom to tilejson
sharkAndshark Dec 13, 2024
967c588
Update doc
sharkAndshark Dec 16, 2024
2635835
Typos and clarity improvemnts for errors
sharkAndshark Dec 16, 2024
0fb459c
cleanup
sharkAndshark Dec 16, 2024
c564111
remove get_tile_json
sharkAndshark Dec 16, 2024
313b1bd
Merge remote-tracking branch 'maplibre/main' into cog
sharkAndshark Dec 16, 2024
9b183f6
Update docs/src/config-file.md
sharkAndshark Dec 16, 2024
5934d88
Update docs/src/sources-cog-files.md
sharkAndshark Dec 16, 2024
907831d
Update docs/src/sources-cog-files.md
sharkAndshark Dec 16, 2024
f78e67a
Update martin/src/cog/errors.rs
sharkAndshark Dec 16, 2024
62c12d9
Update docs/src/sources-cog-files.md
sharkAndshark Dec 16, 2024
bf21fdc
Update docs/src/sources-cog-files.md
sharkAndshark Dec 16, 2024
3ba6ea0
update doc
sharkAndshark Dec 16, 2024
ad0da99
doc improvements
sharkAndshark Dec 16, 2024
6d2d206
Cleanup
sharkAndshark Dec 16, 2024
b8050e1
Merge remote-tracking branch 'upstream/main' into cog
sharkAndshark Dec 16, 2024
7de18bf
format doc
sharkAndshark Dec 16, 2024
3900817
just bless
sharkAndshark Dec 17, 2024
d716eb1
update doc about cog
sharkAndshark Dec 17, 2024
4504cb7
Merge remote-tracking branch 'upstream/main' into cog
sharkAndshark Dec 20, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
395 changes: 194 additions & 201 deletions Cargo.lock

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ moka = { version = "0.12", features = ["future"] }
num_cpus = "1"
pbf_font_tools = { version = "2.5.1", features = ["freetype"] }
pmtiles = { version = "0.11", features = ["http-async", "mmap-async-tokio", "tilejson", "reqwest-rustls-tls-native-roots"] }
png = "0.17.14"
postgis = "0.9"
postgres = { version = "0.19", features = ["with-time-0_3", "with-uuid-1", "with-serde_json-1"] }
postgres-protocol = "0.6"
Expand All @@ -85,6 +86,7 @@ static-files = "0.2"
subst = { version = "0.3", features = ["yaml"] }
testcontainers-modules = { version = "0.11.4", features = ["postgres"] }
thiserror = "2"
tiff = "0.9.1"
tile-grid = "0.6"
tilejson = "0.4"
tokio = { version = "1", features = ["macros"] }
Expand Down
1 change: 1 addition & 0 deletions docs/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
- [PostgreSQL Table Sources](sources-pg-tables.md)
- [PostgreSQL Function Sources](sources-pg-functions.md)
- [MBTiles and PMTiles File Sources](sources-files.md)
- [Cloud Optimized GeoTIFF File Sources](sources-cog-files.md)
- [Composite Sources](sources-composite.md)
- [Sprite Sources](sources-sprites.md)
- [Font Sources](sources-fonts.md)
Expand Down
12 changes: 12 additions & 0 deletions docs/src/config-file.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,18 @@ mbtiles:
# named source matching source name to a single file
mb-src1: /path/to/mbtiles1.mbtiles

# Cloud Optimized GeoTIFF File Sources
cog:
paths:
# scan this whole dir, matching all *.tif files
- /dir-path
# specific TIFF file will be published as a cog source
- /path/to/cogfile.tif
sources:
# named source matching source name to a single file
cog-src1: /path/to/cog1.tif
cog-src2: /path/to/cog2.tif

# Sprite configuration
sprites:
paths:
Expand Down
104 changes: 104 additions & 0 deletions docs/src/sources-cog-files.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# Cloud Optimized GeoTIFF File Sources

Martin can also serve raster sources like local [COG(Cloud Optimized GeoTIFF)](https://cogeo.org/) files. For cog on remote like S3 and other improvements, you could track them on [issue 875](https://github.com/maplibre/martin/issues/875), we are working on and welcome any assistance.

## Supported colortype and bits per sample

| colory type | bits per sample | supported | status |
| ----------- | --------------- | --------- | ---------- |
| rgb/rgba | 8 | βœ… | |
| rgb/rgba | 16/32... | πŸ› οΈ | working on |
| gray | 8/16/32... | πŸ› οΈ | working on |

## Supported compression

* None
* LZW
* Deflate
* PackBits

## Run Martin with CLI to serve cog files

```bash
# Configured with a directory containing TIFF files.
martin /with/tiff/dir1 /with/tiff/dir2
# Configured with dedicated TIFF file.
martin /path/to/target1.tif /path/to/target1.tif
# Configured with a combination of directories and dedicated TIFF files.
martin /with/tiff/files /path/to/target.tif
```

## Run Martin with configuration file

```yml
keep_alive: 75

# The socket address to bind [default: 0.0.0.0:3000]
listen_addresses: '0.0.0.0:3000'

# Number of web server workers
worker_processes: 8

# Amount of memory (in MB) to use for caching tiles [default: 512, 0 to disable]
cache_size_mb: 8

# Database configuration. This can also be a list of PG configs.

cog:
paths:
# scan this whole dir, matching all *.tif files
- /dir-path
# specific TIFF file will be published as a cog source
- /path/to/target1.tif
- /path/to/target2.tif
sources:
# named source matching source name to a single file
cog-src1: /path/to/cog1.tif
cog-src2: /path/to/cog2.tif
```

## About COG

[COG](https://cogeo.org/) is just Cloud Optimized GeoTIFF file.

TIFF is an image file format. TIFF tags are something like key-value pairs inside to describe the metadata about a TIFF file, ike `ImageWidth`, `ImageLength`, etc.

GeoTIFF is a valid TIFF file with a set of TIFF tags to describe the 'Cartographic' information associated with it.

COG is a valid GeoTIFF file with some requirements for efficient reading. That is, all COG files are valid GeoTIFF files, but not all GeoTIFF files are valid COG files. For quick access to tiles in TIFF files, Martin relies on the requirements/recommendations(like the [requirement about Reduced-Resolution Subfiles](https://docs.ogc.org/is/21-026/21-026.html#_requirement_reduced_resolution_subfiles) and [the content dividing strategy](https://docs.ogc.org/is/21-026/21-026.html#_tiles)) so we use the term `COG` over `GeoTIFF` in our documentation and configuration files.

You may want to visit these specs:

* [TIFF 6.0](https://www.itu.int/itudoc/itu-t/com16/tiff-fx/docs/tiff6.pdf)
* [GeoTIFF](https://docs.ogc.org/is/19-008r4/19-008r4.html)
* [Cloud Optimized GeoTIFF](https://docs.ogc.org/is/21-026/21-026.html)

### COG generation with GDAL

You could generate cog with `gdal_translate` or `gdalwarp`. See more details in [gdal doc](https://gdal.org/en/latest/drivers/raster/cog.html).

```bash
# gdal-bin installation
# sudo apt update
# sudo apt install gdal-bin

# gdalwarp
gdalwarp src1.tif src2.tif out.tif -of COG

# or gdal_translate
gdal_translate input.tif output_cog.tif -of COG
```

### The mapping from ZXY to tiff chunk

* A single TIFF file could contains many sub-file about same spatial area, each has different resolution
* A sub file is organized with many tiles

So basically there's a mapping from zxy to tile of sub-file of TIFF.

| zxy | mapping to |
| ---------- | --------------------------- |
| Zoom level | which sub-file in TIFF file |
| X and Y | which tile in subfile |

Clients could read only the header part of COG to figure out the mapping from zxy to the chunk number and the subfile number. Martin get tile to frontend by this mapping.
7 changes: 5 additions & 2 deletions martin/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,13 @@ name = "bench"
harness = false

[features]
default = ["webui", "fonts", "lambda", "mbtiles", "pmtiles", "postgres", "sprites"]
default = ["webui", "fonts", "lambda", "mbtiles", "pmtiles", "cog", "postgres", "sprites"]
webui = ["dep:actix-web-static-files", "dep:static-files"]
fonts = ["dep:bit-set", "dep:pbf_font_tools"]
lambda = ["dep:lambda-web"]
mbtiles = ["dep:mbtiles"]
pmtiles = ["dep:pmtiles"]
cog = ["dep:tiff", "dep:png"]
postgres = ["dep:deadpool-postgres", "dep:json-patch", "dep:postgis", "dep:postgres", "dep:postgres-protocol", "dep:semver", "dep:tokio-postgres-rustls"]
sprites = ["dep:spreet", "tokio/fs"]
bless-tests = []
Expand Down Expand Up @@ -93,6 +94,7 @@ moka.workspace = true
num_cpus.workspace = true
pbf_font_tools = { workspace = true, optional = true }
pmtiles = { workspace = true, optional = true }
png= { workspace = true, optional=true }
postgis = { workspace = true, optional = true }
postgres = { workspace = true, optional = true }
postgres-protocol = { workspace = true, optional = true }
Expand All @@ -101,14 +103,15 @@ rustls-native-certs.workspace = true
rustls-pemfile.workspace = true
rustls.workspace = true
semver = { workspace = true, optional = true }
serde.workspace = true
serde_json.workspace = true
serde_with.workspace = true
serde_yaml.workspace = true
serde.workspace = true
spreet = { workspace = true, optional = true }
static-files = { workspace = true, optional = true }
subst.workspace = true
thiserror.workspace = true
tiff= { workspace = true, optional=true }
tilejson.workspace = true
tokio = { workspace = true, features = ["io-std"] }
tokio-postgres-rustls = { workspace = true, optional = true }
Expand Down
5 changes: 5 additions & 0 deletions martin/src/args/root.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,11 @@ impl Args {
config.mbtiles = parse_file_args(&mut cli_strings, "mbtiles", false);
}

#[cfg(feature = "cog")]
if !cli_strings.is_empty() {
config.cog = parse_file_args(&mut cli_strings, "tif", false);
}

#[cfg(feature = "sprites")]
if !self.extras.sprite.is_empty() {
config.sprites = FileConfigEnum::new(self.extras.sprite);
Expand Down
51 changes: 51 additions & 0 deletions martin/src/cog/errors.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
use std::path::PathBuf;

use png::EncodingError;
use tiff::TiffError;

#[derive(thiserror::Error, Debug)]
pub enum CogError {
#[error("Couldn't decode {1} as tiff file: {0}")]
InvalidTiffFile(TiffError, PathBuf),

#[error("Requested zoom level {0} from file {1} is out of range. Possible zoom levels are {2} to {3}")]
ZoomOutOfRange(u8, PathBuf, u8, u8),

#[error("Couldn't find any image in the tiff file: {0}")]
NoImagesFound(PathBuf),

#[error("Couldn't seek to ifd number {1} (0 based indexing) in tiff file {2}: {0}")]
IfdSeekFailed(TiffError, usize, PathBuf),

#[error("Too many images in the tiff file: {0}")]
TooManyImages(PathBuf),

#[error("Couldn't find tags {1:?} at ifd {2} of tiff file {3}: {0}")]
TagsNotFound(TiffError, Vec<u16>, usize, PathBuf),

#[error(
"Unsupported planar configuration {2} at IFD {1} in TIFF file {0}. Only planar configuration 1 is supported."
)]
PlanarConfigurationNotSupported(PathBuf, usize, u16),

#[error("Failed to read {1}th chunk(0 based index) at ifd {2} from tiff file {3}: {0}")]
ReadChunkFailed(TiffError, u32, usize, PathBuf),

#[error("Failed to write header of png file at {0}: {1}")]
WritePngHeaderFailed(PathBuf, EncodingError),

#[error("Failed to write pixel bytes to png file at {0}: {1}")]
WriteToPngFailed(PathBuf, EncodingError),

#[error("The color type {0:?} and its bit depth of the tiff file {1} is not supported yet")]
NotSupportedColorTypeAndBitDepth(tiff::ColorType, PathBuf),

#[error("Couldn't parse the {0} value in gdal metadata(tiff tag 42112) from {1}")]
ParseSTATISTICSValueFailed(String, PathBuf),

#[error("The gdal metadata(tiff tag 42112) from {1} is not valid: {0}")]
InvalidGdalMetaData(String, PathBuf),

#[error("Striped tiff file is not supported, the tiff file is {0}")]
NotSupportedChunkType(PathBuf),
}
Loading
Loading