egui_map_view/layers/
geojson.rs1use 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 }
105
106 feature.properties = Some(properties);
107 feature
108 }
109}
110
111impl TryFrom<Feature> for Area {
112 type Error = String;
113
114 fn try_from(feature: Feature) -> Result<Self, Self::Error> {
115 let shape = if let Some(geometry) = &feature.geometry {
116 match &geometry.value {
117 GeometryValue::Polygon {
118 coordinates: points,
119 } => {
120 let mut polygon_points: Vec<GeoPos> = points
121 .first()
122 .ok_or("Polygon has no rings")?
123 .iter()
124 .map(|pos| GeoPos {
125 lon: pos[0],
126 lat: pos[1],
127 })
128 .collect();
129
130 if polygon_points.first() == polygon_points.last() {
132 polygon_points.pop();
133 }
134
135 Some(AreaShape::Polygon(polygon_points))
136 }
137 GeometryValue::Point { coordinates: point } => {
138 let properties = feature
139 .properties
140 .as_ref()
141 .ok_or("Feature has no properties")?;
142 let center = GeoPos {
143 lon: point[0],
144 lat: point[1],
145 };
146 let radius = properties
147 .get("radius")
148 .and_then(serde_json::Value::as_f64)
149 .unwrap_or_default();
150 let points = properties.get("points").and_then(serde_json::Value::as_i64);
151
152 if radius <= 0.0 {
153 return Err("Radius must be greater than 0".to_string());
154 }
155
156 Some(AreaShape::Circle {
157 center,
158 radius,
159 points,
160 })
161 }
162 _ => None,
163 }
164 } else {
165 None
166 };
167
168 let shape = shape.ok_or("Unsupported geometry or missing shape data")?;
169
170 let mut stroke = Stroke::new(1.0, Color32::RED);
172 let mut fill = Color32::TRANSPARENT;
173
174 if let Some(properties) = &feature.properties {
175 check_version_from_properties(properties);
176 if let Some(value) = properties.get("stroke_width")
177 && let Some(width) = value.as_f64()
178 {
179 stroke.width = width as f32;
180 }
181 if let Some(value) = properties.get("stroke_color")
182 && let Some(s) = value.as_str()
183 && let Ok(color) = Color32::from_hex(s)
184 {
185 stroke.color = color;
186 }
187 if let Some(value) = properties.get("fill_color")
188 && let Some(s) = value.as_str()
189 && let Ok(color) = Color32::from_hex(s)
190 {
191 fill = color;
192 }
193 }
194
195 let fill_type = if let Some(properties) = &feature.properties {
196 match properties.get("fill_type").and_then(|v| v.as_str()) {
197 Some("None") => FillType::None,
198 Some("Hatching") => FillType::Hatching,
199 _ => FillType::Solid, }
201 } else {
202 FillType::Solid
203 };
204
205 Ok(Area {
206 shape,
207 stroke,
208 fill,
209 fill_type,
210 })
211 }
212}
213
214impl From<Polyline> for Feature {
215 fn from(polyline: Polyline) -> Self {
216 let mut feature = Feature::default();
217 let mut properties = Map::new();
218 add_version_to_properties(&mut properties);
219 feature.properties = Some(properties);
220 let line_string: Vec<geojson::Position> = polyline
221 .0
222 .iter()
223 .map(|gp| geojson::Position::from(vec![gp.lon, gp.lat]))
224 .collect();
225 feature.geometry = Some(Geometry::new(GeometryValue::LineString {
226 coordinates: line_string,
227 }));
228 feature
229 }
230}
231
232impl TryFrom<Feature> for Polyline {
233 type Error = String;
234
235 fn try_from(feature: Feature) -> Result<Self, Self::Error> {
236 if let Some(geometry) = feature.geometry
237 && let GeometryValue::LineString {
238 coordinates: line_string,
239 } = geometry.value
240 {
241 return Ok(Polyline(
242 line_string
243 .iter()
244 .map(|pos| GeoPos {
245 lon: pos[0],
246 lat: pos[1],
247 })
248 .collect(),
249 ));
250 }
251 if let Some(properties) = &feature.properties {
252 check_version_from_properties(properties);
253 }
254 Err("Feature is not a LineString".to_string())
255 }
256}
257
258impl From<Text> for Feature {
259 fn from(text: Text) -> Self {
260 let mut feature = Feature::default();
261 let mut properties = Map::new();
262 add_version_to_properties(&mut properties);
263 let point = Geometry::new(GeometryValue::Point {
264 coordinates: geojson::Position::from(vec![text.pos.lon, text.pos.lat]),
265 });
266 feature.geometry = Some(point);
267 properties.insert("text".to_string(), JsonValue::String(text.text));
268 properties.insert("color".to_string(), JsonValue::String(text.color.to_hex()));
269 properties.insert(
270 "background".to_string(),
271 JsonValue::String(text.background.to_hex()),
272 );
273
274 match text.size {
275 TextSize::Static(size) => {
276 properties.insert(
277 "size_type".to_string(),
278 JsonValue::String("Static".to_string()),
279 );
280 properties.insert("size".to_string(), JsonValue::from(size));
281 }
282 TextSize::Relative(size) => {
283 properties.insert(
284 "size_type".to_string(),
285 JsonValue::String("Relative".to_string()),
286 );
287 properties.insert("size".to_string(), JsonValue::from(size));
288 }
289 }
290
291 feature.properties = Some(properties);
292 feature
293 }
294}
295
296impl TryFrom<Feature> for Text {
297 type Error = String;
298
299 fn try_from(feature: Feature) -> Result<Self, Self::Error> {
300 let mut text = Text::default();
301 if let Some(geometry) = feature.geometry {
302 if let GeometryValue::Point { coordinates: point } = geometry.value {
303 text.pos = GeoPos {
304 lon: point[0],
305 lat: point[1],
306 };
307 } else {
308 return Err("Feature is not a Point".to_string());
309 }
310 } else {
311 return Err("Feature has no geometry".to_string());
312 }
313
314 if let Some(properties) = feature.properties {
315 check_version_from_properties(&properties);
316 if let Some(value) = properties.get("text") {
317 if let Some(s) = value.as_str() {
318 text.text = s.to_string();
319 } else {
320 return Err("Property 'text' is not a string".to_string());
321 }
322 } else {
323 return Err("Feature has no 'text' property".to_string());
324 }
325 if let Some(value) = properties.get("color")
326 && let Some(s) = value.as_str()
327 && let Ok(color) = Color32::from_hex(s)
328 {
329 text.color = color;
330 }
331 if let Some(value) = properties.get("background")
332 && let Some(s) = value.as_str()
333 && let Ok(color) = Color32::from_hex(s)
334 {
335 text.background = color;
336 }
337 if let Some(size_type) = properties.get("size_type")
338 && let Some(size) = properties.get("size")
339 && let Some(size_f32) = size.as_f64()
340 {
341 if size_type == "Static" {
342 text.size = TextSize::Static(size_f32 as f32);
343 } else if size_type == "Relative" {
344 text.size = TextSize::Relative(size_f32 as f32);
345 }
346 }
347 }
348 Ok(text)
349 }
350}