Skip to main content

egui_map_view/layers/
geojson.rs

1//! GeoJSON serialization and deserialization for layers.
2
3use super::area::{Area, AreaShape};
4use super::drawing::Polyline;
5use super::text::{Text, TextSize};
6use crate::projection::GeoPos;
7use egui::{Color32, Stroke};
8use geojson::{Feature, Geometry, Value};
9use serde_json::{Map, Value as JsonValue};
10
11/// Adds crate name and version to the feature properties.
12fn add_version_to_properties(properties: &mut Map<String, JsonValue>) {
13    properties.insert(
14        "x-egui-map-view-crate-name".to_string(),
15        JsonValue::String(env!("CARGO_PKG_NAME").to_string()),
16    );
17    properties.insert(
18        "x-egui-map-view-crate-version".to_string(),
19        JsonValue::String(env!("CARGO_PKG_VERSION").to_string()),
20    );
21}
22
23/// Checks the crate version from the feature properties and logs a warning on mismatch.
24fn check_version_from_properties(properties: &Map<String, JsonValue>) {
25    if let (Some(name), Some(version)) = (
26        properties
27            .get("x-egui-map-view-crate-name")
28            .and_then(|v| v.as_str()),
29        properties
30            .get("x-egui-map-view-crate-version")
31            .and_then(|v| v.as_str()),
32    ) {
33        if name == env!("CARGO_PKG_NAME") && version != env!("CARGO_PKG_VERSION") {
34            log::warn!(
35                "GeoJSON feature was created with a different version of {}. File version: {}, current version: {}. This might lead to unexpected behavior.",
36                name,
37                version,
38                env!("CARGO_PKG_VERSION")
39            );
40        }
41    } else {
42        log::warn!("No egui-map-view version information found in feature properties.");
43    }
44}
45
46impl From<Area> for Feature {
47    fn from(area: Area) -> Self {
48        let mut feature = Feature::default();
49        let mut properties = Map::new();
50        add_version_to_properties(&mut properties);
51
52        properties.insert(
53            "stroke_color".to_string(),
54            JsonValue::String(area.stroke.color.to_hex()),
55        );
56        properties.insert(
57            "stroke_width".to_string(),
58            JsonValue::from(area.stroke.width),
59        );
60        properties.insert(
61            "fill_color".to_string(),
62            JsonValue::String(area.fill.to_hex()),
63        );
64
65        match area.shape {
66            AreaShape::Polygon(points) => {
67                let polygon_points: Vec<Vec<Vec<f64>>> = vec![
68                    points
69                        .iter()
70                        // GeoJSON polygons must be closed, so the first and last points must be the same.
71                        .chain(points.first())
72                        .map(|gp| (*gp).into())
73                        .collect(),
74                ];
75                feature.geometry = Some(Geometry::new(Value::Polygon(polygon_points)));
76            }
77            AreaShape::Circle {
78                center,
79                radius,
80                points,
81            } => {
82                let point = Geometry::new(Value::Point(center.into()));
83                feature.geometry = Some(point);
84                properties.insert("radius".to_string(), JsonValue::from(radius));
85                if let Some(p) = points {
86                    properties.insert("points".to_string(), JsonValue::from(p));
87                }
88            }
89        }
90
91        feature.properties = Some(properties);
92        feature
93    }
94}
95
96impl TryFrom<Feature> for Area {
97    type Error = String;
98
99    fn try_from(feature: Feature) -> Result<Self, Self::Error> {
100        let shape = if let Some(geometry) = &feature.geometry {
101            match &geometry.value {
102                Value::Polygon(points) => {
103                    let mut polygon_points: Vec<GeoPos> = points
104                        .first()
105                        .ok_or("Polygon has no rings")?
106                        .iter()
107                        .map(|pos| pos.clone().into())
108                        .collect();
109
110                    // Remove the closing point, as AreaShape::Polygon doesn't expect it.
111                    if polygon_points.first() == polygon_points.last() {
112                        polygon_points.pop();
113                    }
114
115                    Some(AreaShape::Polygon(polygon_points))
116                }
117                Value::Point(point) => {
118                    let properties = feature
119                        .properties
120                        .as_ref()
121                        .ok_or("Feature has no properties")?;
122                    let center: GeoPos = point.clone().into();
123                    let radius = properties
124                        .get("radius")
125                        .and_then(|v| v.as_f64())
126                        .unwrap_or_default();
127                    let points = properties.get("points").and_then(|v| v.as_i64());
128
129                    if radius <= 0.0 {
130                        return Err("Radius must be greater than 0".to_string());
131                    }
132
133                    Some(AreaShape::Circle {
134                        center,
135                        radius,
136                        points,
137                    })
138                }
139                _ => None,
140            }
141        } else {
142            None
143        };
144
145        let shape = shape.ok_or("Unsupported geometry or missing shape data")?;
146
147        // default stroke and fill settings to use if not present in the feature properties
148        let mut stroke = Stroke::new(1.0, Color32::RED);
149        let mut fill = Color32::TRANSPARENT;
150
151        if let Some(properties) = &feature.properties {
152            check_version_from_properties(properties);
153            if let Some(value) = properties.get("stroke_width") {
154                if let Some(width) = value.as_f64() {
155                    stroke.width = width as f32;
156                }
157            }
158            if let Some(value) = properties.get("stroke_color") {
159                if let Some(s) = value.as_str() {
160                    if let Ok(color) = Color32::from_hex(s) {
161                        stroke.color = color;
162                    }
163                }
164            }
165            if let Some(value) = properties.get("fill_color") {
166                if let Some(s) = value.as_str() {
167                    if let Ok(color) = Color32::from_hex(s) {
168                        fill = color;
169                    }
170                }
171            }
172        }
173
174        Ok(Area {
175            shape,
176            stroke,
177            fill,
178        })
179    }
180}
181
182impl From<Polyline> for Feature {
183    fn from(polyline: Polyline) -> Self {
184        let mut feature = Feature::default();
185        let mut properties = Map::new();
186        add_version_to_properties(&mut properties);
187        feature.properties = Some(properties);
188        let line_string: Vec<Vec<f64>> = polyline.0.iter().map(|gp| (*gp).into()).collect();
189        feature.geometry = Some(Geometry::new(Value::LineString(line_string)));
190        feature
191    }
192}
193
194impl TryFrom<Feature> for Polyline {
195    type Error = String;
196
197    fn try_from(feature: Feature) -> Result<Self, Self::Error> {
198        if let Some(geometry) = feature.geometry {
199            if let Value::LineString(line_string) = geometry.value {
200                return Ok(Polyline(
201                    line_string.iter().map(|pos| pos.clone().into()).collect(),
202                ));
203            }
204        }
205        if let Some(properties) = &feature.properties {
206            check_version_from_properties(properties);
207        }
208        Err("Feature is not a LineString".to_string())
209    }
210}
211
212impl From<Text> for Feature {
213    fn from(text: Text) -> Self {
214        let mut feature = Feature::default();
215        let mut properties = Map::new();
216        add_version_to_properties(&mut properties);
217        let point = Geometry::new(Value::Point(text.pos.into()));
218        feature.geometry = Some(point);
219        properties.insert("text".to_string(), JsonValue::String(text.text));
220        properties.insert("color".to_string(), JsonValue::String(text.color.to_hex()));
221        properties.insert(
222            "background".to_string(),
223            JsonValue::String(text.background.to_hex()),
224        );
225
226        match text.size {
227            TextSize::Static(size) => {
228                properties.insert(
229                    "size_type".to_string(),
230                    JsonValue::String("Static".to_string()),
231                );
232                properties.insert("size".to_string(), JsonValue::from(size));
233            }
234            TextSize::Relative(size) => {
235                properties.insert(
236                    "size_type".to_string(),
237                    JsonValue::String("Relative".to_string()),
238                );
239                properties.insert("size".to_string(), JsonValue::from(size));
240            }
241        }
242
243        feature.properties = Some(properties);
244        feature
245    }
246}
247
248impl TryFrom<Feature> for Text {
249    type Error = String;
250
251    fn try_from(feature: Feature) -> Result<Self, Self::Error> {
252        let mut text = Text::default();
253        if let Some(geometry) = feature.geometry {
254            if let Value::Point(point) = geometry.value {
255                text.pos = point.into();
256            } else {
257                return Err("Feature is not a Point".to_string());
258            }
259        } else {
260            return Err("Feature has no geometry".to_string());
261        }
262
263        if let Some(properties) = feature.properties {
264            check_version_from_properties(&properties);
265            if let Some(value) = properties.get("text") {
266                if let Some(s) = value.as_str() {
267                    text.text = s.to_string();
268                } else {
269                    return Err("Property 'text' is not a string".to_string());
270                }
271            } else {
272                return Err("Feature has no 'text' property".to_string());
273            }
274            if let Some(value) = properties.get("color") {
275                if let Some(s) = value.as_str() {
276                    if let Ok(color) = Color32::from_hex(s) {
277                        text.color = color;
278                    }
279                }
280            }
281            if let Some(value) = properties.get("background") {
282                if let Some(s) = value.as_str() {
283                    if let Ok(color) = Color32::from_hex(s) {
284                        text.background = color;
285                    }
286                }
287            }
288            if let Some(size_type) = properties.get("size_type") {
289                if let Some(size) = properties.get("size") {
290                    if let Some(size_f32) = size.as_f64() {
291                        if size_type == "Static" {
292                            text.size = TextSize::Static(size_f32 as f32);
293                        } else if size_type == "Relative" {
294                            text.size = TextSize::Relative(size_f32 as f32);
295                        }
296                    }
297                }
298            }
299        }
300        Ok(text)
301    }
302}