use crate::{BoxedLayer, Feature, Layer, Projection};
use ccutils::containers::RefOrValue;
struct ZoomBand<TFeature: Feature>
{
max_zoom: f64,
layer: BoxedLayer<TFeature>,
}
pub struct ZoomSplitLayer<TFeature: Feature>
{
bands: Vec<ZoomBand<TFeature>>,
projection: Projection,
}
impl<TFeature: Feature> ZoomSplitLayer<TFeature>
{
fn new(bands: Vec<ZoomBand<TFeature>>) -> Self
{
let projection = bands
.first()
.map(|b| b.layer.projection().clone())
.unwrap_or_else(Projection::wgs84);
Self { bands, projection }
}
}
impl<TFeature: Feature> Layer<TFeature> for ZoomSplitLayer<TFeature>
{
fn projection(&self) -> &Projection
{
&self.projection
}
fn features<'a>(
&'a self,
rect: geo::Rect,
zoom_level: f64,
) -> Box<dyn Iterator<Item = RefOrValue<'a, TFeature>> + 'a>
{
match self.bands.iter().find(|b| zoom_level < b.max_zoom)
{
Some(band) => band.layer.features(rect, zoom_level),
None => Box::new(std::iter::empty()),
}
}
}
pub struct ZoomSplitLayerBuilder<TFeature: Feature>
{
bands: Vec<ZoomBand<TFeature>>,
}
impl<TFeature: Feature> Default for ZoomSplitLayerBuilder<TFeature>
{
fn default() -> Self
{
Self::new()
}
}
impl<TFeature: Feature> ZoomSplitLayerBuilder<TFeature>
{
pub fn new() -> Self
{
Self { bands: Vec::new() }
}
pub fn add_band(mut self, max_zoom: f64, layer: impl Layer<TFeature> + 'static) -> Self
{
self.bands.push(ZoomBand {
max_zoom,
layer: Box::new(layer),
});
self
}
pub fn build(mut self) -> ZoomSplitLayer<TFeature>
{
self
.bands
.sort_by(|a, b| a.max_zoom.partial_cmp(&b.max_zoom).unwrap());
ZoomSplitLayer::new(self.bands)
}
}
#[cfg(test)]
mod tests
{
use super::*;
use crate::{FeaturesVecLayer, GeometryRef};
use std::sync::{Arc, Mutex};
struct Pt(geo::Geometry);
impl GeometryRef for Pt
{
fn geometry_ref(&self) -> &geo::Geometry
{
&self.0
}
}
fn pt(x: f64, y: f64) -> Pt
{
Pt(geo::Geometry::Point(geo::Point::new(x, y)))
}
struct SpyLayer
{
inner: FeaturesVecLayer<Pt>,
calls: Arc<Mutex<Vec<f64>>>,
}
impl SpyLayer
{
fn new(features: Vec<Pt>) -> (Self, Arc<Mutex<Vec<f64>>>)
{
let calls = Arc::new(Mutex::new(Vec::new()));
(
Self {
inner: features.into(),
calls: calls.clone(),
},
calls,
)
}
}
impl Layer<Pt> for SpyLayer
{
fn projection(&self) -> &Projection
{
self.inner.projection()
}
fn features<'a>(
&'a self,
rect: geo::Rect,
zoom_level: f64,
) -> Box<dyn Iterator<Item = RefOrValue<'a, Pt>> + 'a>
{
self.calls.lock().unwrap().push(zoom_level);
self.inner.features(rect, zoom_level)
}
}
fn world_rect() -> geo::Rect
{
geo::Rect::new(
geo::coord! { x: -180.0, y: -90.0 },
geo::coord! { x: 180.0, y: 90.0 },
)
}
#[test]
fn selects_coarse_band_at_low_zoom()
{
let (coarse, coarse_calls) = SpyLayer::new(vec![pt(0.0, 0.0)]);
let (fine, _fine_calls) = SpyLayer::new(vec![]);
let lod = ZoomSplitLayerBuilder::new()
.add_band(8.0, coarse)
.add_band(f64::INFINITY, fine)
.build();
let _: Vec<_> = lod.features(world_rect(), 5.0).collect();
assert_eq!(*coarse_calls.lock().unwrap(), vec![5.0]);
}
#[test]
fn selects_fine_band_at_high_zoom()
{
let (coarse, coarse_calls) = SpyLayer::new(vec![]);
let (fine, fine_calls) = SpyLayer::new(vec![pt(1.0, 1.0)]);
let lod = ZoomSplitLayerBuilder::new()
.add_band(8.0, coarse)
.add_band(f64::INFINITY, fine)
.build();
let _: Vec<_> = lod.features(world_rect(), 10.0).collect();
assert!(coarse_calls.lock().unwrap().is_empty());
assert_eq!(*fine_calls.lock().unwrap(), vec![10.0]);
}
#[test]
fn boundary_zoom_picks_upper_band()
{
let (coarse, coarse_calls) = SpyLayer::new(vec![]);
let (fine, fine_calls) = SpyLayer::new(vec![]);
let lod = ZoomSplitLayerBuilder::new()
.add_band(8.0, coarse)
.add_band(f64::INFINITY, fine)
.build();
let _: Vec<_> = lod.features(world_rect(), 8.0).collect();
assert!(coarse_calls.lock().unwrap().is_empty());
assert!(!fine_calls.lock().unwrap().is_empty());
}
#[test]
fn builder_sorts_out_of_order_bands()
{
let (coarse, coarse_calls) = SpyLayer::new(vec![pt(0.0, 0.0)]);
let (fine, _) = SpyLayer::new(vec![]);
let lod = ZoomSplitLayerBuilder::new()
.add_band(f64::INFINITY, fine)
.add_band(8.0, coarse)
.build();
let _: Vec<_> = lod.features(world_rect(), 3.0).collect();
assert!(!coarse_calls.lock().unwrap().is_empty());
}
#[test]
fn empty_builder_returns_empty_iterator()
{
let lod: ZoomSplitLayer<Pt> = ZoomSplitLayerBuilder::new().build();
let result: Vec<_> = lod.features(world_rect(), 10.0).collect();
assert!(result.is_empty());
}
#[test]
fn three_band_selection()
{
let (coarse, coarse_calls) = SpyLayer::new(vec![]);
let (medium, medium_calls) = SpyLayer::new(vec![]);
let (fine, fine_calls) = SpyLayer::new(vec![]);
let lod = ZoomSplitLayerBuilder::new()
.add_band(8.0, coarse)
.add_band(13.0, medium)
.add_band(f64::INFINITY, fine)
.build();
let _: Vec<_> = lod.features(world_rect(), 6.0).collect();
let _: Vec<_> = lod.features(world_rect(), 10.0).collect();
let _: Vec<_> = lod.features(world_rect(), 15.0).collect();
assert_eq!(coarse_calls.lock().unwrap().len(), 1);
assert_eq!(medium_calls.lock().unwrap().len(), 1);
assert_eq!(fine_calls.lock().unwrap().len(), 1);
}
}