use crate::layers::{Layer, dist_sq_to_segment, projection_factor, serde_stroke};
use crate::projection::{GeoPos, MapProjection};
use egui::{Color32, Painter, Pos2, Response, Stroke};
use serde::{Deserialize, Serialize};
use std::any::Any;
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Polyline(pub Vec<GeoPos>);
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub enum DrawMode {
#[default]
Disabled,
Draw,
Erase,
}
#[derive(Clone, Serialize, Deserialize)]
#[serde(default)]
pub struct DrawingLayer {
polylines: Vec<Polyline>,
#[serde(with = "serde_stroke")]
pub stroke: Stroke,
#[serde(skip)]
pub draw_mode: DrawMode,
}
impl DrawingLayer {
#[cfg(feature = "geojson")]
#[cfg(feature = "geojson")]
pub fn to_geojson_str(&self, layer_id: &str) -> Result<String, serde_json::Error> {
let features: Vec<geojson::Feature> = self
.polylines
.clone()
.into_iter()
.map(|p| {
let mut feature = geojson::Feature::from(p);
if let Some(properties) = &mut feature.properties {
properties.insert(
"stroke_width".to_string(),
serde_json::Value::from(self.stroke.width),
);
properties.insert(
"stroke_color".to_string(),
serde_json::Value::String(self.stroke.color.to_hex()),
);
properties.insert(
"layer_id".to_string(),
serde_json::Value::String(layer_id.to_string()),
);
}
feature
})
.collect();
let mut foreign_members = serde_json::Map::new();
foreign_members.insert(
"stroke_width".to_string(),
serde_json::Value::from(self.stroke.width),
);
foreign_members.insert(
"stroke_color".to_string(),
serde_json::Value::String(self.stroke.color.to_hex()),
);
foreign_members.insert(
"layer_id".to_string(),
serde_json::Value::String(layer_id.to_string()),
);
let feature_collection = geojson::FeatureCollection {
bbox: None,
features,
foreign_members: Some(foreign_members),
};
serde_json::to_string(&feature_collection)
}
#[cfg(feature = "geojson")]
pub fn from_geojson_str(
&mut self,
s: &str,
layer_id: Option<&str>,
) -> Result<(), serde_json::Error> {
let feature_collection: geojson::FeatureCollection = serde_json::from_str(s)?;
let new_polylines: Vec<Polyline> = feature_collection
.features
.iter()
.filter_map(|f| {
if let Some(target_id) = layer_id {
if let Some(properties) = &f.properties {
if let Some(val) = properties.get("layer_id") {
if let Some(id) = val.as_str() {
if id != target_id {
return None;
}
} else {
return None;
}
} else {
return None;
}
} else {
return None;
}
}
let polyline = Polyline::try_from(f.clone()).ok();
if polyline.is_some()
&& let Some(properties) = &f.properties
{
if let Some(value) = properties.get("stroke_width")
&& let Some(width) = value.as_f64()
{
self.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)
{
self.stroke.color = color;
}
}
polyline
})
.collect();
self.polylines.extend(new_polylines);
if let Some(foreign_members) = feature_collection.foreign_members {
if let Some(value) = foreign_members.get("stroke_width")
&& let Some(width) = value.as_f64()
{
self.stroke.width = width as f32;
}
if let Some(value) = foreign_members.get("stroke_color")
&& let Some(s) = value.as_str()
&& let Ok(color) = Color32::from_hex(s)
{
self.stroke.color = color;
}
}
Ok(())
}
#[must_use]
pub fn new(stroke: Stroke) -> Self {
Self {
polylines: Vec::new(),
stroke,
draw_mode: DrawMode::default(),
}
}
}
impl Default for DrawingLayer {
fn default() -> Self {
Self {
polylines: Vec::new(),
stroke: Stroke::new(2.0, Color32::RED),
draw_mode: DrawMode::default(),
}
}
}
impl DrawingLayer {
fn handle_draw_input(&mut self, response: &Response, projection: &MapProjection) -> bool {
if response.hovered() {
response.ctx.set_cursor_icon(egui::CursorIcon::Crosshair);
}
if response.clicked()
&& let Some(pointer_pos) = response.interact_pointer_pos()
{
let geo_pos = projection.unproject(pointer_pos);
if let Some(last_line) = self.polylines.last_mut()
&& response.ctx.input(|i| i.modifiers.shift)
{
last_line.0.push(geo_pos);
} else {
let geo_pos2 = projection.unproject(pointer_pos + egui::vec2(1.0, 0.0));
self.polylines.push(Polyline(vec![geo_pos, geo_pos2]));
}
}
if response.drag_started() {
self.polylines.push(Polyline(Vec::new()));
}
if response.dragged()
&& let Some(pointer_pos) = response.interact_pointer_pos()
&& let Some(last_line) = self.polylines.last_mut()
{
let geo_pos = projection.unproject(pointer_pos);
last_line.0.push(geo_pos);
}
response.hovered()
}
fn handle_erase_input(&mut self, response: &Response, projection: &MapProjection) -> bool {
if response.hovered() {
response.ctx.set_cursor_icon(egui::CursorIcon::NotAllowed);
}
if (response.dragged() || response.clicked())
&& let Some(pointer_pos) = response.interact_pointer_pos()
{
self.erase_at(pointer_pos, projection);
}
response.hovered()
}
fn erase_at(&mut self, pointer_pos: Pos2, projection: &MapProjection) {
let erase_radius_screen = self.stroke.width;
let erase_radius_sq = erase_radius_screen * erase_radius_screen;
let old_polylines = std::mem::take(&mut self.polylines);
self.polylines = old_polylines
.into_iter()
.flat_map(|polyline| {
split_polyline_by_erase_circle(
&polyline.0,
pointer_pos,
erase_radius_sq,
projection,
)
.into_iter()
.map(Polyline)
})
.collect();
}
}
impl Layer for DrawingLayer {
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
fn handle_input(&mut self, response: &Response, projection: &MapProjection) -> bool {
match self.draw_mode {
DrawMode::Disabled => false,
DrawMode::Draw => self.handle_draw_input(response, projection),
DrawMode::Erase => self.handle_erase_input(response, projection),
}
}
fn draw(&self, painter: &Painter, projection: &MapProjection) {
for polyline in &self.polylines {
if polyline.0.len() > 1 {
let screen_points: Vec<egui::Pos2> =
polyline.0.iter().map(|p| projection.project(*p)).collect();
painter.add(egui::Shape::line(screen_points, self.stroke));
}
}
}
}
fn split_polyline_by_erase_circle(
polyline: &[GeoPos],
pointer_pos: Pos2,
erase_radius_sq: f32,
projection: &MapProjection,
) -> Vec<Vec<GeoPos>> {
if polyline.len() < 2 {
return vec![];
}
let screen_points: Vec<Pos2> = polyline.iter().map(|p| projection.project(*p)).collect();
let mut new_polylines = Vec::new();
let mut current_line = Vec::new();
let mut in_visible_part = true;
if dist_sq_to_segment(pointer_pos, screen_points[0], screen_points[1]) < erase_radius_sq {
in_visible_part = false;
} else {
current_line.push(polyline[0]);
}
for i in 0..(polyline.len() - 1) {
let p2_geo = polyline[i + 1];
let p1_screen = screen_points[i];
let p2_screen = screen_points[i + 1];
let segment_is_erased =
dist_sq_to_segment(pointer_pos, p1_screen, p2_screen) < erase_radius_sq;
if in_visible_part {
if segment_is_erased {
let t = projection_factor(pointer_pos, p1_screen, p2_screen);
let split_point_screen = p1_screen.lerp(p2_screen, t);
let split_point_geo = projection.unproject(split_point_screen);
current_line.push(split_point_geo);
if current_line.len() > 1 {
new_polylines.push(std::mem::take(&mut current_line));
}
in_visible_part = false;
} else {
current_line.push(p2_geo);
}
} else {
if !segment_is_erased {
let t = projection_factor(pointer_pos, p1_screen, p2_screen);
let split_point_screen = p1_screen.lerp(p2_screen, t);
let split_point_geo = projection.unproject(split_point_screen);
current_line.push(split_point_geo);
current_line.push(p2_geo);
in_visible_part = true;
}
}
}
if current_line.len() > 1 {
new_polylines.push(current_line);
}
new_polylines
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn drawing_layer_new() {
let layer = DrawingLayer::default();
assert_eq!(layer.draw_mode, DrawMode::Disabled);
assert!(layer.polylines.is_empty());
}
#[test]
fn drawing_layer_as_any() {
let layer = DrawingLayer::default();
assert!(layer.as_any().is::<DrawingLayer>());
}
#[test]
fn drawing_layer_as_any_mut() {
let mut layer = DrawingLayer::default();
assert!(layer.as_any_mut().is::<DrawingLayer>());
}
#[test]
fn drawing_layer_serde() {
let mut layer = DrawingLayer::default();
layer.draw_mode = DrawMode::Draw; layer.polylines.push(Polyline(vec![
GeoPos { lon: 1.0, lat: 2.0 },
GeoPos { lon: 3.0, lat: 4.0 },
]));
layer.stroke = Stroke::new(5.0, Color32::BLUE);
let json = serde_json::to_string(&layer).unwrap();
assert!(json.contains(r##""polylines":[[{"lon":1.0,"lat":2.0},{"lon":3.0,"lat":4.0}]],"stroke":{"width":5.0,"color":"#0000ffff"}"##));
assert!(!json.contains("draw_mode"));
let deserialized: DrawingLayer = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.polylines, layer.polylines);
assert_eq!(deserialized.stroke.width, 5.0);
assert_eq!(deserialized.stroke.color, Color32::BLUE);
assert_eq!(deserialized.draw_mode, DrawMode::Disabled);
}
#[cfg(feature = "geojson")]
mod geojson_tests {
use super::*;
#[test]
fn drawing_layer_geojson() {
let mut layer = DrawingLayer::default();
layer.polylines.push(Polyline(vec![
(10.0, 20.0).into(),
(30.0, 40.0).into(),
(50.0, 60.0).into(),
]));
layer.stroke = Stroke::new(5.0, Color32::BLUE);
let geojson_str = layer.to_geojson_str("my_layer").unwrap();
let mut new_layer = DrawingLayer::default();
new_layer
.from_geojson_str(&geojson_str, Some("my_layer"))
.unwrap();
assert_eq!(new_layer.polylines.len(), 1);
assert_eq!(layer.polylines[0], new_layer.polylines[0]);
assert_eq!(layer.stroke, new_layer.stroke);
let mut other_layer = DrawingLayer::default();
other_layer
.from_geojson_str(&geojson_str, Some("other_layer"))
.unwrap();
assert_eq!(other_layer.polylines.len(), 0);
let mut all_layer = DrawingLayer::default();
all_layer.from_geojson_str(&geojson_str, None).unwrap();
assert_eq!(all_layer.polylines.len(), 1);
}
}
}