use std::fmt::Debug;
use geo::{BoundingRect as _, Distance, MapCoords, geometry};
use crate::{
BoxedImageDataRef, ImageFeature, Projection, Result, YAxisDirection, map,
styling::{self},
transform,
};
mod rect;
#[derive(Debug)]
pub struct ViewController
{
projection: Projection,
map_rect: geo::Rect,
view_rect: geo::Rect,
}
impl ViewController
{
pub fn set_projection(&mut self, proj: Projection) -> Result<()>
{
self.map_rect = transform(&self.projection, &proj, &self.map_rect)?;
self.projection = proj;
Ok(())
}
pub fn show_extent(map_rect: geo::Rect, view_rect: geo::Rect) -> Self
{
Self {
projection: Projection::wgs84(),
map_rect,
view_rect,
}
}
fn coord_to_view(&self, pt: &geo::Coord, y_axis_direction: YAxisDirection) -> geo::Coord
{
let x = pt.x;
let y = pt.y;
let x = self.view_rect.min().x
+ (x - self.map_rect.min().x) / self.map_rect.width() * self.view_rect.width();
let y = match y_axis_direction
{
YAxisDirection::South =>
{
self.view_rect.min().y
+ (y - self.map_rect.min().y) / self.map_rect.height() * self.view_rect.height()
}
YAxisDirection::North =>
{
self.view_rect.max().y
- (y - self.map_rect.min().y) / self.map_rect.height() * self.view_rect.height()
}
};
geo::Coord { x, y }
}
pub(crate) fn map_rect_ref(&self) -> &geo::Rect
{
&self.map_rect
}
pub fn map_rect(&self) -> geo::Rect
{
self.map_rect
}
pub fn set_map_rect(&mut self, rect: geo::Rect)
{
self.map_rect = rect
}
pub(crate) fn view_to_coord(&self, pt: &geo::Coord) -> geo::Coord
{
let motion = self.motion_view_to_coord(pt);
geo::Coord {
x: self.map_rect.min().x + motion.x,
y: self.map_rect.min().y + motion.y,
}
}
pub(crate) fn motion_view_to_coord(&self, m: &geo::Coord) -> geo::Coord
{
let x = m.x;
let y = m.y;
geo::Coord {
x: (x - self.view_rect.min().x) / (self.view_rect.width()) * (self.map_rect.width()),
y: (y - self.view_rect.min().y) / (self.view_rect.height()) * (self.map_rect.height()),
}
}
pub(crate) fn set_view(&mut self, view_rect: geo::Rect)
{
self.view_rect = view_rect;
}
fn geometry_to_view(
&self,
projection: &Projection,
geo: geometry::Geometry,
) -> crate::Result<geometry::Geometry>
{
geo.try_map_coords(|coord| {
let coord = {
if projection == &self.projection
{
coord
}
else
{
transform(projection, &self.projection, &coord)?
}
};
Ok(self.coord_to_view(&coord, projection.y_axis_direction()))
})
}
fn rect_to_view(&self, rect: geo::Rect, y_axis_direction: YAxisDirection) -> geo::Rect
{
let top_left = self.coord_to_view(&rect.min(), y_axis_direction);
let bottom_right = self.coord_to_view(&rect.max(), y_axis_direction);
geo::Rect::new(top_left, bottom_right)
}
pub(crate) fn zoom(&mut self, factor: f64)
{
let (x, y) = self.map_rect.center().x_y();
let w = self.map_rect.width();
let h = self.map_rect.height();
self.map_rect = rect::from_center_size(x, y, factor * w, factor * h);
}
pub(crate) fn pan(&mut self, x: f64, y: f64)
{
self.map_rect = match self.projection.y_axis_direction()
{
YAxisDirection::North => rect::pan(&self.map_rect, x, -y),
YAxisDirection::South => rect::pan(&self.map_rect, x, y),
}
}
pub(crate) fn view_rect(&self) -> geo::Rect
{
self.view_rect.to_owned()
}
pub fn zoom_level(&self) -> f64
{
let map_rect_wgs = transform(&self.projection, &Projection::wgs84(), &self.map_rect).unwrap();
let c = 40075016.686;
let left_right_distance = if map_rect_wgs.min().x == -180.0 && map_rect_wgs.max().x == 180.0
{
c
}
else
{
geo::Haversine.distance(
geo::Point::new(map_rect_wgs.min().x, 0.0),
geo::Point::new(map_rect_wgs.max().x, 0.0),
)
};
let spixel = left_right_distance / (self.view_rect.max().x - self.view_rect.min().x);
(c / spixel).ln() / 2.0f64.ln() - 8.0
}
}
pub trait MapRenderer<TFeature: crate::Feature>
{
fn draw_point(
&mut self,
rendering_state: &styling::RenderingState,
feature: &TFeature,
point: &geo::Point,
symbol: &styling::Symbol<TFeature>,
);
fn draw_polygon(
&mut self,
rendering_state: &styling::RenderingState,
feature: &TFeature,
polygon: &geo::Polygon,
symbol: &styling::Symbol<TFeature>,
);
fn draw_line_string(
&mut self,
rendering_state: &styling::RenderingState,
feature: &TFeature,
line: &geo::LineString,
symbol: &styling::Symbol<TFeature>,
)
{
let _ = (rendering_state, feature, line, symbol);
}
fn draw_image(
&mut self,
rendering_state: &styling::RenderingState,
feature: &TFeature,
image: ImageFeature<BoxedImageDataRef<'_>>,
target_rect: geo::Rect,
);
fn draw_label(
&mut self,
rendering_state: &styling::RenderingState,
feature: &TFeature,
anchor: geo::Point,
config: &styling::LabelConfig<TFeature>,
);
fn draw_geometry(
&mut self,
rendering_state: &styling::RenderingState,
feature: &TFeature,
geometry: &geo::Geometry,
symbol: &styling::Symbol<TFeature>,
)
{
match geometry
{
geo::Geometry::Point(point) => self.draw_point(rendering_state, feature, point, symbol),
geo::Geometry::Line(_) => log::error!("Line is not supported."),
geo::Geometry::LineString(line) =>
{
self.draw_line_string(rendering_state, feature, line, symbol)
}
geo::Geometry::Polygon(poly) => self.draw_polygon(rendering_state, feature, poly, symbol),
geo::Geometry::MultiPoint(mp) =>
{
for point in mp.iter()
{
self.draw_point(rendering_state, feature, point, symbol);
}
}
geo::Geometry::MultiLineString(mls) =>
{
for line in mls.iter()
{
self.draw_line_string(rendering_state, feature, line, symbol);
}
}
geo::Geometry::MultiPolygon(mp) =>
{
for poly in mp.iter()
{
self.draw_polygon(rendering_state, feature, poly, symbol);
}
}
geo::Geometry::GeometryCollection(_) => log::error!("GeometryCollection is not supported."),
geo::Geometry::Rect(_) => log::error!("Rect is not supported."),
geo::Geometry::Triangle(_) => log::error!("Triangle is not supported."),
}
}
fn start_map(&mut self) {}
fn draw_background(&mut self, background_color: &styling::Rgba, size: (f64, f64));
fn draw_map(
&mut self,
map: &map::Map<TFeature>,
view_controller: &ViewController,
style: &styling::Style<TFeature>,
) where
TFeature: map::Feature,
{
self.start_map();
self.draw_background(
&style.background_color(),
(
view_controller.view_rect().width(),
view_controller.view_rect().height(),
),
);
let rendering_state = styling::RenderingState {
zoom_level: view_controller.zoom_level(),
};
for layer in map.visible_layers()
{
let map_rect = transform(
&view_controller.projection,
layer.projection(),
view_controller.map_rect_ref(),
)
.unwrap();
for feat in layer.features(map_rect, rendering_state.zoom_level)
{
if let Some(image) = feat.image()
{
let image_bbox = image.image_data().bounding_box();
let image_rect_view =
transform(layer.projection(), &view_controller.projection, &image_bbox).unwrap();
let target_rect = view_controller.rect_to_view(
image_rect_view,
view_controller.projection.y_axis_direction(),
);
self.draw_image(&rendering_state, &feat, image, target_rect);
}
}
for r in style.rules()
{
for feat in layer.features(map_rect, rendering_state.zoom_level)
{
if (r.check)(&rendering_state, &feat)
&& let Some(geometry) = feat.geometry()
&& let Ok(geometry) = view_controller.geometry_to_view(layer.projection(), geometry)
{
self.draw_geometry(&rendering_state, &feat, &geometry, &r.symbol);
if let Some(label) = &r.symbol.label
&& let Some(anchor) = geometry_label_anchor(&geometry)
{
self.draw_label(&rendering_state, &feat, anchor, label);
}
}
}
}
}
}
}
fn geometry_label_anchor(geometry: &geo::Geometry) -> Option<geo::Point>
{
geometry
.bounding_rect()
.map(|rect| geo::Point::from(rect.center()))
}
#[cfg(test)]
mod tests
{
use super::*;
#[test]
fn test_view_controller()
{
let controller = ViewController::show_extent(
geo::Rect::new(
geo::Coord {
x: -180.0,
y: -80.0,
},
geo::Coord { x: 180.0, y: 80.0 },
),
geo::Rect::new(
geo::Coord { x: 0.0, y: 0.0 },
geo::Coord { x: 512.0, y: 512.0 },
),
);
let p = controller.view_to_coord(&(256.0, 256.0).into());
assert!(p.x == 0.0);
assert!(p.y == 0.0);
}
}