euv_chart/geo/impl.rs
1use super::*;
2
3/// Inherent implementation of [`GeoPoint`].
4impl GeoPoint {
5 /// Returns a point of the globe.
6 ///
7 /// # Arguments
8 ///
9 /// - `f64` - A 64-bit float (`f64`).
10 pub fn new(lng: f64, lat: f64) -> Self {
11 Self { lng, lat }
12 }
13}
14
15/// Default construction for [`GeoBounds`].
16impl Default for GeoBounds {
17 /// Constructs a default [`GeoBounds`] value.
18 fn default() -> Self {
19 Self {
20 min_lng: f64::INFINITY,
21 min_lat: f64::INFINITY,
22 max_lng: f64::NEG_INFINITY,
23 max_lat: f64::NEG_INFINITY,
24 }
25 }
26}
27
28/// Inherent implementation of [`GeoBounds`].
29impl GeoBounds {
30 /// Returns the width of the box, in degrees.
31 ///
32 /// # Returns
33 ///
34 /// - `f64` - A 64-bit float.
35 pub fn width(&self) -> f64 {
36 self.get_max_lng() - self.get_min_lng()
37 }
38
39 /// Returns the height of the box, in degrees.
40 ///
41 /// # Returns
42 ///
43 /// - `f64` - A 64-bit float.
44 pub fn height(&self) -> f64 {
45 self.get_max_lat() - self.get_min_lat()
46 }
47
48 /// Returns `true` when the box holds no point at all.
49 ///
50 /// # Returns
51 ///
52 /// - `bool` - A boolean.
53 pub fn is_empty(&self) -> bool {
54 !(self.get_min_lng() <= self.get_max_lng() && self.get_min_lat() <= self.get_max_lat())
55 }
56
57 /// Extends the box so that it holds the given point.
58 ///
59 /// # Arguments
60 ///
61 /// - `GeoPoint` - A `GeoPoint` parameter.
62 pub fn extend(&mut self, point: GeoPoint) {
63 self.set_min_lng(self.get_min_lng().min(point.lng));
64 self.set_min_lat(self.get_min_lat().min(point.lat));
65 self.set_max_lng(self.get_max_lng().max(point.lng));
66 self.set_max_lat(self.get_max_lat().max(point.lat));
67 }
68}
69
70/// Inherent implementation of [`MapRegion`].
71impl MapRegion {
72 /// Returns the middle of the region, in geographic coordinates.
73 ///
74 /// The middle is the centre of gravity of the outline, which is
75 /// where a label of the region belongs. A region without a usable
76 /// outline has no middle.
77 ///
78 /// # Returns
79 ///
80 /// - `Option<GeoPoint>` - `Some(...)` on success, `None` otherwise.
81 pub fn center(&self) -> Option<GeoPoint> {
82 let polygon: &RegionPolygon = self.get_polygons().first()?;
83 ring_center(&polygon.outer)
84 }
85}
86
87/// Inherent implementation of [`MapData`].
88impl MapData {
89 /// Builds a map out of its regions, and measures the box they cover.
90 ///
91 /// # Arguments
92 ///
93 /// - `&str` - Shared reference to a `str`.
94 /// - `Vec<MapRegion>` - A `Vec<MapRegion>` parameter.
95 pub fn from_regions(name: &str, regions: Vec<MapRegion>) -> Self {
96 let mut bounds: GeoBounds = GeoBounds::default();
97 for region in ®ions {
98 for polygon in ®ion.polygons {
99 for point in &polygon.outer {
100 bounds.extend(*point);
101 }
102 }
103 }
104 Self {
105 name: name.to_string(),
106 regions,
107 bounds,
108 }
109 }
110
111 /// Reads a GeoJSON document into a map.
112 ///
113 /// The document is the shape GeoJSON has: a collection of features,
114 /// each with a geometry and a name. The name is taken from the
115 /// `name` property of the feature, because that is where GeoJSON
116 /// keeps it.
117 ///
118 /// # Arguments
119 ///
120 /// - `&str` - Shared reference to a `str`.
121 /// - `&DataValue` - Shared reference to a `DataValue`.
122 ///
123 /// # Returns
124 ///
125 /// - `Result<Self, GeoError>` - Result of the operation; an `Err` variant on failure.
126 pub fn from_geojson(name: &str, value: &DataValue) -> Result<Self, GeoError> {
127 parse_geojson(name, value)
128 }
129
130 /// Returns the region with the given name.
131 ///
132 /// # Arguments
133 ///
134 /// - `&str` - Shared reference to a `str`.
135 ///
136 /// # Returns
137 ///
138 /// - `Option<&MapRegion>` - `Some(...)` on success, `None` otherwise.
139 pub fn region(&self, name: &str) -> Option<&MapRegion> {
140 self.get_regions()
141 .iter()
142 .find(|region: &&MapRegion| region.name == name)
143 }
144}
145
146/// Inherent implementation of [`MapRegistry`].
147impl MapRegistry {
148 /// Returns an empty registry.
149 pub fn new() -> Self {
150 Self {
151 maps: BTreeMap::new(),
152 }
153 }
154
155 /// Adds a map to the registry, replacing the one of the same name.
156 ///
157 /// # Arguments
158 ///
159 /// - `MapData` - A `MapData` parameter.
160 ///
161 /// # Returns
162 ///
163 /// - `&mut Self` - A `mut Self` value.
164 pub fn register(&mut self, data: MapData) -> &mut Self {
165 self.get_mut_maps().insert(data.name.clone(), data);
166 self
167 }
168
169 /// Reads a GeoJSON document and adds the map it holds.
170 ///
171 /// # Arguments
172 ///
173 /// - `&str` - Shared reference to a `str`.
174 /// - `&DataValue` - Shared reference to a `DataValue`.
175 ///
176 /// # Returns
177 ///
178 /// - `Result<&mut Self, GeoError>` - Result of the operation; an `Err` variant on failure.
179 pub fn register_geojson(
180 &mut self,
181 name: &str,
182 value: &DataValue,
183 ) -> Result<&mut Self, GeoError> {
184 let data: MapData = MapData::from_geojson(name, value)?;
185 self.get_mut_maps().insert(name.to_string(), data);
186 Ok(self)
187 }
188
189 /// Returns the map with the given name.
190 ///
191 /// # Arguments
192 ///
193 /// - `&str` - Shared reference to a `str`.
194 ///
195 /// # Returns
196 ///
197 /// - `Option<&MapData>` - `Some(...)` on success, `None` otherwise.
198 pub fn get(&self, name: &str) -> Option<&MapData> {
199 self.get_maps().get(name)
200 }
201
202 /// Returns the names of the maps of the registry.
203 ///
204 /// # Returns
205 ///
206 /// - `Vec<String>` - A `Vec<String>` value.
207 pub fn names(&self) -> Vec<String> {
208 self.get_maps().keys().cloned().collect()
209 }
210
211 /// Returns the number of maps in the registry.
212 ///
213 /// # Returns
214 ///
215 /// - `usize` - A non-negative integer.
216 pub fn len(&self) -> usize {
217 self.get_maps().len()
218 }
219
220 /// Returns `true` when the registry holds no map.
221 ///
222 /// # Returns
223 ///
224 /// - `bool` - A boolean.
225 pub fn is_empty(&self) -> bool {
226 self.get_maps().is_empty()
227 }
228
229 /// Removes every map from the registry.
230 pub fn clear(&mut self) {
231 self.get_mut_maps().clear();
232 }
233}
234
235/// Inherent implementation of [`GeoLayout`].
236impl GeoLayout {
237 /// Projects a map into a rectangle of the chart.
238 ///
239 /// The projection is the one ECharts uses: the longitude and the
240 /// latitude are scaled evenly, and the height of the map is
241 /// shortened by the aspect scale so that a map of the mid-latitudes
242 /// does not look stretched sideways. The map is centred in the
243 /// rectangle and takes most of it.
244 ///
245 /// # Arguments
246 ///
247 /// - `&MapData` - Shared reference to a `MapData`.
248 /// - `BBox` - A `BBox` parameter.
249 /// - `f64` - A 64-bit float (`f64`).
250 pub fn compute(data: &MapData, rect: BBox, aspect_scale: f64) -> Self {
251 let bounds: GeoBounds = data.bounds;
252 let geo_width: f64 = bounds.width().max(f64::EPSILON);
253 let geo_height: f64 = bounds.height().max(f64::EPSILON);
254 // The aspect of the map: its width over its height, shortened
255 // by the aspect scale.
256 let aspect: f64 = geo_width / geo_height * aspect_scale.max(f64::EPSILON);
257 let view: BBox = fit_aspect(rect, aspect);
258 // The map is scaled evenly. The direction that binds leaves the
259 // map smaller than the view, and the map is centred in it.
260 let scale: f64 = (view.width() / geo_width).min(view.height() / geo_height);
261 let drawn_width: f64 = geo_width * scale;
262 let drawn_height: f64 = geo_height * scale;
263 let origin_x: f64 = view.min_x + (view.width() - drawn_width) * 0.5;
264 let origin_y: f64 = view.min_y + (view.height() - drawn_height) * 0.5;
265 // The longitude grows to the right and the latitude grows
266 // upwards, while the screen grows downwards: the vertical axis
267 // is flipped.
268 let transform: Transform2D = Transform2D {
269 a: scale,
270 b: 0.0,
271 c: 0.0,
272 d: -scale,
273 e: origin_x - bounds.min_lng * scale,
274 f: origin_y + bounds.max_lat * scale,
275 };
276 let regions: Vec<GeoRegionLayout> = data
277 .regions
278 .iter()
279 .map(|region: &MapRegion| {
280 let path: Path = region_path(region, &transform);
281 let center: Point = region_center(region, &transform);
282 GeoRegionLayout {
283 name: region.name.clone(),
284 path,
285 center,
286 }
287 })
288 .collect();
289 Self {
290 name: data.name.clone(),
291 regions,
292 transform,
293 }
294 }
295
296 /// Returns the region with the given name.
297 ///
298 /// # Arguments
299 ///
300 /// - `&str` - Shared reference to a `str`.
301 ///
302 /// # Returns
303 ///
304 /// - `Option<&GeoRegionLayout>` - `Some(...)` on success, `None` otherwise.
305 pub fn region(&self, name: &str) -> Option<&GeoRegionLayout> {
306 self.get_regions()
307 .iter()
308 .find(|region: &&GeoRegionLayout| region.name == name)
309 }
310}
311
312/// Implements `impl std::fmt::Display for GeoError`.
313impl Display for GeoError {
314 /// Formats the [`GeoError`] via the supplied formatter.
315 ///
316 /// # Arguments
317 ///
318 /// - `&mut std::fmt::Formatter<'_>` - The formatter receiving the formatted output.
319 ///
320 /// # Returns
321 ///
322 /// - `std::fmt::Result` - A `std::fmt::Result` value.
323 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
324 match self {
325 GeoError::MissingType => write!(formatter, "the map has no `type` field"),
326 GeoError::UnsupportedType(kind) => {
327 write!(formatter, "the map kind `{}` is not supported", kind)
328 }
329 GeoError::MissingGeometry => write!(formatter, "the map feature has no geometry"),
330 GeoError::MalformedGeometry(reason) => {
331 write!(formatter, "the map geometry is malformed: {}", reason)
332 }
333 }
334 }
335}
336
337/// Implements `impl std::error::Error for GeoError {}`.
338impl Error for GeoError {}