use egui::{Painter, Pos2, Response};
use std::any::Any;
use crate::projection::MapProjection;
#[cfg(feature = "geojson")]
pub mod geojson;
#[cfg(feature = "drawing-layer")]
pub mod drawing;
#[cfg(feature = "text-layer")]
pub mod text;
#[cfg(feature = "svg-layer")]
pub mod svg;
#[cfg(feature = "area-layer")]
pub mod area;
#[cfg(feature = "tile-layer")]
pub mod tile;
pub(crate) mod serde_color32 {
use egui::Color32;
use serde::{self, Deserialize, Deserializer, Serializer};
pub fn serialize<S>(color: &Color32, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&color.to_hex())
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<Color32, D::Error>
where
D: Deserializer<'de>,
{
let s = <String as Deserialize>::deserialize(deserializer)?;
Color32::from_hex(&s).map_err(|err| serde::de::Error::custom(format!("{err:?}")))
}
}
pub(crate) mod serde_stroke {
use crate::layers::serde_color32;
use egui::{Color32, Stroke};
use serde::{self, Deserialize, Deserializer, Serialize, Serializer};
#[derive(Serialize, Deserialize)]
struct StrokeHelper {
width: f32,
#[serde(with = "serde_color32")]
color: Color32,
}
pub fn serialize<S>(stroke: &Stroke, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let helper = StrokeHelper {
width: stroke.width,
color: stroke.color,
};
helper.serialize(serializer)
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<Stroke, D::Error>
where
D: Deserializer<'de>,
{
let helper = StrokeHelper::deserialize(deserializer)?;
Ok(Stroke {
width: helper.width,
color: helper.color,
})
}
}
pub trait Layer: Any {
fn handle_input(&mut self, response: &Response, projection: &MapProjection) -> bool;
fn draw(&self, painter: &Painter, projection: &MapProjection);
fn as_any(&self) -> &dyn Any;
fn as_any_mut(&mut self) -> &mut dyn Any;
}
pub(crate) fn dist_sq_to_segment(p: Pos2, a: Pos2, b: Pos2) -> f32 {
let ab = b - a;
let ap = p - a;
let l2 = ab.length_sq();
if l2 == 0.0 {
return ap.length_sq();
}
let t = (ap.dot(ab) / l2).clamp(0.0, 1.0);
let closest_point = a + t * ab;
p.distance_sq(closest_point)
}
pub(crate) fn projection_factor(p: Pos2, a: Pos2, b: Pos2) -> f32 {
let ab = b - a;
let ap = p - a;
let l2 = ab.length_sq();
if l2 == 0.0 {
return 0.0;
}
(ap.dot(ab) / l2).clamp(0.0, 1.0)
}
pub(crate) fn segments_intersect(p1: Pos2, q1: Pos2, p2: Pos2, q2: Pos2) -> bool {
fn orientation(p: Pos2, q: Pos2, r: Pos2) -> i8 {
let val = (q.y - p.y) * (r.x - q.x) - (q.x - p.x) * (r.y - q.y);
if val.abs() < 1e-6 {
0 } else if val > 0.0 {
1 } else {
-1 }
}
let o1 = orientation(p1, q1, p2);
let o2 = orientation(p1, q1, q2);
let o3 = orientation(p2, q2, p1);
let o4 = orientation(p2, q2, q1);
if o1 != o2 && o3 != o4 {
return true;
}
false
}
#[cfg(test)]
mod tests {
use super::*;
use egui::pos2;
const EPSILON: f32 = 1e-6;
#[test]
fn test_dist_sq_to_segment() {
let a = pos2(0.0, 0.0);
let b = pos2(10.0, 0.0);
let p1 = pos2(5.0, 0.0);
assert!((dist_sq_to_segment(p1, a, b) - 0.0).abs() < EPSILON);
let p2 = pos2(5.0, 5.0);
assert!((dist_sq_to_segment(p2, a, b) - 25.0).abs() < EPSILON);
let p3 = pos2(-5.0, 5.0);
assert!((dist_sq_to_segment(p3, a, b) - 50.0).abs() < EPSILON);
let p4 = pos2(15.0, 5.0);
assert!((dist_sq_to_segment(p4, a, b) - 50.0).abs() < EPSILON);
let c = pos2(5.0, 5.0);
let p5 = pos2(10.0, 10.0);
assert!((dist_sq_to_segment(p5, c, c) - 50.0).abs() < EPSILON); }
#[test]
fn test_projection_factor() {
let a = pos2(0.0, 0.0);
let b = pos2(10.0, 0.0);
assert!((projection_factor(a, a, b) - 0.0).abs() < EPSILON);
assert!((projection_factor(b, a, b) - 1.0).abs() < EPSILON);
let p1 = pos2(5.0, 0.0);
assert!((projection_factor(p1, a, b) - 0.5).abs() < EPSILON);
let p2 = pos2(5.0, 5.0);
assert!((projection_factor(p2, a, b) - 0.5).abs() < EPSILON);
let p3 = pos2(-5.0, 5.0);
assert!((projection_factor(p3, a, b) - 0.0).abs() < EPSILON);
let p4 = pos2(15.0, 5.0);
assert!((projection_factor(p4, a, b) - 1.0).abs() < EPSILON);
let c = pos2(5.0, 5.0);
let p5 = pos2(10.0, 10.0);
assert!((projection_factor(p5, c, c) - 0.0).abs() < EPSILON);
}
#[test]
fn test_segments_intersect() {
let p1 = pos2(0.0, 0.0);
let q1 = pos2(10.0, 10.0);
let p2 = pos2(0.0, 10.0);
let q2 = pos2(10.0, 0.0);
assert!(segments_intersect(p1, q1, p2, q2), "General intersection");
let p1 = pos2(0.0, 0.0);
let q1 = pos2(10.0, 0.0);
let p2 = pos2(0.0, 5.0);
let q2 = pos2(10.0, 5.0);
assert!(
!segments_intersect(p1, q1, p2, q2),
"Parallel, no intersection"
);
let p1 = pos2(0.0, 0.0);
let q1 = pos2(1.0, 1.0);
let p2 = pos2(2.0, 2.0);
let q2 = pos2(3.0, 0.0);
assert!(
!segments_intersect(p1, q1, p2, q2),
"Not parallel, no intersection"
);
let p1 = pos2(0.0, 5.0);
let q1 = pos2(10.0, 5.0);
let p2 = pos2(5.0, 0.0);
let q2 = pos2(5.0, 5.0);
assert!(segments_intersect(p1, q1, p2, q2), "T-junction");
let p1 = pos2(0.0, 0.0);
let q1 = pos2(5.0, 5.0);
let p2 = pos2(5.0, 5.0);
let q2 = pos2(10.0, 0.0);
assert!(segments_intersect(p1, q1, p2, q2), "Meeting at an endpoint");
let p1 = pos2(0.0, 0.0);
let q1 = pos2(10.0, 0.0);
let p2 = pos2(5.0, 0.0);
let q2 = pos2(15.0, 0.0);
assert!(
!segments_intersect(p1, q1, p2, q2),
"Collinear, overlapping"
);
let p1 = pos2(0.0, 0.0);
let q1 = pos2(10.0, 0.0);
let p2 = pos2(11.0, 0.0);
let q2 = pos2(20.0, 0.0);
assert!(
!segments_intersect(p1, q1, p2, q2),
"Collinear, non-overlapping"
);
let p1 = pos2(0.0, 0.0);
let q1 = pos2(10.0, 0.0);
let p2 = pos2(2.0, 0.0);
let q2 = pos2(8.0, 0.0);
assert!(!segments_intersect(p1, q1, p2, q2), "Collinear, contained");
let p1 = pos2(0.0, 0.0);
let q1 = pos2(10.0, 0.0);
let p2 = pos2(5.0, 0.0);
let q2 = pos2(5.0, 0.0);
assert!(!segments_intersect(p1, q1, p2, q2), "Point on segment");
}
}