egui_map_view/layers/
geojson.rs1use 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
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
65 match area.shape {
66 AreaShape::Polygon(points) => {
67 let polygon_points: Vec<Vec<Vec<f64>>> = vec![
68 points
69 .iter()
70 .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 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 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}