1#![doc = include_str!("../README.md")]
2
3pub mod surface;
4use std::collections::BTreeMap;
5use std::fmt;
6
7use serde::{Deserialize, Serialize};
8use serde_json::Value;
9
10#[derive(Debug)]
12pub enum GeoError {
13 InvalidArgument(String),
15 Source(String),
17 Io(std::io::Error),
19}
20
21impl GeoError {
22 pub fn invalid_argument(message: impl Into<String>) -> Self {
24 Self::InvalidArgument(message.into())
25 }
26
27 pub fn source(message: impl Into<String>) -> Self {
29 Self::Source(message.into())
30 }
31}
32
33impl fmt::Display for GeoError {
34 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
35 match self {
36 Self::InvalidArgument(message) => write!(formatter, "invalid argument: {message}"),
37 Self::Source(message) => write!(formatter, "source error: {message}"),
38 Self::Io(error) => write!(formatter, "I/O error: {error}"),
39 }
40 }
41}
42
43impl std::error::Error for GeoError {
44 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
45 match self {
46 Self::Io(error) => Some(error),
47 Self::InvalidArgument(_) | Self::Source(_) => None,
48 }
49 }
50}
51
52impl From<std::io::Error> for GeoError {
53 fn from(error: std::io::Error) -> Self {
54 Self::Io(error)
55 }
56}
57
58pub type Result<T> = std::result::Result<T, GeoError>;
60
61pub type Properties = BTreeMap<String, Value>;
63
64pub type Position = [f64; 2];
66
67const GEOMETRY_EPSILON: f64 = 1e-12;
68
69fn invalid_argument(message: impl Into<String>) -> GeoError {
70 GeoError::invalid_argument(message)
71}
72
73#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
74pub struct Coordinate {
76 pub lon: f64,
78 pub lat: f64,
80}
81
82impl Coordinate {
83 pub fn new(lon: f64, lat: f64) -> Result<Self> {
85 let coordinate = Self { lon, lat };
86 coordinate.validate()?;
87 Ok(coordinate)
88 }
89
90 pub fn from_position(position: Position) -> Result<Self> {
92 Self::new(position[0], position[1])
93 }
94
95 pub fn as_position(self) -> Position {
97 [self.lon, self.lat]
98 }
99
100 pub fn validate(self) -> Result<()> {
102 if !self.lon.is_finite() || !self.lat.is_finite() {
103 return Err(invalid_argument("coordinate values must be finite"));
104 }
105 Ok(())
106 }
107
108 pub fn validate_geographic(self) -> Result<()> {
110 self.validate()?;
111 if !(-180.0..=180.0).contains(&self.lon) {
112 return Err(invalid_argument(
113 "coordinate longitude must be between -180 and 180",
114 ));
115 }
116 if !(-90.0..=90.0).contains(&self.lat) {
117 return Err(invalid_argument(
118 "coordinate latitude must be between -90 and 90",
119 ));
120 }
121 Ok(())
122 }
123}
124
125impl From<Coordinate> for Position {
126 fn from(value: Coordinate) -> Self {
127 value.as_position()
128 }
129}
130
131impl TryFrom<Position> for Coordinate {
132 type Error = GeoError;
133
134 fn try_from(value: Position) -> Result<Self> {
135 Self::from_position(value)
136 }
137}
138
139#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
140pub struct BBox {
142 pub min_lon: f64,
144 pub min_lat: f64,
146 pub max_lon: f64,
148 pub max_lat: f64,
150}
151
152impl BBox {
153 pub fn new(values: [f64; 4]) -> Result<Self> {
155 let bbox = Self {
156 min_lon: values[0],
157 min_lat: values[1],
158 max_lon: values[2],
159 max_lat: values[3],
160 };
161 bbox.validate()?;
162 Ok(bbox)
163 }
164
165 pub fn as_array(self) -> [f64; 4] {
167 [self.min_lon, self.min_lat, self.max_lon, self.max_lat]
168 }
169
170 pub fn validate(self) -> Result<()> {
172 if !self.min_lon.is_finite()
173 || !self.min_lat.is_finite()
174 || !self.max_lon.is_finite()
175 || !self.max_lat.is_finite()
176 {
177 return Err(invalid_argument("bbox values must be finite"));
178 }
179 if self.min_lon > self.max_lon {
180 return Err(invalid_argument("bbox min_lon must be <= max_lon"));
181 }
182 if self.min_lat > self.max_lat {
183 return Err(invalid_argument("bbox min_lat must be <= max_lat"));
184 }
185 Ok(())
186 }
187
188 pub fn validate_geographic(self) -> Result<()> {
190 self.validate()?;
191 Coordinate::new(self.min_lon, self.min_lat)?.validate_geographic()?;
192 Coordinate::new(self.max_lon, self.max_lat)?.validate_geographic()?;
193 Ok(())
194 }
195
196 pub fn contains(self, coordinate: Coordinate) -> bool {
198 coordinate.lon >= self.min_lon
199 && coordinate.lon <= self.max_lon
200 && coordinate.lat >= self.min_lat
201 && coordinate.lat <= self.max_lat
202 }
203
204 pub fn intersects_coordinates(self, coordinates: &[Coordinate]) -> bool {
206 coordinates
207 .iter()
208 .copied()
209 .any(|coordinate| self.contains(coordinate))
210 }
211
212 pub fn intersects_geometry(self, geometry: &Geometry) -> bool {
214 geometry_intersects_bbox(geometry, self)
215 }
216}
217
218#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
219#[serde(tag = "type")]
220pub enum Geometry {
222 Point {
224 coordinates: Position,
226 },
227 MultiPoint {
229 coordinates: Vec<Position>,
231 },
232 LineString {
234 coordinates: Vec<Position>,
236 },
237 MultiLineString {
239 coordinates: Vec<Vec<Position>>,
241 },
242 Polygon {
244 coordinates: Vec<Vec<Position>>,
246 },
247 MultiPolygon {
249 coordinates: Vec<Vec<Vec<Position>>>,
251 },
252 GeometryCollection {
254 geometries: Vec<Geometry>,
256 },
257}
258
259impl Geometry {
260 pub fn validate(&self) -> Result<()> {
262 match self {
263 Self::Point { coordinates } => {
264 Coordinate::from_position(*coordinates)?;
265 }
266 Self::MultiPoint { coordinates } => {
267 validate_positions(coordinates, "multipoint coordinates")?;
268 }
269 Self::LineString { coordinates } => {
270 validate_line_positions(coordinates, "linestring coordinates")?;
271 }
272 Self::MultiLineString { coordinates } => {
273 for line in coordinates {
274 validate_line_positions(line, "multilinestring coordinates")?;
275 }
276 }
277 Self::Polygon { coordinates } => {
278 validate_polygon_positions(coordinates, "polygon coordinates")?;
279 }
280 Self::MultiPolygon { coordinates } => {
281 for polygon in coordinates {
282 validate_polygon_positions(polygon, "multipolygon coordinates")?;
283 }
284 }
285 Self::GeometryCollection { geometries } => {
286 for geometry in geometries {
287 geometry.validate()?;
288 }
289 }
290 }
291 Ok(())
292 }
293}
294
295#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
296pub struct GeoFeature {
298 #[serde(skip_serializing_if = "Option::is_none")]
300 pub id: Option<String>,
301 #[serde(skip_serializing_if = "Option::is_none")]
303 pub bbox: Option<BBox>,
304 #[serde(skip_serializing_if = "Option::is_none")]
306 pub geometry: Option<Geometry>,
307 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
309 pub properties: Properties,
310}
311
312impl GeoFeature {
313 pub fn new(geometry: Option<Geometry>) -> Self {
315 Self {
316 id: None,
317 bbox: None,
318 geometry,
319 properties: Properties::new(),
320 }
321 }
322
323 pub fn with_id(mut self, id: impl Into<String>) -> Self {
325 self.id = Some(id.into());
326 self
327 }
328
329 pub fn with_bbox(mut self, bbox: BBox) -> Result<Self> {
331 bbox.validate()?;
332 self.bbox = Some(bbox);
333 Ok(self)
334 }
335
336 pub fn insert_property(&mut self, key: impl Into<String>, value: impl Into<Value>) {
338 self.properties.insert(key.into(), value.into());
339 }
340
341 pub fn validate(&self) -> Result<()> {
343 if self.id.as_ref().is_some_and(String::is_empty) {
344 return Err(invalid_argument("feature id must not be empty"));
345 }
346 if let Some(bbox) = self.bbox {
347 bbox.validate()?;
348 }
349 if let Some(geometry) = &self.geometry {
350 geometry.validate()?;
351 }
352 Ok(())
353 }
354}
355
356#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
357pub struct GeoFeatureCollection {
359 #[serde(skip_serializing_if = "Option::is_none")]
361 pub bbox: Option<BBox>,
362 pub features: Vec<GeoFeature>,
364}
365
366impl GeoFeatureCollection {
367 pub fn new(features: impl Into<Vec<GeoFeature>>) -> Self {
369 Self {
370 bbox: None,
371 features: features.into(),
372 }
373 }
374
375 pub fn push(&mut self, feature: GeoFeature) {
377 self.features.push(feature);
378 }
379
380 pub fn filter_intersecting_bbox(&self, bbox: BBox) -> Self {
382 Self {
383 bbox: self.bbox,
384 features: self
385 .features
386 .iter()
387 .filter(|feature| {
388 feature
389 .geometry
390 .as_ref()
391 .is_some_and(|geometry| bbox.intersects_geometry(geometry))
392 })
393 .cloned()
394 .collect(),
395 }
396 }
397
398 pub fn validate(&self) -> Result<()> {
400 if let Some(bbox) = self.bbox {
401 bbox.validate()?;
402 }
403 for feature in &self.features {
404 feature.validate()?;
405 }
406 Ok(())
407 }
408}
409
410pub fn point(coordinate: Coordinate) -> Geometry {
412 Geometry::Point {
413 coordinates: coordinate.as_position(),
414 }
415}
416
417pub fn line_string(coordinates: &[Coordinate]) -> Result<Geometry> {
419 let coordinates = coordinates_to_positions(coordinates)?;
420 validate_line_positions(&coordinates, "linestring coordinates")?;
421 Ok(Geometry::LineString { coordinates })
422}
423
424pub fn polygon_or_multipolygon(polygons: Vec<Vec<Vec<Coordinate>>>) -> Option<Geometry> {
426 let mut output: Vec<Vec<Vec<Position>>> = polygons
427 .into_iter()
428 .map(|polygon| {
429 polygon
430 .into_iter()
431 .map(|ring| ring.into_iter().map(Coordinate::as_position).collect())
432 .collect()
433 })
434 .collect();
435
436 match output.len() {
437 0 => None,
438 1 => Some(Geometry::Polygon {
439 coordinates: output.remove(0),
440 }),
441 _ => Some(Geometry::MultiPolygon {
442 coordinates: output,
443 }),
444 }
445}
446
447pub fn assemble_multipolygon(
449 outer_segments: Vec<Vec<Coordinate>>,
450 inner_segments: Vec<Vec<Coordinate>>,
451) -> Result<Geometry> {
452 let mut outer_rings = stitch_rings(outer_segments)
453 .ok_or_else(|| invalid_argument("outer ring segments could not be stitched"))?;
454 let mut inner_rings = stitch_rings(inner_segments)
455 .ok_or_else(|| invalid_argument("inner ring segments could not be stitched"))?;
456
457 if outer_rings.is_empty() {
458 return Err(invalid_argument(
459 "multipolygon requires at least one outer ring",
460 ));
461 }
462
463 for ring in &mut outer_rings {
464 normalize_ring_orientation(ring, true);
465 }
466 for ring in &mut inner_rings {
467 normalize_ring_orientation(ring, false);
468 }
469
470 let mut polygons: Vec<Vec<Vec<Coordinate>>> =
471 outer_rings.into_iter().map(|outer| vec![outer]).collect();
472
473 for inner in inner_rings {
474 let Some(point) = inner.first().copied() else {
475 return Err(invalid_argument("inner ring must not be empty"));
476 };
477 let Some((target_index, _)) = polygons
478 .iter()
479 .enumerate()
480 .filter_map(|(index, polygon)| {
481 let outer = &polygon[0];
482 if point_in_ring(point, outer) {
483 Some((index, ring_area(outer).abs()))
484 } else {
485 None
486 }
487 })
488 .min_by(|(_, left), (_, right)| left.total_cmp(right))
489 else {
490 return Err(invalid_argument(
491 "inner ring is not contained by an outer ring",
492 ));
493 };
494 polygons[target_index].push(inner);
495 }
496
497 polygon_or_multipolygon(polygons)
498 .ok_or_else(|| invalid_argument("multipolygon requires at least one polygon"))
499}
500
501pub fn stitch_rings(mut segments: Vec<Vec<Coordinate>>) -> Option<Vec<Vec<Coordinate>>> {
503 let mut rings = Vec::new();
504
505 while !segments.is_empty() {
506 let mut ring = segments.remove(0);
507 if ring.len() < 2 {
508 return None;
509 }
510
511 loop {
512 if is_valid_closed_ring(&ring) {
513 rings.push(ring);
514 break;
515 }
516
517 let (index, action) = find_connecting_segment(&ring, &segments)?;
518 let segment = segments.remove(index);
519 apply_segment(&mut ring, segment, action);
520 }
521 }
522
523 Some(rings)
524}
525
526pub fn is_valid_closed_ring(ring: &[Coordinate]) -> bool {
528 ring.len() >= 4 && ring.first() == ring.last()
529}
530
531pub fn ring_area(ring: &[Coordinate]) -> f64 {
533 if ring.len() < 4 {
534 return 0.0;
535 }
536 ring.windows(2)
537 .map(|window| {
538 let a = window[0];
539 let b = window[1];
540 (a.lon * b.lat) - (b.lon * a.lat)
541 })
542 .sum::<f64>()
543 / 2.0
544}
545
546pub fn normalize_ring_orientation(ring: &mut [Coordinate], counter_clockwise: bool) {
548 let is_counter_clockwise = ring_area(ring) > 0.0;
549 if is_counter_clockwise != counter_clockwise {
550 ring.reverse();
551 }
552}
553
554pub fn point_in_ring(point: Coordinate, ring: &[Coordinate]) -> bool {
556 if ring.len() < 4 {
557 return false;
558 }
559
560 let mut inside = false;
561 let mut previous = ring[ring.len() - 1];
562 for current in ring.iter().copied() {
563 let intersects = ((current.lat > point.lat) != (previous.lat > point.lat))
564 && (point.lon
565 < (previous.lon - current.lon) * (point.lat - current.lat)
566 / (previous.lat - current.lat)
567 + current.lon);
568 if intersects {
569 inside = !inside;
570 }
571 previous = current;
572 }
573 inside
574}
575
576pub fn geometry_intersects_bbox(geometry: &Geometry, bbox: BBox) -> bool {
578 match geometry {
579 Geometry::Point { coordinates } => Coordinate::from_position(*coordinates)
580 .map(|coordinate| bbox.contains(coordinate))
581 .unwrap_or(false),
582 Geometry::MultiPoint { coordinates } => point_positions_intersect_bbox(coordinates, bbox),
583 Geometry::LineString { coordinates } => line_positions_intersect_bbox(coordinates, bbox),
584 Geometry::MultiLineString { coordinates } => coordinates
585 .iter()
586 .any(|line| line_positions_intersect_bbox(line, bbox)),
587 Geometry::Polygon { coordinates } => polygon_positions_intersect_bbox(coordinates, bbox),
588 Geometry::MultiPolygon { coordinates } => coordinates
589 .iter()
590 .any(|polygon| polygon_positions_intersect_bbox(polygon, bbox)),
591 Geometry::GeometryCollection { geometries } => geometries
592 .iter()
593 .any(|geometry| geometry_intersects_bbox(geometry, bbox)),
594 }
595}
596
597fn line_positions_intersect_bbox(coordinates: &[Position], bbox: BBox) -> bool {
598 if positions_intersect_bbox(coordinates, bbox) {
599 return true;
600 }
601 positions_to_coordinates(coordinates)
602 .map(|coordinates| line_segments_intersect_bbox(&coordinates, bbox))
603 .unwrap_or(false)
604}
605
606fn polygon_positions_intersect_bbox(polygon: &[Vec<Position>], bbox: BBox) -> bool {
607 let rings = polygon
608 .iter()
609 .map(|ring| positions_to_coordinates(ring))
610 .collect::<Result<Vec<_>>>();
611 let Ok(rings) = rings else {
612 return false;
613 };
614 if rings.iter().any(|ring| {
615 ring.iter()
616 .copied()
617 .any(|coordinate| bbox.contains(coordinate))
618 || line_segments_intersect_bbox(ring, bbox)
619 }) {
620 return true;
621 }
622 let Some(exterior) = rings.first() else {
623 return false;
624 };
625 bbox_corners(bbox).iter().copied().any(|corner| {
626 point_in_ring(corner, exterior)
627 && !rings
628 .iter()
629 .skip(1)
630 .any(|interior| point_in_ring(corner, interior))
631 })
632}
633
634fn line_segments_intersect_bbox(coordinates: &[Coordinate], bbox: BBox) -> bool {
635 coordinates
636 .windows(2)
637 .any(|segment| segment_intersects_bbox(segment[0], segment[1], bbox))
638}
639
640fn segment_intersects_bbox(start: Coordinate, end: Coordinate, bbox: BBox) -> bool {
641 if bbox.contains(start) || bbox.contains(end) {
642 return true;
643 }
644 if start.lon.max(end.lon) < bbox.min_lon
645 || start.lon.min(end.lon) > bbox.max_lon
646 || start.lat.max(end.lat) < bbox.min_lat
647 || start.lat.min(end.lat) > bbox.max_lat
648 {
649 return false;
650 }
651 let corners = bbox_corners(bbox);
652 let edges = [
653 (corners[0], corners[1]),
654 (corners[1], corners[2]),
655 (corners[2], corners[3]),
656 (corners[3], corners[0]),
657 ];
658 edges
659 .iter()
660 .any(|(edge_start, edge_end)| segments_intersect(start, end, *edge_start, *edge_end))
661}
662
663fn bbox_corners(bbox: BBox) -> [Coordinate; 4] {
664 [
665 Coordinate {
666 lon: bbox.min_lon,
667 lat: bbox.min_lat,
668 },
669 Coordinate {
670 lon: bbox.max_lon,
671 lat: bbox.min_lat,
672 },
673 Coordinate {
674 lon: bbox.max_lon,
675 lat: bbox.max_lat,
676 },
677 Coordinate {
678 lon: bbox.min_lon,
679 lat: bbox.max_lat,
680 },
681 ]
682}
683
684fn segments_intersect(
685 first_start: Coordinate,
686 first_end: Coordinate,
687 second_start: Coordinate,
688 second_end: Coordinate,
689) -> bool {
690 let o1 = orientation_sign(first_start, first_end, second_start);
691 let o2 = orientation_sign(first_start, first_end, second_end);
692 let o3 = orientation_sign(second_start, second_end, first_start);
693 let o4 = orientation_sign(second_start, second_end, first_end);
694
695 if o1 == 0 && coordinate_on_segment(second_start, first_start, first_end) {
696 return true;
697 }
698 if o2 == 0 && coordinate_on_segment(second_end, first_start, first_end) {
699 return true;
700 }
701 if o3 == 0 && coordinate_on_segment(first_start, second_start, second_end) {
702 return true;
703 }
704 if o4 == 0 && coordinate_on_segment(first_end, second_start, second_end) {
705 return true;
706 }
707
708 o1 != o2 && o3 != o4
709}
710
711fn orientation_sign(a: Coordinate, b: Coordinate, c: Coordinate) -> i8 {
712 let orientation = (b.lon - a.lon) * (c.lat - a.lat) - (b.lat - a.lat) * (c.lon - a.lon);
713 if orientation.abs() <= GEOMETRY_EPSILON {
714 0
715 } else if orientation > 0.0 {
716 1
717 } else {
718 -1
719 }
720}
721
722fn coordinate_on_segment(point: Coordinate, start: Coordinate, end: Coordinate) -> bool {
723 orientation_sign(start, end, point) == 0
724 && point.lon >= start.lon.min(end.lon) - GEOMETRY_EPSILON
725 && point.lon <= start.lon.max(end.lon) + GEOMETRY_EPSILON
726 && point.lat >= start.lat.min(end.lat) - GEOMETRY_EPSILON
727 && point.lat <= start.lat.max(end.lat) + GEOMETRY_EPSILON
728}
729
730pub fn map_geometry_coordinates<F>(geometry: &Geometry, mut transform: F) -> Result<Geometry>
732where
733 F: FnMut(Coordinate) -> Result<Coordinate>,
734{
735 map_geometry_coordinates_inner(geometry, &mut transform)
736}
737
738pub fn map_feature_coordinates<F>(feature: &GeoFeature, transform: F) -> Result<GeoFeature>
740where
741 F: FnMut(Coordinate) -> Result<Coordinate>,
742{
743 let geometry = match &feature.geometry {
744 Some(geometry) => Some(map_geometry_coordinates(geometry, transform)?),
745 None => None,
746 };
747
748 Ok(GeoFeature {
749 id: feature.id.clone(),
750 bbox: feature.bbox,
751 geometry,
752 properties: feature.properties.clone(),
753 })
754}
755
756pub fn translate_geometry(geometry: &Geometry, delta_lon: f64, delta_lat: f64) -> Result<Geometry> {
758 if !delta_lon.is_finite() || !delta_lat.is_finite() {
759 return Err(invalid_argument("translation delta values must be finite"));
760 }
761 map_geometry_coordinates(geometry, |coordinate| {
762 Coordinate::new(coordinate.lon + delta_lon, coordinate.lat + delta_lat)
763 })
764}
765
766pub fn simplify_geometry(geometry: &Geometry, tolerance: f64) -> Result<Geometry> {
768 validate_tolerance(tolerance)?;
769 match geometry {
770 Geometry::Point { .. } | Geometry::MultiPoint { .. } => Ok(geometry.clone()),
771 Geometry::LineString { coordinates } => Ok(Geometry::LineString {
772 coordinates: coordinates_to_positions(&simplify_line(
773 &positions_to_coordinates(coordinates)?,
774 tolerance,
775 )?)?,
776 }),
777 Geometry::MultiLineString { coordinates } => Ok(Geometry::MultiLineString {
778 coordinates: coordinates
779 .iter()
780 .map(|line| {
781 let simplified = simplify_line(&positions_to_coordinates(line)?, tolerance)?;
782 coordinates_to_positions(&simplified)
783 })
784 .collect::<Result<Vec<_>>>()?,
785 }),
786 Geometry::Polygon { coordinates } => Ok(Geometry::Polygon {
787 coordinates: simplify_polygon_positions(coordinates, tolerance)?,
788 }),
789 Geometry::MultiPolygon { coordinates } => Ok(Geometry::MultiPolygon {
790 coordinates: coordinates
791 .iter()
792 .map(|polygon| simplify_polygon_positions(polygon, tolerance))
793 .collect::<Result<Vec<_>>>()?,
794 }),
795 Geometry::GeometryCollection { geometries } => Ok(Geometry::GeometryCollection {
796 geometries: geometries
797 .iter()
798 .map(|geometry| simplify_geometry(geometry, tolerance))
799 .collect::<Result<Vec<_>>>()?,
800 }),
801 }
802}
803
804pub fn simplify_line(coordinates: &[Coordinate], tolerance: f64) -> Result<Vec<Coordinate>> {
806 validate_tolerance(tolerance)?;
807 validate_coordinate_slice(coordinates, 2, "line coordinates")?;
808
809 if coordinates.len() <= 2 || tolerance == 0.0 {
810 return Ok(coordinates.to_vec());
811 }
812
813 let mut keep = vec![false; coordinates.len()];
814 keep[0] = true;
815 keep[coordinates.len() - 1] = true;
816 simplify_line_range(coordinates, 0, coordinates.len() - 1, tolerance, &mut keep);
817
818 Ok(coordinates
819 .iter()
820 .copied()
821 .zip(keep)
822 .filter_map(|(coordinate, keep)| keep.then_some(coordinate))
823 .collect())
824}
825
826pub fn simplify_ring(ring: &[Coordinate], tolerance: f64) -> Result<Vec<Coordinate>> {
828 validate_tolerance(tolerance)?;
829 validate_ring(ring, "ring coordinates")?;
830
831 if ring.len() <= 5 || tolerance == 0.0 {
832 return Ok(ring.to_vec());
833 }
834
835 let mut open_ring = ring[..ring.len() - 1].to_vec();
836 let first = open_ring[0];
837 open_ring.push(first);
838 let mut simplified = simplify_line(&open_ring, tolerance)?;
839 simplified.pop();
840
841 if simplified.len() < 3 {
842 return Ok(ring.to_vec());
843 }
844
845 if simplified.last().copied() != Some(first) {
846 simplified.push(first);
847 }
848 Ok(simplified)
849}
850
851fn validate_positions(coordinates: &[Position], label: &str) -> Result<()> {
852 for coordinate in coordinates {
853 Coordinate::from_position(*coordinate)
854 .map_err(|_| invalid_argument(format!("{label} must be finite")))?;
855 }
856 Ok(())
857}
858
859fn validate_line_positions(coordinates: &[Position], label: &str) -> Result<()> {
860 if coordinates.len() < 2 {
861 return Err(invalid_argument(format!(
862 "{label} must contain at least two positions"
863 )));
864 }
865 validate_positions(coordinates, label)
866}
867
868fn validate_polygon_positions(coordinates: &[Vec<Position>], label: &str) -> Result<()> {
869 if coordinates.is_empty() {
870 return Err(invalid_argument(format!(
871 "{label} must contain at least one ring"
872 )));
873 }
874 for ring in coordinates {
875 let ring = positions_to_coordinates(ring)?;
876 validate_ring(&ring, label)?;
877 }
878 Ok(())
879}
880
881fn validate_coordinate_slice(
882 coordinates: &[Coordinate],
883 minimum: usize,
884 label: &str,
885) -> Result<()> {
886 if coordinates.len() < minimum {
887 return Err(invalid_argument(format!(
888 "{label} must contain at least {minimum} positions"
889 )));
890 }
891 for coordinate in coordinates {
892 coordinate.validate()?;
893 }
894 Ok(())
895}
896
897fn validate_ring(ring: &[Coordinate], label: &str) -> Result<()> {
898 validate_coordinate_slice(ring, 4, label)?;
899 if ring.first() != ring.last() {
900 return Err(invalid_argument(format!("{label} must be closed")));
901 }
902 Ok(())
903}
904
905fn validate_tolerance(tolerance: f64) -> Result<()> {
906 if tolerance < 0.0 || !tolerance.is_finite() {
907 return Err(invalid_argument(
908 "simplification tolerance must be finite and non-negative",
909 ));
910 }
911 Ok(())
912}
913
914fn positions_to_coordinates(positions: &[Position]) -> Result<Vec<Coordinate>> {
915 positions
916 .iter()
917 .copied()
918 .map(Coordinate::from_position)
919 .collect()
920}
921
922fn coordinates_to_positions(coordinates: &[Coordinate]) -> Result<Vec<Position>> {
923 coordinates
924 .iter()
925 .map(|coordinate| {
926 coordinate.validate()?;
927 Ok(coordinate.as_position())
928 })
929 .collect()
930}
931
932fn point_positions_intersect_bbox(coordinates: &[Position], bbox: BBox) -> bool {
933 coordinates
934 .iter()
935 .copied()
936 .filter_map(|position| Coordinate::from_position(position).ok())
937 .any(|coordinate| bbox.contains(coordinate))
938}
939
940fn positions_intersect_bbox(coordinates: &[Position], bbox: BBox) -> bool {
941 let coordinates = coordinates
942 .iter()
943 .copied()
944 .filter_map(|position| Coordinate::from_position(position).ok())
945 .collect::<Vec<_>>();
946 coordinates
947 .iter()
948 .copied()
949 .any(|coordinate| bbox.contains(coordinate))
950 || line_segments_intersect_bbox(&coordinates, bbox)
951}
952
953fn map_geometry_coordinates_inner(
954 geometry: &Geometry,
955 transform: &mut dyn FnMut(Coordinate) -> Result<Coordinate>,
956) -> Result<Geometry> {
957 match geometry {
958 Geometry::Point { coordinates } => Ok(Geometry::Point {
959 coordinates: transform(Coordinate::from_position(*coordinates)?)?.as_position(),
960 }),
961 Geometry::MultiPoint { coordinates } => Ok(Geometry::MultiPoint {
962 coordinates: map_positions(coordinates, transform)?,
963 }),
964 Geometry::LineString { coordinates } => Ok(Geometry::LineString {
965 coordinates: map_positions(coordinates, transform)?,
966 }),
967 Geometry::MultiLineString { coordinates } => Ok(Geometry::MultiLineString {
968 coordinates: coordinates
969 .iter()
970 .map(|line| map_positions(line, transform))
971 .collect::<Result<Vec<_>>>()?,
972 }),
973 Geometry::Polygon { coordinates } => Ok(Geometry::Polygon {
974 coordinates: coordinates
975 .iter()
976 .map(|ring| map_positions(ring, transform))
977 .collect::<Result<Vec<_>>>()?,
978 }),
979 Geometry::MultiPolygon { coordinates } => Ok(Geometry::MultiPolygon {
980 coordinates: coordinates
981 .iter()
982 .map(|polygon| {
983 polygon
984 .iter()
985 .map(|ring| map_positions(ring, transform))
986 .collect::<Result<Vec<_>>>()
987 })
988 .collect::<Result<Vec<_>>>()?,
989 }),
990 Geometry::GeometryCollection { geometries } => Ok(Geometry::GeometryCollection {
991 geometries: geometries
992 .iter()
993 .map(|geometry| map_geometry_coordinates_inner(geometry, transform))
994 .collect::<Result<Vec<_>>>()?,
995 }),
996 }
997}
998
999fn map_positions(
1000 positions: &[Position],
1001 transform: &mut dyn FnMut(Coordinate) -> Result<Coordinate>,
1002) -> Result<Vec<Position>> {
1003 positions
1004 .iter()
1005 .copied()
1006 .map(|position| {
1007 transform(Coordinate::from_position(position)?).map(Coordinate::as_position)
1008 })
1009 .collect()
1010}
1011
1012fn simplify_polygon_positions(
1013 polygon: &[Vec<Position>],
1014 tolerance: f64,
1015) -> Result<Vec<Vec<Position>>> {
1016 polygon
1017 .iter()
1018 .map(|ring| {
1019 let simplified = simplify_ring(&positions_to_coordinates(ring)?, tolerance)?;
1020 coordinates_to_positions(&simplified)
1021 })
1022 .collect()
1023}
1024
1025fn simplify_line_range(
1026 coordinates: &[Coordinate],
1027 start: usize,
1028 end: usize,
1029 tolerance: f64,
1030 keep: &mut [bool],
1031) {
1032 if end <= start + 1 {
1033 return;
1034 }
1035
1036 let mut farthest_index = start + 1;
1037 let mut farthest_distance = 0.0;
1038 for index in start + 1..end {
1039 let distance =
1040 perpendicular_distance(coordinates[index], coordinates[start], coordinates[end]);
1041 if distance > farthest_distance {
1042 farthest_distance = distance;
1043 farthest_index = index;
1044 }
1045 }
1046
1047 if farthest_distance > tolerance {
1048 keep[farthest_index] = true;
1049 simplify_line_range(coordinates, start, farthest_index, tolerance, keep);
1050 simplify_line_range(coordinates, farthest_index, end, tolerance, keep);
1051 }
1052}
1053
1054fn perpendicular_distance(point: Coordinate, start: Coordinate, end: Coordinate) -> f64 {
1055 let dx = end.lon - start.lon;
1056 let dy = end.lat - start.lat;
1057 if dx == 0.0 && dy == 0.0 {
1058 return (point.lon - start.lon).hypot(point.lat - start.lat);
1059 }
1060 ((dy * point.lon - dx * point.lat + end.lon * start.lat - end.lat * start.lon).abs())
1061 / dx.hypot(dy)
1062}
1063
1064#[derive(Debug, Clone, Copy)]
1065enum StitchAction {
1066 AppendForward,
1067 AppendReverse,
1068 PrependForward,
1069 PrependReverse,
1070}
1071
1072fn find_connecting_segment(
1073 ring: &[Coordinate],
1074 segments: &[Vec<Coordinate>],
1075) -> Option<(usize, StitchAction)> {
1076 let first = *ring.first()?;
1077 let last = *ring.last()?;
1078 segments.iter().enumerate().find_map(|(index, segment)| {
1079 let segment_first = *segment.first()?;
1080 let segment_last = *segment.last()?;
1081 if last == segment_first {
1082 Some((index, StitchAction::AppendForward))
1083 } else if last == segment_last {
1084 Some((index, StitchAction::AppendReverse))
1085 } else if first == segment_last {
1086 Some((index, StitchAction::PrependForward))
1087 } else if first == segment_first {
1088 Some((index, StitchAction::PrependReverse))
1089 } else {
1090 None
1091 }
1092 })
1093}
1094
1095fn apply_segment(ring: &mut Vec<Coordinate>, mut segment: Vec<Coordinate>, action: StitchAction) {
1096 match action {
1097 StitchAction::AppendForward => ring.extend(segment.into_iter().skip(1)),
1098 StitchAction::AppendReverse => {
1099 segment.reverse();
1100 ring.extend(segment.into_iter().skip(1));
1101 }
1102 StitchAction::PrependForward => {
1103 segment.pop();
1104 segment.extend(ring.iter().copied());
1105 *ring = segment;
1106 }
1107 StitchAction::PrependReverse => {
1108 segment.reverse();
1109 segment.pop();
1110 segment.extend(ring.iter().copied());
1111 *ring = segment;
1112 }
1113 }
1114}
1115
1116#[cfg(test)]
1117mod tests {
1118 use super::*;
1119
1120 fn coord(lon: f64, lat: f64) -> Coordinate {
1121 Coordinate::new(lon, lat).unwrap()
1122 }
1123
1124 #[test]
1125 fn bbox_contains_coordinate() {
1126 let bbox = BBox::new([8.5, 48.8, 9.3, 49.2]).unwrap();
1127
1128 assert!(bbox.contains(coord(8.7, 48.9)));
1129 assert!(!bbox.contains(coord(10.0, 48.9)));
1130 }
1131
1132 #[test]
1133 fn bbox_intersects_crossing_line_segments() {
1134 let bbox = BBox::new([0.0, 0.0, 1.0, 1.0]).unwrap();
1135 let geometry = Geometry::LineString {
1136 coordinates: vec![[-1.0, 0.5], [2.0, 0.5]],
1137 };
1138
1139 assert!(bbox.intersects_geometry(&geometry));
1140 }
1141
1142 #[test]
1143 fn segment_intersection_handles_crossing_touching_overlap_and_separated() {
1144 assert!(segments_intersect(
1145 coord(0.0, 0.0),
1146 coord(2.0, 2.0),
1147 coord(0.0, 2.0),
1148 coord(2.0, 0.0),
1149 ));
1150 assert!(segments_intersect(
1151 coord(0.0, 0.0),
1152 coord(1.0, 1.0),
1153 coord(1.0, 1.0),
1154 coord(2.0, 0.0),
1155 ));
1156 assert!(segments_intersect(
1157 coord(0.0, 0.0),
1158 coord(2.0, 0.0),
1159 coord(1.0, 0.0),
1160 coord(3.0, 0.0),
1161 ));
1162 assert!(!segments_intersect(
1163 coord(0.0, 0.0),
1164 coord(1.0, 0.0),
1165 coord(2.0, 0.0),
1166 coord(3.0, 0.0),
1167 ));
1168 }
1169
1170 #[test]
1171 fn coordinate_on_segment_handles_endpoint_interior_and_off_segment() {
1172 let start = coord(0.0, 0.0);
1173 let end = coord(2.0, 2.0);
1174
1175 assert!(coordinate_on_segment(start, start, end));
1176 assert!(coordinate_on_segment(coord(1.0, 1.0), start, end));
1177 assert!(!coordinate_on_segment(coord(1.0, 1.1), start, end));
1178 assert!(!coordinate_on_segment(coord(3.0, 3.0), start, end));
1179 }
1180
1181 #[test]
1182 fn positions_intersect_bbox_detects_crossing_without_inside_vertices() {
1183 let bbox = BBox::new([0.0, 0.0, 1.0, 1.0]).unwrap();
1184
1185 assert!(positions_intersect_bbox(&[[-1.0, 0.5], [2.0, 0.5]], bbox));
1186 }
1187
1188 #[test]
1189 fn bbox_intersects_polygon_that_contains_viewport() {
1190 let bbox = BBox::new([0.0, 0.0, 1.0, 1.0]).unwrap();
1191 let geometry = Geometry::Polygon {
1192 coordinates: vec![vec![
1193 [-1.0, -1.0],
1194 [2.0, -1.0],
1195 [2.0, 2.0],
1196 [-1.0, 2.0],
1197 [-1.0, -1.0],
1198 ]],
1199 };
1200
1201 assert!(bbox.intersects_geometry(&geometry));
1202 }
1203
1204 #[test]
1205 fn bbox_inside_polygon_hole_does_not_intersect() {
1206 let bbox = BBox::new([0.25, 0.25, 0.75, 0.75]).unwrap();
1207 let geometry = Geometry::Polygon {
1208 coordinates: vec![
1209 vec![
1210 [-1.0, -1.0],
1211 [2.0, -1.0],
1212 [2.0, 2.0],
1213 [-1.0, 2.0],
1214 [-1.0, -1.0],
1215 ],
1216 vec![[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0], [0.0, 0.0]],
1217 ],
1218 };
1219
1220 assert!(!bbox.intersects_geometry(&geometry));
1221 }
1222
1223 #[test]
1224 fn normalizes_ring_orientation() {
1225 let mut ring = vec![
1226 coord(0.0, 0.0),
1227 coord(0.0, 1.0),
1228 coord(1.0, 1.0),
1229 coord(1.0, 0.0),
1230 coord(0.0, 0.0),
1231 ];
1232
1233 normalize_ring_orientation(&mut ring, true);
1234 assert!(ring_area(&ring) > 0.0);
1235 normalize_ring_orientation(&mut ring, false);
1236 assert!(ring_area(&ring) < 0.0);
1237 }
1238
1239 #[test]
1240 fn point_in_ring_detects_inside_and_outside() {
1241 let ring = vec![
1242 coord(0.0, 0.0),
1243 coord(1.0, 0.0),
1244 coord(1.0, 1.0),
1245 coord(0.0, 1.0),
1246 coord(0.0, 0.0),
1247 ];
1248
1249 assert!(point_in_ring(coord(0.5, 0.5), &ring));
1250 assert!(!point_in_ring(coord(2.0, 0.5), &ring));
1251 }
1252
1253 #[test]
1254 fn stitches_reversed_segments_into_ring() {
1255 let segments = vec![
1256 vec![coord(0.0, 0.0), coord(1.0, 0.0), coord(1.0, 1.0)],
1257 vec![coord(0.0, 0.0), coord(0.0, 1.0), coord(1.0, 1.0)],
1258 ];
1259
1260 let rings = stitch_rings(segments).unwrap();
1261
1262 assert_eq!(rings.len(), 1);
1263 assert!(is_valid_closed_ring(&rings[0]));
1264 }
1265
1266 #[test]
1267 fn assemble_multipolygon_assigns_inner_ring() {
1268 let outer = vec![
1269 coord(0.0, 0.0),
1270 coord(4.0, 0.0),
1271 coord(4.0, 4.0),
1272 coord(0.0, 4.0),
1273 coord(0.0, 0.0),
1274 ];
1275 let inner = vec![
1276 coord(1.0, 1.0),
1277 coord(2.0, 1.0),
1278 coord(2.0, 2.0),
1279 coord(1.0, 2.0),
1280 coord(1.0, 1.0),
1281 ];
1282
1283 let geometry = assemble_multipolygon(vec![outer], vec![inner]).unwrap();
1284
1285 let Geometry::Polygon { coordinates } = geometry else {
1286 panic!("expected polygon");
1287 };
1288 assert_eq!(coordinates.len(), 2);
1289 }
1290
1291 #[test]
1292 fn maps_geometry_coordinates() {
1293 let geometry = point(coord(1.0, 2.0));
1294
1295 let translated = translate_geometry(&geometry, 3.0, 4.0).unwrap();
1296
1297 assert_eq!(
1298 translated,
1299 Geometry::Point {
1300 coordinates: [4.0, 6.0]
1301 }
1302 );
1303 }
1304
1305 #[test]
1306 fn simplifies_line_coordinates() {
1307 let line = vec![
1308 coord(0.0, 0.0),
1309 coord(1.0, 0.01),
1310 coord(2.0, -0.01),
1311 coord(3.0, 0.0),
1312 ];
1313
1314 let simplified = simplify_line(&line, 0.1).unwrap();
1315
1316 assert_eq!(simplified, vec![coord(0.0, 0.0), coord(3.0, 0.0)]);
1317 }
1318
1319 #[test]
1320 fn simplify_ring_preserves_valid_closure() {
1321 let ring = vec![
1322 coord(0.0, 0.0),
1323 coord(1.0, 0.01),
1324 coord(2.0, 0.0),
1325 coord(2.0, 2.0),
1326 coord(0.0, 2.0),
1327 coord(0.0, 0.0),
1328 ];
1329
1330 let simplified = simplify_ring(&ring, 0.1).unwrap();
1331
1332 assert!(is_valid_closed_ring(&simplified));
1333 assert_eq!(simplified.first(), simplified.last());
1334 }
1335
1336 #[test]
1337 fn feature_properties_are_generic_json_values() {
1338 let mut feature = GeoFeature::new(Some(point(coord(8.7, 48.9)))).with_id("node/123");
1339 feature.insert_property("name", "Test");
1340
1341 assert_eq!(feature.id.as_deref(), Some("node/123"));
1342 assert_eq!(feature.properties["name"], Value::from("Test"));
1343 }
1344}