use std::fmt::{self, Debug, Display, Formatter};
use crate::google::style::{PolygonStyle, PolylineStyle};
use crate::google::utils::{FormatterExt, JavaScript, RawIdent};
use crate::{BoundingBox, Location};
pub mod style;
mod utils;
const MAP_IDENT: RawIdent<'static> = RawIdent("__map");
#[derive(Debug)]
pub struct GoogleMap {
apikey: String,
page_title: Option<String>,
center: Location,
zoom: u8,
map_type: Option<MapType>,
disable_default_gui: Option<bool>,
disable_double_click_zoom: Option<bool>,
shapes: Vec<Box<dyn Shape>>,
}
impl GoogleMap {
pub fn new<'a>(
center: impl Into<Location>,
zoom: u8,
apikey: impl Into<Option<&'a str>>,
) -> Self {
GoogleMap {
apikey: apikey.into().unwrap_or("").to_string(),
page_title: None,
center: center.into(),
zoom,
map_type: None,
disable_default_gui: None,
disable_double_click_zoom: None,
shapes: Vec::default(),
}
}
pub fn page_title(&mut self, value: impl AsRef<str>) -> &mut Self {
self.page_title = Some(value.as_ref().to_string());
self
}
pub fn map_type(&mut self, value: MapType) -> &mut Self {
self.map_type = Some(value);
self
}
pub fn disable_default_gui(&mut self, value: bool) -> &mut Self {
self.disable_default_gui = Some(value);
self
}
pub fn disable_double_click_zoom(&mut self, value: bool) -> &mut Self {
self.disable_double_click_zoom = Some(value);
self
}
pub fn draw(&mut self, shape: impl Shape + 'static) -> &mut Self {
self.shapes.push(Box::new(shape));
self
}
pub fn draw_all(
&mut self,
shapes: impl IntoIterator<Item = impl Shape + 'static>,
) -> &mut Self {
for shape in shapes {
self.shapes.push(Box::new(shape))
}
self
}
}
impl JavaScript for GoogleMap {
fn fmt_js(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.write_str("\t\tconst ")?;
MAP_IDENT.fmt_js(f)?;
f.write_str(" = new google.maps.Map(document.getElementById(\"map_canvas\"), ")?;
f.write_object()
.entry("center", &self.center)
.entry("zoom", &self.zoom)
.entry_opt("mapTypeId", &self.map_type)
.entry_opt("disableDefaultUI", &self.disable_default_gui)
.entry_opt("disableDoubleClickZoom", &self.disable_double_click_zoom)
.finish()?;
f.write_str(");\n\n")?;
for shape in &self.shapes {
f.write_str("\t\t")?;
shape.fmt_js(f)?;
f.write_str(";\n")?;
}
Ok(())
}
}
impl Display for GoogleMap {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(
f,
r#"
<html>
<head>
<meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
<meta http-equiv="content-type" content="text/html; charset=UTF-8"/>
<title>{title}</title>
<script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?libraries=visualization&sensor=true_or_false&key={apikey}"></script>
<script type="text/javascript">
function initialize() {{
"#,
title = if let Some(t) = &self.page_title {
t.as_str()
} else {
"Google Maps - mapplot"
},
apikey = self.apikey
)?;
self.fmt_js(f)?;
write!(
f,
r#"
}}
</script>
</head>
<body style="margin:0px; padding:0px;" onload="initialize()">
<div id="map_canvas" style="width: 100%; height: 100%;"></div>
</body>
</html>
"#
)
}
}
#[derive(Debug, Copy, Clone)]
pub enum MapType {
Roadmap,
Satellite,
Hybrid,
Terrain,
}
impl JavaScript for MapType {
fn fmt_js(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
MapType::Roadmap => f.write_str("google.maps.MapTypeId.ROADMAP"),
MapType::Satellite => f.write_str("google.maps.MapTypeId.SATELLITE"),
MapType::Hybrid => f.write_str("google.maps.MapTypeId.HYBRID"),
MapType::Terrain => f.write_str("google.maps.MapTypeId.TERRAIN"),
}
}
}
pub trait Shape: Debug {
#[allow(clippy::missing_errors_doc)]
fn fmt_js(&self, f: &mut Formatter<'_>) -> fmt::Result;
}
#[derive(Default, Debug, Copy, Clone)]
struct CommonOptions {
draggable: Option<bool>,
editable: Option<bool>,
visible: Option<bool>,
z_index: Option<isize>,
}
#[derive(Debug, Clone)]
pub struct Marker {
position: Location,
label: Option<String>,
title: Option<String>,
opacity: Option<f64>,
z_index: Option<isize>,
}
impl Marker {
#[must_use]
pub fn new(pos: impl Into<Location>) -> Self {
Marker {
position: pos.into(),
label: None,
title: None,
opacity: None,
z_index: None,
}
}
#[must_use]
pub fn label(mut self, value: impl AsRef<str>) -> Self {
self.label = Some(value.as_ref().to_string());
self
}
#[must_use]
pub fn title(mut self, value: impl AsRef<str>) -> Self {
self.title = Some(value.as_ref().to_string());
self
}
}
impl Shape for Marker {
fn fmt_js(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.write_str("new google.maps.Marker(")?;
f.write_object()
.entry("map", &MAP_IDENT)
.entry("position", &self.position)
.entry_opt("label", &self.label)
.entry_opt("title", &self.title)
.finish()?;
f.write_str(")")?;
Ok(())
}
}
impl From<Marker> for Location {
fn from(m: Marker) -> Self {
m.position
}
}
#[derive(Debug, Clone)]
pub struct Polyline {
path: Vec<Location>,
geodesic: Option<bool>,
style: PolylineStyle,
common: CommonOptions,
}
impl Polyline {
#[must_use]
pub fn new(points: impl IntoIterator<Item = impl Into<Location>>) -> Self {
Polyline {
path: points.into_iter().map(Into::into).collect(),
geodesic: None,
style: PolylineStyle::default(),
common: CommonOptions::default(),
}
}
#[must_use]
pub fn geodesic(mut self, value: bool) -> Self {
self.geodesic = Some(value);
self
}
#[must_use]
pub fn style(mut self, value: impl Into<PolylineStyle>) -> Self {
self.style = value.into();
self
}
#[must_use]
pub fn draggable(mut self, value: bool) -> Self {
self.common.draggable = Some(value);
self
}
#[must_use]
pub fn editable(mut self, value: bool) -> Self {
self.common.editable = Some(value);
self
}
#[must_use]
pub fn visible(mut self, value: bool) -> Self {
self.common.visible = Some(value);
self
}
#[must_use]
pub fn z_index(mut self, value: isize) -> Self {
self.common.z_index = Some(value);
self
}
}
impl Shape for Polyline {
fn fmt_js(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.write_str("new google.maps.Polyline(")?;
f.write_object()
.entry("map", &MAP_IDENT)
.entry("path", &self.path)
.entry_opt("geodesic", &self.geodesic)
.entry_opt("strokeColor", &self.style.stroke_color)
.entry_opt("strokeOpacity", &self.style.stroke_opacity)
.entry_opt("strokeWeight", &self.style.stroke_weight)
.entry_opt("draggable", &self.common.draggable)
.entry_opt("editable", &self.common.editable)
.entry_opt("visible", &self.common.visible)
.entry_opt("zIndex", &self.common.z_index)
.finish()?;
f.write_str(")")?;
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct Polygon {
paths: Vec<Vec<Location>>,
geodesic: Option<bool>,
style: PolygonStyle,
common: CommonOptions,
}
impl Polygon {
#[must_use]
pub fn new(points: impl IntoIterator<Item = impl Into<Location>>) -> Self {
Polygon {
paths: vec![points.into_iter().map(Into::into).collect()],
geodesic: None,
style: PolygonStyle::default(),
common: CommonOptions::default(),
}
}
#[must_use]
pub fn path(mut self, points: impl IntoIterator<Item = impl Into<Location>>) -> Self {
self.paths
.push(points.into_iter().map(Into::into).collect());
self
}
#[must_use]
pub fn geodesic(mut self, value: bool) -> Self {
self.geodesic = Some(value);
self
}
#[must_use]
pub fn style(mut self, value: impl Into<PolygonStyle>) -> Self {
self.style = value.into();
self
}
#[must_use]
pub fn draggable(mut self, value: bool) -> Self {
self.common.draggable = Some(value);
self
}
#[must_use]
pub fn editable(mut self, value: bool) -> Self {
self.common.editable = Some(value);
self
}
#[must_use]
pub fn visible(mut self, value: bool) -> Self {
self.common.visible = Some(value);
self
}
#[must_use]
pub fn z_index(mut self, value: isize) -> Self {
self.common.z_index = Some(value);
self
}
}
impl Shape for Polygon {
fn fmt_js(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.write_str("new google.maps.Polygon(")?;
f.write_object()
.entry("map", &MAP_IDENT)
.entry("paths", &self.paths)
.entry_opt("geodesic", &self.geodesic)
.entry_opt("fillColor", &self.style.fill_color)
.entry_opt("fillOpacity", &self.style.fill_opacity)
.entry_opt("strokePosition", &self.style.stroke_position)
.entry_opt("strokeColor", &self.style.stroke_color)
.entry_opt("strokeOpacity", &self.style.stroke_opacity)
.entry_opt("strokeWeight", &self.style.stroke_weight)
.entry_opt("draggable", &self.common.draggable)
.entry_opt("editable", &self.common.editable)
.entry_opt("visible", &self.common.visible)
.entry_opt("zIndex", &self.common.z_index)
.finish()?;
f.write_str(")")?;
Ok(())
}
}
#[derive(Debug, Copy, Clone)]
pub struct Rectangle {
bounds: BoundingBox,
style: PolygonStyle,
common: CommonOptions,
}
impl Rectangle {
#[must_use]
pub fn new(p1: impl Into<Location>, p2: impl Into<Location>) -> Self {
Rectangle {
bounds: BoundingBox::new(p1.into(), p2.into()),
style: PolygonStyle::default(),
common: CommonOptions::default(),
}
}
#[must_use]
pub fn style(mut self, value: impl Into<PolygonStyle>) -> Self {
self.style = value.into();
self
}
#[must_use]
pub fn draggable(mut self, value: bool) -> Self {
self.common.draggable = Some(value);
self
}
#[must_use]
pub fn editable(mut self, value: bool) -> Self {
self.common.editable = Some(value);
self
}
#[must_use]
pub fn visible(mut self, value: bool) -> Self {
self.common.visible = Some(value);
self
}
#[must_use]
pub fn z_index(mut self, value: isize) -> Self {
self.common.z_index = Some(value);
self
}
}
impl Shape for Rectangle {
fn fmt_js(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.write_str("new google.maps.Rectangle(")?;
f.write_object()
.entry("map", &MAP_IDENT)
.entry("bounds", &self.bounds)
.entry_opt("fillColor", &self.style.fill_color)
.entry_opt("fillOpacity", &self.style.fill_opacity)
.entry_opt("strokePosition", &self.style.stroke_position)
.entry_opt("strokeColor", &self.style.stroke_color)
.entry_opt("strokeOpacity", &self.style.stroke_opacity)
.entry_opt("strokeWeight", &self.style.stroke_weight)
.entry_opt("draggable", &self.common.draggable)
.entry_opt("editable", &self.common.editable)
.entry_opt("visible", &self.common.visible)
.entry_opt("zIndex", &self.common.z_index)
.finish()?;
f.write_str(")")?;
Ok(())
}
}
#[derive(Debug, Copy, Clone)]
pub struct Circle {
center: Location,
radius: f64,
style: PolygonStyle,
common: CommonOptions,
}
impl Circle {
#[must_use]
pub fn new(center: impl Into<Location>, radius: f64) -> Self {
Circle {
center: center.into(),
radius,
style: PolygonStyle::default(),
common: CommonOptions::default(),
}
}
#[must_use]
pub fn style(mut self, value: impl Into<PolygonStyle>) -> Self {
self.style = value.into();
self
}
#[must_use]
pub fn draggable(mut self, value: bool) -> Self {
self.common.draggable = Some(value);
self
}
#[must_use]
pub fn editable(mut self, value: bool) -> Self {
self.common.editable = Some(value);
self
}
#[must_use]
pub fn visible(mut self, value: bool) -> Self {
self.common.visible = Some(value);
self
}
#[must_use]
pub fn z_index(mut self, value: isize) -> Self {
self.common.z_index = Some(value);
self
}
}
impl Shape for Circle {
fn fmt_js(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.write_str("new google.maps.Circle(")?;
f.write_object()
.entry("map", &MAP_IDENT)
.entry("center", &self.center)
.entry("radius", &self.radius)
.entry_opt("fillColor", &self.style.fill_color)
.entry_opt("fillOpacity", &self.style.fill_opacity)
.entry_opt("strokePosition", &self.style.stroke_position)
.entry_opt("strokeColor", &self.style.stroke_color)
.entry_opt("strokeOpacity", &self.style.stroke_opacity)
.entry_opt("strokeWeight", &self.style.stroke_weight)
.entry_opt("draggable", &self.common.draggable)
.entry_opt("editable", &self.common.editable)
.entry_opt("visible", &self.common.visible)
.entry_opt("zIndex", &self.common.z_index)
.finish()?;
f.write_str(")")?;
Ok(())
}
}