Skip to content

Commit

Permalink
Autodiscover *.tiff files in addition to *.tif (#1635)
Browse files Browse the repository at this point in the history
  • Loading branch information
sharkAndshark authored Dec 29, 2024
1 parent 26249e2 commit 17edc75
Show file tree
Hide file tree
Showing 9 changed files with 79 additions and 38 deletions.
5 changes: 3 additions & 2 deletions docs/src/config-file.md
Original file line number Diff line number Diff line change
Expand Up @@ -202,10 +202,11 @@ mbtiles:
# Cloud Optimized GeoTIFF File Sources
cog:
paths:
# scan this whole dir, matching all *.tif files
# scan this whole dir, matching all *.tif and *.tiff files
- /dir-path
# specific TIFF file will be published as a cog source
- /path/to/cogfile.tif
- /path/to/cogfile1.tif
- /path/to/cogfile2.tiff
sources:
# named source matching source name to a single file
cog-src1: /path/to/cog1.tif
Expand Down
12 changes: 6 additions & 6 deletions docs/src/sources-cog-files.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@ Martin can also serve raster sources like local [COG(Cloud Optimized GeoTIFF)](h
## Run Martin with CLI to serve cog files

```bash
# Configured with a directory containing TIFF files.
# Configured with a directory containing `*.tif` or `*.tiff` 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 dedicated TIFF file
martin /path/to/target1.tif /path/to/target2.tiff
# Configured with a combination of directories and dedicated TIFF files.
martin /with/tiff/files /path/to/target.tif
martin /with/tiff/files /path/to/target1.tif /path/to/target2.tiff
```

## Run Martin with configuration file
Expand All @@ -46,11 +46,11 @@ cache_size_mb: 8

cog:
paths:
# scan this whole dir, matching all *.tif files
# scan this whole dir, matching all *.tif and *.tiff files
- /dir-path
# specific TIFF file will be published as a cog source
- /path/to/target1.tif
- /path/to/target2.tif
- /path/to/target2.tiff
sources:
# named source matching source name to a single file
cog-src1: /path/to/cog1.tif
Expand Down
48 changes: 38 additions & 10 deletions martin/src/args/root.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,17 +99,17 @@ impl Args {

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

#[cfg(feature = "mbtiles")]
if !cli_strings.is_empty() {
config.mbtiles = parse_file_args(&mut cli_strings, "mbtiles", false);
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);
config.cog = parse_file_args(&mut cli_strings, &["tif", "tiff"], false);
}

#[cfg(feature = "sprites")]
Expand All @@ -125,35 +125,40 @@ impl Args {
}
}

#[cfg(any(feature = "pmtiles", feature = "mbtiles"))]
fn is_url(s: &str, extension: &str) -> bool {
#[cfg(any(feature = "pmtiles", feature = "mbtiles", feature = "cog"))]
fn is_url(s: &str, extension: &[&str]) -> bool {
if s.starts_with("http") {
if let Ok(url) = url::Url::parse(s) {
if url.scheme() == "http" || url.scheme() == "https" {
if let Some(ext) = url.path().rsplit('.').next() {
return ext == extension;
return extension.contains(&ext);
}
}
}
}
false
}

#[cfg(any(feature = "pmtiles", feature = "mbtiles"))]
#[cfg(any(feature = "pmtiles", feature = "mbtiles", feature = "cog"))]
pub fn parse_file_args<T: crate::file_config::ConfigExtras>(
cli_strings: &mut Arguments,
extension: &str,
extensions: &[&str],
allow_url: bool,
) -> FileConfigEnum<T> {
use crate::args::State::{Ignore, Share, Take};

let paths = cli_strings.process(|s| {
let path = PathBuf::from(s);
if allow_url && is_url(s, extension) {
if allow_url && is_url(s, extensions) {
Take(path)
} else if path.is_dir() {
Share(path)
} else if path.is_file() && path.extension().map_or(false, |e| e == extension) {
} else if path.is_file()
&& extensions.iter().any(|&expected_ext| {
path.extension()
.is_some_and(|actual_ext| actual_ext == expected_ext)
})
{
Take(path)
} else {
Ignore
Expand All @@ -166,6 +171,8 @@ pub fn parse_file_args<T: crate::file_config::ConfigExtras>(
#[cfg(test)]
mod tests {

use insta::assert_yaml_snapshot;

use super::*;
use crate::args::PreferredEncoding;
use crate::test_utils::FauxEnv;
Expand Down Expand Up @@ -275,4 +282,25 @@ mod tests {
let bad = vec!["foobar".to_string()];
assert!(matches!(err, UnrecognizableConnections(v) if v == bad));
}

#[test]
fn cli_multiple_extensions() {
let args = Args::parse_from([
"martin",
"../tests/fixtures/cog",
"../tests/fixtures/cog/rgba_u8_nodata.tiff",
"../tests/fixtures/cog/rgba_u8.tif",
]);

let env = FauxEnv::default();
let mut config = Config::default();
let err = args.merge_into_config(&mut config, &env);
assert!(err.is_ok());
assert_yaml_snapshot!(config, @r#"
cog:
- "../tests/fixtures/cog/rgb_u8.tif"
- "../tests/fixtures/cog/rgba_u8_nodata.tiff"
- "../tests/fixtures/cog/rgba_u8.tif"
"#);
}
}
6 changes: 3 additions & 3 deletions martin/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -178,21 +178,21 @@ impl Config {
#[cfg(feature = "pmtiles")]
if !self.pmtiles.is_empty() {
let cfg = &mut self.pmtiles;
let val = crate::file_config::resolve_files(cfg, idr, cache.clone(), "pmtiles");
let val = crate::file_config::resolve_files(cfg, idr, cache.clone(), &["pmtiles"]);
sources.push(Box::pin(val));
}

#[cfg(feature = "mbtiles")]
if !self.mbtiles.is_empty() {
let cfg = &mut self.mbtiles;
let val = crate::file_config::resolve_files(cfg, idr, cache.clone(), "mbtiles");
let val = crate::file_config::resolve_files(cfg, idr, cache.clone(), &["mbtiles"]);
sources.push(Box::pin(val));
}

#[cfg(feature = "cog")]
if !self.cog.is_empty() {
let cfg = &mut self.cog;
let val = crate::file_config::resolve_files(cfg, idr, cache.clone(), "tif");
let val = crate::file_config::resolve_files(cfg, idr, cache.clone(), &["tif", "tiff"]);
sources.push(Box::pin(val));
}

Expand Down
40 changes: 26 additions & 14 deletions martin/src/file_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ pub async fn resolve_files<T: SourceConfigExtras>(
config: &mut FileConfigEnum<T>,
idr: &IdResolver,
cache: OptMainCache,
extension: &str,
extension: &[&str],
) -> MartinResult<TileInfoSources> {
resolve_int(config, idr, cache, extension)
.map_err(crate::MartinError::from)
Expand All @@ -246,7 +246,7 @@ async fn resolve_int<T: SourceConfigExtras>(
config: &mut FileConfigEnum<T>,
idr: &IdResolver,
cache: OptMainCache,
extension: &str,
extension: &[&str],
) -> FileResult<TileInfoSources> {
let Some(cfg) = config.extract_file_config(cache)? else {
return Ok(TileInfoSources::default());
Expand Down Expand Up @@ -285,16 +285,20 @@ async fn resolve_int<T: SourceConfigExtras>(

for path in cfg.paths {
if let Some(url) = parse_url(T::parse_urls(), &path)? {
let id = url
.path_segments()
.and_then(Iterator::last)
.and_then(|s| {
// Strip extension and trailing dot, or keep the original string
s.strip_suffix(extension)
.and_then(|s| s.strip_suffix('.'))
.or(Some(s))
})
.unwrap_or("pmt_web_source");
let target_ext = extension.iter().find(|&e| url.to_string().ends_with(e));
let id = if let Some(ext) = target_ext {
url.path_segments()
.and_then(Iterator::last)
.and_then(|s| {
// Strip extension and trailing dot, or keep the original string
s.strip_suffix(ext)
.and_then(|s| s.strip_suffix('.'))
.or(Some(s))
})
.unwrap_or("web_source")
} else {
"web_source"
};

let id = idr.resolve(id, url.to_string());
configs.insert(id.clone(), FileConfigSrc::Path(path));
Expand Down Expand Up @@ -337,13 +341,21 @@ async fn resolve_int<T: SourceConfigExtras>(
Ok(results)
}

fn dir_to_paths(path: &Path, extension: &str) -> Result<Vec<PathBuf>, FileError> {
fn dir_to_paths(path: &Path, extension: &[&str]) -> Result<Vec<PathBuf>, FileError> {
Ok(path
.read_dir()
.map_err(|e| IoError(e, path.to_path_buf()))?
.filter_map(Result::ok)
.filter(|f| {
f.path().extension().filter(|e| *e == extension).is_some() && f.path().is_file()
f.path()
.extension()
.filter(|actual_ext| {
extension
.iter()
.any(|expected_ext| expected_ext == actual_ext)
})
.is_some()
&& f.path().is_file()
})
.map(|f| f.path())
.collect())
Expand Down
2 changes: 1 addition & 1 deletion tests/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ sprites:

cog:
paths:
- tests/fixtures/cog/rgba_u8_nodata.tif
- tests/fixtures/cog/rgba_u8_nodata.tiff
sources:
cog-src1: tests/fixtures/cog/rgba_u8.tif
cog-src2: tests/fixtures/cog/rgb_u8.tif
Expand Down
2 changes: 1 addition & 1 deletion tests/expected/auto/save_config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ cog:
sources:
rgb_u8: tests/fixtures/cog/rgb_u8.tif
rgba_u8: tests/fixtures/cog/rgba_u8.tif
rgba_u8_nodata: tests/fixtures/cog/rgba_u8_nodata.tif
rgba_u8_nodata: tests/fixtures/cog/rgba_u8_nodata.tiff
sprites: tests/fixtures/sprites/src1
fonts:
- tests/fixtures/fonts/overpass-mono-regular.ttf
Expand Down
2 changes: 1 addition & 1 deletion tests/expected/configured/save_config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ cog:
sources:
cog-src1: tests/fixtures/cog/rgba_u8.tif
cog-src2: tests/fixtures/cog/rgb_u8.tif
rgba_u8_nodata: tests/fixtures/cog/rgba_u8_nodata.tif
rgba_u8_nodata: tests/fixtures/cog/rgba_u8_nodata.tiff
sprites:
paths: tests/fixtures/sprites/src1
sources:
Expand Down
File renamed without changes.

0 comments on commit 17edc75

Please sign in to comment.