use super::area::{Area, AreaShape, FillType};
use super::drawing::Polyline;
use super::text::{Text, TextSize};
use crate::projection::GeoPos;
use egui::{Color32, Stroke};
use geojson::{Feature, Geometry, GeometryValue};
use serde_json::{Map, Value as JsonValue};
fn add_version_to_properties(properties: &mut Map<String, JsonValue>) {
properties.insert(
"x-egui-map-view-crate-name".to_string(),
JsonValue::String(env!("CARGO_PKG_NAME").to_string()),
);
properties.insert(
"x-egui-map-view-crate-version".to_string(),
JsonValue::String(env!("CARGO_PKG_VERSION").to_string()),
);
}
fn check_version_from_properties(properties: &Map<String, JsonValue>) {
if let (Some(name), Some(version)) = (
properties
.get("x-egui-map-view-crate-name")
.and_then(|v| v.as_str()),
properties
.get("x-egui-map-view-crate-version")
.and_then(|v| v.as_str()),
) {
if name == env!("CARGO_PKG_NAME") && version != env!("CARGO_PKG_VERSION") {
log::warn!(
"GeoJSON feature was created with a different version of {}. File version: {}, current version: {}. This might lead to unexpected behavior.",
name,
version,
env!("CARGO_PKG_VERSION")
);
}
} else {
log::warn!("No egui-map-view version information found in feature properties.");
}
}
impl From<Area> for Feature {
fn from(area: Area) -> Self {
let mut feature = Feature::default();
let mut properties = Map::new();
add_version_to_properties(&mut properties);
properties.insert(
"stroke_color".to_string(),
JsonValue::String(area.stroke.color.to_hex()),
);
properties.insert(
"stroke_width".to_string(),
JsonValue::from(area.stroke.width),
);
properties.insert(
"fill_color".to_string(),
JsonValue::String(area.fill.to_hex()),
);
properties.insert(
"fill_type".to_string(),
JsonValue::String(
match area.fill_type {
FillType::None => "None",
FillType::Solid => "Solid",
FillType::Hatching => "Hatching",
}
.to_string(),
),
);
match area.shape {
AreaShape::Polygon(points) => {
let polygon_points: Vec<Vec<geojson::Position>> = vec![
points
.iter()
.chain(points.first())
.map(|gp| geojson::Position::from(vec![gp.lon, gp.lat]))
.collect(),
];
feature.geometry = Some(Geometry::new(GeometryValue::Polygon {
coordinates: polygon_points,
}));
}
AreaShape::Circle {
center,
radius,
points,
} => {
let point = Geometry::new(GeometryValue::Point {
coordinates: geojson::Position::from(vec![center.lon, center.lat]),
});
feature.geometry = Some(point);
properties.insert("radius".to_string(), JsonValue::from(radius));
if let Some(p) = points {
properties.insert("points".to_string(), JsonValue::from(p));
}
}
}
feature.properties = Some(properties);
feature
}
}
impl TryFrom<Feature> for Area {
type Error = String;
fn try_from(feature: Feature) -> Result<Self, Self::Error> {
let shape = if let Some(geometry) = &feature.geometry {
match &geometry.value {
GeometryValue::Polygon {
coordinates: points,
} => {
let mut polygon_points: Vec<GeoPos> = points
.first()
.ok_or("Polygon has no rings")?
.iter()
.map(|pos| GeoPos {
lon: pos[0],
lat: pos[1],
})
.collect();
if polygon_points.first() == polygon_points.last() {
polygon_points.pop();
}
Some(AreaShape::Polygon(polygon_points))
}
GeometryValue::Point { coordinates: point } => {
let properties = feature
.properties
.as_ref()
.ok_or("Feature has no properties")?;
let center = GeoPos {
lon: point[0],
lat: point[1],
};
let radius = properties
.get("radius")
.and_then(serde_json::Value::as_f64)
.unwrap_or_default();
let points = properties.get("points").and_then(serde_json::Value::as_i64);
if radius <= 0.0 {
return Err("Radius must be greater than 0".to_string());
}
Some(AreaShape::Circle {
center,
radius,
points,
})
}
_ => None,
}
} else {
None
};
let shape = shape.ok_or("Unsupported geometry or missing shape data")?;
let mut stroke = Stroke::new(1.0, Color32::RED);
let mut fill = Color32::TRANSPARENT;
if let Some(properties) = &feature.properties {
check_version_from_properties(properties);
if let Some(value) = properties.get("stroke_width")
&& let Some(width) = value.as_f64()
{
stroke.width = width as f32;
}
if let Some(value) = properties.get("stroke_color")
&& let Some(s) = value.as_str()
&& let Ok(color) = Color32::from_hex(s)
{
stroke.color = color;
}
if let Some(value) = properties.get("fill_color")
&& let Some(s) = value.as_str()
&& let Ok(color) = Color32::from_hex(s)
{
fill = color;
}
}
let fill_type = if let Some(properties) = &feature.properties {
match properties.get("fill_type").and_then(|v| v.as_str()) {
Some("None") => FillType::None,
Some("Hatching") => FillType::Hatching,
_ => FillType::Solid, }
} else {
FillType::Solid
};
Ok(Area {
shape,
stroke,
fill,
fill_type,
})
}
}
impl From<Polyline> for Feature {
fn from(polyline: Polyline) -> Self {
let mut feature = Feature::default();
let mut properties = Map::new();
add_version_to_properties(&mut properties);
feature.properties = Some(properties);
let line_string: Vec<geojson::Position> = polyline
.0
.iter()
.map(|gp| geojson::Position::from(vec![gp.lon, gp.lat]))
.collect();
feature.geometry = Some(Geometry::new(GeometryValue::LineString {
coordinates: line_string,
}));
feature
}
}
impl TryFrom<Feature> for Polyline {
type Error = String;
fn try_from(feature: Feature) -> Result<Self, Self::Error> {
if let Some(geometry) = feature.geometry
&& let GeometryValue::LineString {
coordinates: line_string,
} = geometry.value
{
return Ok(Polyline(
line_string
.iter()
.map(|pos| GeoPos {
lon: pos[0],
lat: pos[1],
})
.collect(),
));
}
if let Some(properties) = &feature.properties {
check_version_from_properties(properties);
}
Err("Feature is not a LineString".to_string())
}
}
impl From<Text> for Feature {
fn from(text: Text) -> Self {
let mut feature = Feature::default();
let mut properties = Map::new();
add_version_to_properties(&mut properties);
let point = Geometry::new(GeometryValue::Point {
coordinates: geojson::Position::from(vec![text.pos.lon, text.pos.lat]),
});
feature.geometry = Some(point);
properties.insert("text".to_string(), JsonValue::String(text.text));
properties.insert("color".to_string(), JsonValue::String(text.color.to_hex()));
properties.insert(
"background".to_string(),
JsonValue::String(text.background.to_hex()),
);
match text.size {
TextSize::Static(size) => {
properties.insert(
"size_type".to_string(),
JsonValue::String("Static".to_string()),
);
properties.insert("size".to_string(), JsonValue::from(size));
}
TextSize::Relative(size) => {
properties.insert(
"size_type".to_string(),
JsonValue::String("Relative".to_string()),
);
properties.insert("size".to_string(), JsonValue::from(size));
}
}
feature.properties = Some(properties);
feature
}
}
impl TryFrom<Feature> for Text {
type Error = String;
fn try_from(feature: Feature) -> Result<Self, Self::Error> {
let mut text = Text::default();
if let Some(geometry) = feature.geometry {
if let GeometryValue::Point { coordinates: point } = geometry.value {
text.pos = GeoPos {
lon: point[0],
lat: point[1],
};
} else {
return Err("Feature is not a Point".to_string());
}
} else {
return Err("Feature has no geometry".to_string());
}
if let Some(properties) = feature.properties {
check_version_from_properties(&properties);
if let Some(value) = properties.get("text") {
if let Some(s) = value.as_str() {
text.text = s.to_string();
} else {
return Err("Property 'text' is not a string".to_string());
}
} else {
return Err("Feature has no 'text' property".to_string());
}
if let Some(value) = properties.get("color")
&& let Some(s) = value.as_str()
&& let Ok(color) = Color32::from_hex(s)
{
text.color = color;
}
if let Some(value) = properties.get("background")
&& let Some(s) = value.as_str()
&& let Ok(color) = Color32::from_hex(s)
{
text.background = color;
}
if let Some(size_type) = properties.get("size_type")
&& let Some(size) = properties.get("size")
&& let Some(size_f32) = size.as_f64()
{
if size_type == "Static" {
text.size = TextSize::Static(size_f32 as f32);
} else if size_type == "Relative" {
text.size = TextSize::Relative(size_f32 as f32);
}
}
}
Ok(text)
}
}