1use super::area::{Area, AreaShape, FillType};
4use super::drawing::Polyline;
5use super::text::{Text, TextSize};
6use crate::projection::GeoPos;
7use egui::{Color32, Stroke};
8use geojson::{Feature, Geometry, GeometryValue};
9use serde_json::{Map, Value as JsonValue};
10
11fn 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
23fn 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 properties.insert(
65 "fill_type".to_string(),
66 JsonValue::String(
67 match area.fill_type {
68 FillType::None => "None",
69 FillType::Solid => "Solid",
70 FillType::Hatching => "Hatching",
71 }
72 .to_string(),
73 ),
74 );
75
76 match area.shape {
77 AreaShape::Polygon(points) => {
78 let polygon_points: Vec<Vec<geojson::Position>> = vec![
79 points
80 .iter()
81 .chain(points.first())
83 .map(|gp| geojson::Position::from(vec![gp.lon, gp.lat]))
84 .collect(),
85 ];
86 feature.geometry = Some(Geometry::new(GeometryValue::Polygon {
87 coordinates: polygon_points,
88 }));
89 }
90 AreaShape::Circle {
91 center,
92 radius,
93 points,
94 } => {
95 let point = Geometry::new(GeometryValue::Point {
96 coordinates: geojson::Position::from(vec![center.lon, center.lat]),
97 });
98 feature.geometry = Some(point);
99 properties.insert("radius".to_string(), JsonValue::from(radius));
100 if let Some(p) = points {
101 properties.insert("points".to_string(), JsonValue::from(p));
102 }
103 }
104 AreaShape::Ellipse {
105 center,
106 radius_major,
107 radius_minor,
108 rotation,
109 points,
110 } => {
111 let point = Geometry::new(GeometryValue::Point {
112 coordinates: geojson::Position::from(vec![center.lon, center.lat]),
113 });
114 feature.geometry = Some(point);
115 properties.insert("radius_major".to_string(), JsonValue::from(radius_major));
116 properties.insert("radius_minor".to_string(), JsonValue::from(radius_minor));
117 properties.insert("rotation".to_string(), JsonValue::from(rotation));
118 if let Some(p) = points {
119 properties.insert("points".to_string(), JsonValue::from(p));
120 }
121 }
122 }
123
124 feature.properties = Some(properties);
125 feature
126 }
127}
128
129impl TryFrom<Feature> for Area {
130 type Error = String;
131
132 fn try_from(feature: Feature) -> Result<Self, Self::Error> {
133 let shape = if let Some(geometry) = &feature.geometry {
134 match &geometry.value {
135 GeometryValue::Polygon {
136 coordinates: points,
137 } => {
138 let mut polygon_points: Vec<GeoPos> = points
139 .first()
140 .ok_or("Polygon has no rings")?
141 .iter()
142 .map(|pos| GeoPos {
143 lon: pos[0],
144 lat: pos[1],
145 })
146 .collect();
147
148 if polygon_points.first() == polygon_points.last() {
150 polygon_points.pop();
151 }
152
153 Some(AreaShape::Polygon(polygon_points))
154 }
155 GeometryValue::Point { coordinates: point } => {
156 let properties = feature
157 .properties
158 .as_ref()
159 .ok_or("Feature has no properties")?;
160 let center = GeoPos {
161 lon: point[0],
162 lat: point[1],
163 };
164 let points = properties.get("points").and_then(serde_json::Value::as_i64);
165
166 if properties.contains_key("radius_major")
167 || properties.contains_key("radius_minor")
168 {
169 let radius_major = properties
170 .get("radius_major")
171 .and_then(serde_json::Value::as_f64)
172 .unwrap_or_default();
173 let radius_minor = properties
174 .get("radius_minor")
175 .and_then(serde_json::Value::as_f64)
176 .unwrap_or_default();
177 let rotation = properties
178 .get("rotation")
179 .and_then(serde_json::Value::as_f64)
180 .unwrap_or_default();
181
182 if radius_major <= 0.0 || radius_minor <= 0.0 {
183 return Err("Radii must be greater than 0".to_string());
184 }
185
186 Some(AreaShape::Ellipse {
187 center,
188 radius_major,
189 radius_minor,
190 rotation,
191 points,
192 })
193 } else {
194 let radius = properties
195 .get("radius")
196 .and_then(serde_json::Value::as_f64)
197 .unwrap_or_default();
198
199 if radius <= 0.0 {
200 return Err("Radius must be greater than 0".to_string());
201 }
202
203 Some(AreaShape::Circle {
204 center,
205 radius,
206 points,
207 })
208 }
209 }
210 _ => None,
211 }
212 } else {
213 None
214 };
215
216 let shape = shape.ok_or("Unsupported geometry or missing shape data")?;
217
218 let mut stroke = Stroke::new(1.0, Color32::RED);
220 let mut fill = Color32::TRANSPARENT;
221
222 if let Some(properties) = &feature.properties {
223 check_version_from_properties(properties);
224 if let Some(value) = properties.get("stroke_width")
225 && let Some(width) = value.as_f64()
226 {
227 stroke.width = width as f32;
228 }
229 if let Some(value) = properties.get("stroke_color")
230 && let Some(s) = value.as_str()
231 && let Ok(color) = Color32::from_hex(s)
232 {
233 stroke.color = color;
234 }
235 if let Some(value) = properties.get("fill_color")
236 && let Some(s) = value.as_str()
237 && let Ok(color) = Color32::from_hex(s)
238 {
239 fill = color;
240 }
241 }
242
243 let fill_type = if let Some(properties) = &feature.properties {
244 match properties.get("fill_type").and_then(|v| v.as_str()) {
245 Some("None") => FillType::None,
246 Some("Hatching") => FillType::Hatching,
247 _ => FillType::Solid, }
249 } else {
250 FillType::Solid
251 };
252
253 Ok(Area {
254 shape,
255 stroke,
256 fill,
257 fill_type,
258 })
259 }
260}
261
262impl From<Polyline> for Feature {
263 fn from(polyline: Polyline) -> Self {
264 let mut feature = Feature::default();
265 let mut properties = Map::new();
266 add_version_to_properties(&mut properties);
267 feature.properties = Some(properties);
268 let line_string: Vec<geojson::Position> = polyline
269 .0
270 .iter()
271 .map(|gp| geojson::Position::from(vec![gp.lon, gp.lat]))
272 .collect();
273 feature.geometry = Some(Geometry::new(GeometryValue::LineString {
274 coordinates: line_string,
275 }));
276 feature
277 }
278}
279
280impl TryFrom<Feature> for Polyline {
281 type Error = String;
282
283 fn try_from(feature: Feature) -> Result<Self, Self::Error> {
284 if let Some(geometry) = feature.geometry
285 && let GeometryValue::LineString {
286 coordinates: line_string,
287 } = geometry.value
288 {
289 return Ok(Polyline(
290 line_string
291 .iter()
292 .map(|pos| GeoPos {
293 lon: pos[0],
294 lat: pos[1],
295 })
296 .collect(),
297 ));
298 }
299 if let Some(properties) = &feature.properties {
300 check_version_from_properties(properties);
301 }
302 Err("Feature is not a LineString".to_string())
303 }
304}
305
306impl From<Text> for Feature {
307 fn from(text: Text) -> Self {
308 let mut feature = Feature::default();
309 let mut properties = Map::new();
310 add_version_to_properties(&mut properties);
311 let point = Geometry::new(GeometryValue::Point {
312 coordinates: geojson::Position::from(vec![text.pos.lon, text.pos.lat]),
313 });
314 feature.geometry = Some(point);
315 properties.insert("text".to_string(), JsonValue::String(text.text));
316 properties.insert("color".to_string(), JsonValue::String(text.color.to_hex()));
317 properties.insert(
318 "background".to_string(),
319 JsonValue::String(text.background.to_hex()),
320 );
321
322 match text.size {
323 TextSize::Static(size) => {
324 properties.insert(
325 "size_type".to_string(),
326 JsonValue::String("Static".to_string()),
327 );
328 properties.insert("size".to_string(), JsonValue::from(size));
329 }
330 TextSize::Relative(size) => {
331 properties.insert(
332 "size_type".to_string(),
333 JsonValue::String("Relative".to_string()),
334 );
335 properties.insert("size".to_string(), JsonValue::from(size));
336 }
337 }
338
339 feature.properties = Some(properties);
340 feature
341 }
342}
343
344impl TryFrom<Feature> for Text {
345 type Error = String;
346
347 fn try_from(feature: Feature) -> Result<Self, Self::Error> {
348 let mut text = Text::default();
349 if let Some(geometry) = feature.geometry {
350 if let GeometryValue::Point { coordinates: point } = geometry.value {
351 text.pos = GeoPos {
352 lon: point[0],
353 lat: point[1],
354 };
355 } else {
356 return Err("Feature is not a Point".to_string());
357 }
358 } else {
359 return Err("Feature has no geometry".to_string());
360 }
361
362 if let Some(properties) = feature.properties {
363 check_version_from_properties(&properties);
364 if let Some(value) = properties.get("text") {
365 if let Some(s) = value.as_str() {
366 text.text = s.to_string();
367 } else {
368 return Err("Property 'text' is not a string".to_string());
369 }
370 } else {
371 return Err("Feature has no 'text' property".to_string());
372 }
373 if let Some(value) = properties.get("color")
374 && let Some(s) = value.as_str()
375 && let Ok(color) = Color32::from_hex(s)
376 {
377 text.color = color;
378 }
379 if let Some(value) = properties.get("background")
380 && let Some(s) = value.as_str()
381 && let Ok(color) = Color32::from_hex(s)
382 {
383 text.background = color;
384 }
385 if let Some(size_type) = properties.get("size_type")
386 && let Some(size) = properties.get("size")
387 && let Some(size_f32) = size.as_f64()
388 {
389 if size_type == "Static" {
390 text.size = TextSize::Static(size_f32 as f32);
391 } else if size_type == "Relative" {
392 text.size = TextSize::Relative(size_f32 as f32);
393 }
394 }
395 }
396 Ok(text)
397 }
398}