use std::path::{Path, PathBuf};
use std::sync::Arc;
use bytes::Bytes;
use super::style::{
StyleRule, VectorTileLineSymbol, VectorTilePolygonSymbol, VectorTileStyle, VectorTileSymbol,
};
use super::tile_provider::loader::WebVtLoader;
use super::tile_provider::processor::VectorTileProcessor;
use super::tile_provider::VectorTileProvider;
use super::VectorTileLayer;
use crate::error::GalileoError;
use crate::layer::attribution::Attribution;
use crate::layer::data_provider::{FileCacheController, PersistentCacheController, UrlSource};
use crate::layer::Layer;
use crate::tile_schema::TileIndex;
use crate::{Color, Messenger, TileSchema};
pub struct VectorTileLayerBuilder {
provider_type: ProviderType,
style: Option<VectorTileStyle>,
tile_schema: Option<TileSchema>,
messenger: Option<Box<dyn Messenger>>,
cache: CacheType,
offline_mode: bool,
attribution: Option<Attribution>,
}
enum ProviderType {
Rest(Box<dyn UrlSource<TileIndex>>),
Custom(VectorTileProvider),
}
enum CacheType {
None,
File(PathBuf),
Custom(Box<dyn PersistentCacheController<str, Bytes>>),
}
impl VectorTileLayerBuilder {
pub fn new_rest(tile_source: impl UrlSource<TileIndex> + 'static) -> Self {
Self {
provider_type: ProviderType::Rest(Box::new(tile_source)),
style: None,
tile_schema: None,
messenger: None,
cache: CacheType::None,
offline_mode: false,
attribution: None,
}
}
pub fn new_with_provider(provider: VectorTileProvider) -> Self {
Self {
provider_type: ProviderType::Custom(provider),
style: None,
tile_schema: None,
messenger: None,
cache: CacheType::None,
offline_mode: false,
attribution: None,
}
}
pub fn with_file_cache(mut self, path: impl AsRef<Path>) -> Self {
self.cache = CacheType::File(path.as_ref().into());
self
}
pub fn with_attribution(mut self, text: String, url: String) -> Self {
self.attribution = Some(Attribution::new(text, Some(url)));
self
}
pub fn with_file_cache_checked(self, _path: impl AsRef<Path>) -> Self {
#[allow(unused_mut)]
let mut this = self;
#[cfg(not(target_arch = "wasm32"))]
{
this = this.with_file_cache(_path);
}
this
}
pub fn with_cache_controller(
mut self,
cache: impl PersistentCacheController<str, Bytes> + 'static,
) -> Self {
self.cache = CacheType::Custom(Box::new(cache));
self
}
pub fn with_offline_mode(mut self) -> Self {
self.offline_mode = true;
self
}
pub fn with_tile_schema(mut self, tile_schema: TileSchema) -> Self {
self.tile_schema = Some(tile_schema);
self
}
pub fn with_messenger(mut self, messenger: impl Messenger + 'static) -> Self {
self.messenger = Some(Box::new(messenger));
self
}
pub fn with_style(mut self, style: VectorTileStyle) -> Self {
self.style = Some(style);
self
}
pub fn build(self) -> Result<VectorTileLayer, GalileoError> {
let Self {
provider_type,
style,
tile_schema,
messenger,
cache,
offline_mode,
attribution,
} = self;
let tile_schema = tile_schema.unwrap_or_else(|| TileSchema::web(18));
let cache_controller: Option<Box<dyn PersistentCacheController<str, Bytes>>> = match cache {
CacheType::None => None,
CacheType::File(path_buf) => Some(Box::new(FileCacheController::new(&path_buf)?)),
CacheType::Custom(persistent_cache_controller) => Some(persistent_cache_controller),
};
if cache_controller.is_none() && offline_mode {
return Err(GalileoError::Configuration(
"offline mode cannot be used without cache".into(),
));
}
let processor = Self::create_processor(tile_schema.clone());
let provider = match provider_type {
ProviderType::Rest(url_source) => {
let loader = WebVtLoader::new(cache_controller, url_source, offline_mode);
VectorTileProvider::new(Arc::new(loader), Arc::new(processor))
}
ProviderType::Custom(raster_tile_provider) => {
if cache_controller.is_some() {
return Err(GalileoError::Configuration(
"custom tile provider cannot be used together with a cache controller"
.into(),
));
}
raster_tile_provider
}
};
let style = style.unwrap_or_else(Self::default_style);
let mut layer = VectorTileLayer::new(provider, style, tile_schema, attribution);
if let Some(messenger) = messenger {
layer.set_messenger(messenger);
}
Ok(layer)
}
fn create_processor(tile_schema: TileSchema) -> impl VectorTileProcessor {
#[cfg(target_arch = "wasm32")]
{
crate::platform::web::vt_processor::WebWorkerVtProcessor::new(
tile_schema.clone(),
crate::platform::web::web_workers::WebWorkerService::instance(),
)
}
#[cfg(not(target_arch = "wasm32"))]
{
crate::platform::native::vt_processor::ThreadVtProcessor::new(tile_schema.clone())
}
}
fn default_style() -> VectorTileStyle {
VectorTileStyle {
rules: vec![
StyleRule {
layer_name: None,
properties: Default::default(),
symbol: VectorTileSymbol::Line(VectorTileLineSymbol {
width: 1.0,
stroke_color: Color::BLACK,
}),
},
StyleRule {
layer_name: None,
properties: Default::default(),
symbol: VectorTileSymbol::Polygon(VectorTilePolygonSymbol {
fill_color: Color::GRAY,
}),
},
],
background: Color::WHITE,
}
}
}
#[cfg(test)]
mod tests {
use insta::assert_compact_debug_snapshot;
use super::*;
fn custom_provider() -> VectorTileProvider {
VectorTileLayerBuilder::new_rest(|_| unimplemented!())
.build()
.unwrap()
.provider()
.clone()
}
#[test]
fn with_file_cache_replaces_cache_controller() {
let cache = FileCacheController::new("target").unwrap();
let builder = VectorTileLayerBuilder::new_rest(|_| unimplemented!())
.with_cache_controller(cache)
.with_file_cache("target");
assert!(matches!(builder.cache, CacheType::File(_)));
}
#[test]
fn with_file_cache_fails_build_if_cannot_init_folder() {
let result = VectorTileLayerBuilder::new_rest(|_| unimplemented!())
.with_file_cache("Cargo.toml")
.build();
assert!(result.is_err());
assert_compact_debug_snapshot!(result, @r#"Err(FsIo("failed to initialize file cache folder \"Cargo.toml\": File exists (os error 17)"))"#);
}
#[test]
fn with_file_cache_fails_build_if_custom_provider() {
let provider = custom_provider();
let result = VectorTileLayerBuilder::new_with_provider(provider)
.with_file_cache("target")
.build();
assert!(result.is_err());
assert_compact_debug_snapshot!(result, @r#"Err(Configuration("custom tile provider cannot be used together with a cache controller"))"#);
}
#[test]
fn with_cache_controller_replaces_file_cache() {
let cache = FileCacheController::new("target").unwrap();
let builder = VectorTileLayerBuilder::new_rest(|_| unimplemented!())
.with_file_cache("target")
.with_cache_controller(cache);
assert!(matches!(builder.cache, CacheType::Custom(_)));
}
#[test]
fn with_cache_controller_fails_build_if_custom_provider() {
let provider = custom_provider();
let cache = FileCacheController::new("target").unwrap();
let result = VectorTileLayerBuilder::new_with_provider(provider)
.with_cache_controller(cache)
.build();
assert!(result.is_err());
assert_compact_debug_snapshot!(result, @r#"Err(Configuration("custom tile provider cannot be used together with a cache controller"))"#);
}
#[test]
fn with_offline_mode_incompatible_with_custom_provider() {
let provider = custom_provider();
let result = VectorTileLayerBuilder::new_with_provider(provider)
.with_file_cache("target")
.with_offline_mode()
.build();
assert!(result.is_err());
assert_compact_debug_snapshot!(result, @r#"Err(Configuration("custom tile provider cannot be used together with a cache controller"))"#);
}
#[test]
fn with_offline_mode_does_not_work_without_cache() {
let result = VectorTileLayerBuilder::new_rest(|_| unimplemented!())
.with_offline_mode()
.build();
assert!(result.is_err());
assert_compact_debug_snapshot!(result, @r#"Err(Configuration("offline mode cannot be used without cache"))"#);
}
#[test]
fn default_tile_schema() {
let layer = VectorTileLayerBuilder::new_rest(|_| unimplemented!())
.build()
.unwrap();
assert_eq!(*layer.tile_schema().as_ref().unwrap(), TileSchema::web(18));
}
}