#[cfg(all(feature = "contour", feature = "_tiles"))]
mod contour;
#[cfg(all(feature = "contour", feature = "_tiles"))]
pub use contour::trace_contour;
#[cfg(all(feature = "hillshade", feature = "_tiles"))]
mod hillshade;
#[cfg(all(any(feature = "hillshade", feature = "contour"), feature = "_tiles"))]
mod neighbourhood;
#[cfg(all(feature = "mlt", feature = "_tiles"))]
mod to_mlt;
#[cfg(all(feature = "mlt", feature = "_tiles"))]
mod to_mvt;
#[cfg(all(feature = "hillshade", feature = "_tiles"))]
pub use hillshade::bake_hillshade;
use martin_core::tiles::Tile;
#[cfg(all(feature = "contour", feature = "_tiles"))]
use martin_core::tiles::contour::ContourError;
#[cfg(all(feature = "hillshade", feature = "_tiles"))]
use martin_core::tiles::hillshade::HillshadeError;
#[cfg(all(feature = "mlt", feature = "_tiles"))]
use martin_tile_utils::Format;
#[cfg(all(feature = "mlt", feature = "_tiles"))]
use to_mlt::convert_mvt_to_mlt;
#[cfg(all(feature = "mlt", feature = "_tiles"))]
use to_mvt::convert_mlt_to_mvt;
#[cfg(all(feature = "contour", feature = "_tiles"))]
use crate::config::file::ContourRangeError;
#[cfg(all(feature = "hillshade", feature = "_tiles"))]
use crate::config::file::HillshadeRangeError;
#[cfg(all(feature = "mlt", feature = "_tiles"))]
use crate::config::file::{MltConversion, MvtConversion, ResolvedProcess};
#[derive(thiserror::Error, Debug)]
pub enum ProcessError {
#[cfg(all(feature = "mlt", feature = "_tiles"))]
#[error("MVT to MLT conversion failed: {0}")]
MltConversion(String),
#[cfg(all(feature = "mlt", feature = "_tiles"))]
#[error("MLT encoding failed: {0}")]
MltEncoding(String),
#[cfg(all(feature = "mlt", feature = "_tiles"))]
#[error("MLT to MVT conversion failed: {0}")]
MvtConversion(String),
#[error("Tile decompression failed: {0}")]
DecompressionFailed(String),
#[cfg(all(feature = "hillshade", feature = "_tiles"))]
#[error(transparent)]
HillshadeParameter(#[from] HillshadeRangeError),
#[cfg(all(feature = "hillshade", feature = "_tiles"))]
#[error("Hillshade failed: {0}")]
Hillshade(#[from] HillshadeError),
#[cfg(all(feature = "hillshade", feature = "_tiles"))]
#[error("Could not read the normal tiles a hillshade needs: {0}")]
HillshadeSource(String),
#[cfg(all(feature = "hillshade", feature = "_tiles"))]
#[error("The normals source changed and must be reloaded")]
HillshadeSourceNeedsReload,
#[cfg(all(feature = "hillshade", feature = "_tiles"))]
#[error("The hillshade bake did not complete: {0}")]
HillshadeBakeFailed(String),
#[cfg(all(feature = "hillshade", feature = "_tiles"))]
#[error("The server is shutting down and cannot start a new hillshade bake")]
HillshadeShuttingDown,
#[cfg(all(feature = "contour", feature = "_tiles"))]
#[error(transparent)]
ContourParameter(#[from] ContourRangeError),
#[cfg(all(feature = "contour", feature = "_tiles"))]
#[error("Contour tracing failed: {0}")]
Contour(#[from] ContourError),
#[cfg(all(feature = "contour", feature = "_tiles"))]
#[error("Could not read the elevation tiles a contour needs: {0}")]
ContourSource(String),
#[cfg(all(feature = "contour", feature = "_tiles"))]
#[error("The elevation source changed and must be reloaded")]
ContourSourceNeedsReload,
#[cfg(all(feature = "contour", feature = "_tiles"))]
#[error("The contour trace did not complete: {0}")]
ContourTraceFailed(String),
#[cfg(all(feature = "contour", feature = "_tiles"))]
#[error("The server is shutting down and cannot start a new contour trace")]
ContourShuttingDown,
}
impl From<ProcessError> for actix_web::Error {
fn from(e: ProcessError) -> Self {
match e {
#[cfg(all(feature = "hillshade", feature = "_tiles"))]
ProcessError::HillshadeParameter(ref inner) => {
actix_web::error::ErrorBadRequest(inner.to_string())
}
#[cfg(all(feature = "hillshade", feature = "_tiles"))]
ProcessError::HillshadeShuttingDown => {
actix_web::error::ErrorServiceUnavailable(e.to_string())
}
#[cfg(all(feature = "contour", feature = "_tiles"))]
ProcessError::ContourParameter(ref inner) => {
actix_web::error::ErrorBadRequest(inner.to_string())
}
#[cfg(all(feature = "contour", feature = "_tiles"))]
ProcessError::ContourShuttingDown => {
actix_web::error::ErrorServiceUnavailable(e.to_string())
}
other => actix_web::error::ErrorInternalServerError(other.to_string()),
}
}
}
pub fn apply_pre_cache_processors(
tile: Tile,
#[cfg(all(feature = "mlt", feature = "_tiles"))] config: &ResolvedProcess,
#[cfg(all(feature = "mlt", feature = "_tiles"))] accepted: Option<Format>,
) -> Result<Tile, ProcessError> {
if tile.data.is_empty() {
return Ok(tile);
}
#[cfg(all(feature = "mlt", feature = "_tiles"))]
let tile = if accepted == Some(Format::Mlt) && tile.info.format == Format::Mvt {
match config.mlt {
MltConversion::Encode(cfg) => convert_mvt_to_mlt(tile, cfg)?,
MltConversion::Disabled => tile,
}
} else if accepted == Some(Format::Mvt)
&& tile.info.format == Format::Mlt
&& config.mvt == MvtConversion::Encode
{
convert_mlt_to_mvt(tile)?
} else {
tile
};
Ok(tile)
}
#[cfg(test)]
mod tests {
#[cfg(all(feature = "mlt", feature = "_tiles"))]
use martin_core::tiles::Tile;
#[cfg(all(feature = "mlt", feature = "_tiles"))]
use martin_tile_utils::{Encoding, Format, TileInfo};
#[cfg(all(feature = "mlt", feature = "_tiles"))]
use rstest::rstest;
#[cfg(all(feature = "mlt", feature = "_tiles"))]
use super::to_mvt::{empty_layer_mvt_bytes, mvt_with_feature_bytes};
#[cfg(all(feature = "mlt", feature = "_tiles"))]
use super::*;
#[cfg(all(feature = "mlt", feature = "_tiles"))]
fn make_tile(data: Vec<u8>, format: Format, encoding: Encoding) -> Tile {
Tile::new_hash_etag(data, TileInfo::new(format, encoding))
}
#[cfg(all(feature = "mlt", feature = "_tiles"))]
#[rstest]
#[case::mvt_unc_mlt(Format::Mvt, Encoding::Uncompressed, Format::Mlt)]
#[case::mlt_unc_mvt(Format::Mlt, Encoding::Uncompressed, Format::Mvt)]
fn empty_tile_is_noop(
#[case] format: Format,
#[case] encoding: Encoding,
#[case] target: Format,
) {
let tile = make_tile(Vec::new(), format, encoding);
let result =
apply_pre_cache_processors(tile, &ResolvedProcess::default(), Some(target)).unwrap();
assert!(result.data.is_empty());
}
#[cfg(all(feature = "mlt", feature = "_tiles"))]
#[test]
fn mvt_request_is_noop() {
let tile = make_tile(vec![1, 2, 3], Format::Mvt, Encoding::Uncompressed);
let config = ResolvedProcess::default();
let result = apply_pre_cache_processors(tile, &config, Some(Format::Mvt)).unwrap();
assert_eq!(result.data, vec![1, 2, 3]);
assert_eq!(result.info.format, Format::Mvt);
}
#[cfg(all(feature = "mlt", feature = "_tiles"))]
#[test]
fn no_accept_header_is_noop() {
let tile = make_tile(vec![1, 2, 3], Format::Mvt, Encoding::Uncompressed);
let result = apply_pre_cache_processors(tile, &ResolvedProcess::default(), None).unwrap();
assert_eq!(result.data, vec![1, 2, 3]);
assert_eq!(result.info.format, Format::Mvt);
}
#[cfg(all(feature = "mlt", feature = "_tiles"))]
#[test]
fn non_mvt_source_with_mlt_accept_is_noop() {
let tile = make_tile(vec![1, 2, 3], Format::Png, Encoding::Internal);
let result =
apply_pre_cache_processors(tile, &ResolvedProcess::default(), Some(Format::Mlt))
.unwrap();
assert_eq!(result.info.format, Format::Png);
}
#[cfg(all(feature = "mlt", feature = "_tiles"))]
#[test]
fn mlt_accept_converts_mvt_with_default_encoder() {
let tile = make_tile(empty_layer_mvt_bytes(), Format::Mvt, Encoding::Uncompressed);
let result =
apply_pre_cache_processors(tile, &ResolvedProcess::default(), Some(Format::Mlt))
.unwrap();
assert_eq!(result.info.format, Format::Mlt);
assert_eq!(result.info.encoding, Encoding::Internal);
}
#[cfg(all(feature = "mlt", feature = "_tiles"))]
#[test]
fn mlt_accept_uses_explicit_encoder_overrides() {
let tile = make_tile(empty_layer_mvt_bytes(), Format::Mvt, Encoding::Uncompressed);
let config = ResolvedProcess::default();
let result = apply_pre_cache_processors(tile, &config, Some(Format::Mlt)).unwrap();
assert_eq!(result.info.format, Format::Mlt);
}
#[cfg(all(feature = "mlt", feature = "_tiles"))]
#[test]
fn mlt_accept_with_disabled_serves_mvt_unchanged() {
let tile = make_tile(empty_layer_mvt_bytes(), Format::Mvt, Encoding::Uncompressed);
let config = ResolvedProcess {
mlt: MltConversion::Disabled,
..Default::default()
};
let result = apply_pre_cache_processors(tile, &config, Some(Format::Mlt)).unwrap();
assert_eq!(result.info.format, Format::Mvt);
assert_eq!(result.data, empty_layer_mvt_bytes());
}
#[cfg(all(feature = "mlt", feature = "_tiles"))]
#[test]
fn compressed_mvt_decompressed_and_converted() {
use martin_tile_utils::encode_gzip;
let gzipped = encode_gzip(&empty_layer_mvt_bytes()).unwrap();
let tile = make_tile(gzipped, Format::Mvt, Encoding::Gzip);
let result =
apply_pre_cache_processors(tile, &ResolvedProcess::default(), Some(Format::Mlt))
.unwrap();
assert_eq!(result.info.format, Format::Mlt);
assert_eq!(result.info.encoding, Encoding::Internal);
}
#[cfg(all(feature = "mlt", feature = "_tiles"))]
fn mvt_with_feature() -> Vec<u8> {
mvt_with_feature_bytes()
}
#[cfg(all(feature = "mlt", feature = "_tiles"))]
#[test]
fn mlt_to_mvt_round_trip() {
let original = make_tile(mvt_with_feature(), Format::Mvt, Encoding::Uncompressed);
let encoded =
apply_pre_cache_processors(original, &ResolvedProcess::default(), Some(Format::Mlt))
.unwrap();
assert_eq!(encoded.info.format, Format::Mlt);
assert!(!encoded.data.is_empty(), "MLT tile should have data");
let decoded =
apply_pre_cache_processors(encoded, &ResolvedProcess::default(), Some(Format::Mvt))
.unwrap();
assert_eq!(decoded.info.format, Format::Mvt);
assert_eq!(decoded.info.encoding, Encoding::Uncompressed);
assert!(!decoded.data.is_empty());
}
#[cfg(all(feature = "mlt", feature = "_tiles"))]
#[test]
fn conversion_suffixes_source_etag() {
let tile = Tile::new_with_etag(
mvt_with_feature_bytes(),
TileInfo::new(Format::Mvt, Encoding::Uncompressed),
"upstream-etag".to_owned(),
);
let mlt = apply_pre_cache_processors(tile, &ResolvedProcess::default(), Some(Format::Mlt))
.unwrap();
assert_eq!(mlt.info.format, Format::Mlt);
assert_eq!(mlt.etag, "upstream-etag+mlt");
let mvt = apply_pre_cache_processors(mlt, &ResolvedProcess::default(), Some(Format::Mvt))
.unwrap();
assert_eq!(mvt.info.format, Format::Mvt);
assert_eq!(mvt.etag, "upstream-etag+mlt+mvt");
}
#[cfg(all(feature = "mlt", feature = "_tiles"))]
#[test]
fn mlt_source_with_mvt_accept_converts() {
let original = make_tile(mvt_with_feature(), Format::Mvt, Encoding::Uncompressed);
let encoded =
apply_pre_cache_processors(original, &ResolvedProcess::default(), Some(Format::Mlt))
.unwrap();
assert!(!encoded.data.is_empty());
let tile = make_tile(encoded.data, Format::Mlt, Encoding::Uncompressed);
let result =
apply_pre_cache_processors(tile, &ResolvedProcess::default(), Some(Format::Mvt))
.unwrap();
assert_eq!(result.info.format, Format::Mvt);
}
}