hrdf_routing_engine/isochrone/
models.rs1use geo::{Area, Contains, LineString, MultiPolygon, Polygon};
2use hrdf_parser::Coordinates;
3use serde::Serialize;
4use strum_macros::EnumString;
5
6#[cfg(feature = "svg")]
7use geo::BoundingRect;
8#[cfg(feature = "svg")]
9use std::error::Error;
10#[cfg(feature = "svg")]
11use svg::node::element::Polygon as SvgPolygon;
12#[cfg(feature = "svg")]
13use svg::Document;
14
15use super::utils::wgs84_to_lv95;
16
17#[derive(Debug, Serialize)]
18pub struct IsochroneMap {
19 isochrones: Vec<Isochrone>,
20 departure_stop_coord: Coordinates,
21 bounding_box: ((f64, f64), (f64, f64)),
22}
23
24impl IsochroneMap {
25 pub fn new(
26 isochrones: Vec<Isochrone>,
27 departure_stop_coord: Coordinates,
28 bounding_box: ((f64, f64), (f64, f64)),
29 ) -> Self {
30 Self {
31 isochrones,
32 departure_stop_coord,
33 bounding_box,
34 }
35 }
36
37 pub fn compute_areas(&self) -> Vec<f64> {
38 self.isochrones.iter().map(|i| i.compute_area()).collect()
39 }
40
41 pub fn get_polygons(&self) -> Vec<MultiPolygon> {
42 let mut polygons = self
43 .isochrones
44 .iter()
45 .map(|i| i.to_polygons())
46 .collect::<Vec<_>>();
47
48 let polygons_original = polygons.clone();
49
50 for i in 0..polygons.len() - 1 {
51 for p_ext in &mut polygons[i + 1] {
52 for p_int in &polygons_original[i] {
53 if p_ext.contains(p_int) {
54 p_ext.interiors_push(p_int.exterior().clone());
55 }
56 }
57 }
58 }
59
60 polygons
61 }
62
63 #[cfg(feature = "svg")]
64 pub fn write_svg(&self, path: &str) -> Result<(), Box<dyn Error>> {
65 let polys = self.get_polygons();
66
67 let bounding_rect = polys.last().unwrap().bounding_rect().unwrap();
68 let (min_x, min_y) = bounding_rect.min().x_y();
69 let (max_x, max_y) = bounding_rect.max().x_y();
70 let document = polys.iter().fold(
71 Document::new().set(
72 "viewBox",
73 (
74 min_x / 100.0,
75 min_y / 100.0,
76 max_x / 100.0 - min_x / 100.0,
77 max_y / 100.0 - min_y / 100.0,
78 ),
79 ),
80 |mut doc, pi| {
81 doc = pi.iter().fold(doc, |doc_nested, p| {
82 let points_ext = p
83 .exterior()
84 .coords()
85 .map(|coord| {
86 format!(
87 "{},{}",
88 coord.x / 100.0,
89 (min_y + (max_y - coord.y)) / 100.0
90 )
91 })
92 .collect::<Vec<_>>();
93
94 doc_nested.add(
95 SvgPolygon::new()
96 .set("fill", "none")
97 .set("stroke", "red")
98 .set("points", points_ext.join(" ")),
99 )
100 });
101 doc
102 },
103 );
104 svg::save(format!("{path}"), &document)?;
105 Ok(())
106 }
107}
108
109#[derive(Debug, Serialize)]
110pub struct Isochrone {
111 polygons: Vec<Vec<Coordinates>>,
112 time_limit: u32, }
114
115impl Isochrone {
116 pub fn new(polygons: Vec<Vec<Coordinates>>, time_limit: u32) -> Self {
117 Self {
118 polygons,
119 time_limit,
120 }
121 }
122
123 pub fn to_polygons(&self) -> MultiPolygon {
126 self.polygons
127 .iter()
128 .map(|p| {
129 Polygon::new(
130 LineString::from(
131 p.iter()
132 .map(|c| {
133 if let (Some(x), Some(y)) = (c.easting(), c.northing()) {
134 (x, y)
135 } else {
136 wgs84_to_lv95(c.latitude().unwrap(), c.longitude().unwrap())
137 }
138 })
139 .collect::<Vec<_>>(),
140 ),
141 vec![],
142 )
143 })
144 .collect()
145 }
146
147 pub fn compute_area(&self) -> f64 {
148 self.to_polygons().iter().map(|p| p.unsigned_area()).sum()
149 }
150}
151
152#[derive(Debug, EnumString, PartialEq, Clone, Copy)]
153pub enum DisplayMode {
154 #[strum(serialize = "circles")]
155 Circles,
156 #[strum(serialize = "contour_line")]
157 ContourLine,
158}