1#![warn(missing_docs)]
4#![allow(dead_code)]
5
6use crate::{MyError, config::config, crs::CRS, text::cql2::wkt};
10use core::fmt;
11use geos::{CoordSeq, Geometry};
12use tracing::error;
13
14fn ensure_precision(x: &f64) -> f64 {
17 let d = 10.0_f64.powi(
18 config()
19 .default_precision()
20 .try_into()
21 .expect("Failed coercing DEFAULT_PRECISION"),
22 );
23 (x * d).round() / d
24}
25
26#[derive(Debug, Clone, PartialEq, PartialOrd)]
28pub enum G {
29 Point(Point),
31 Line(Line),
33 Polygon(Polygon),
35 Points(Points),
37 Lines(Lines),
39 Polygons(Polygons),
41 Vec(Geometries),
43 BBox(BBox),
45}
46
47pub trait GTrait {
49 fn is_2d(&self) -> bool;
51
52 fn to_wkt(&self) -> String {
59 self.to_wkt_fmt(config().default_precision())
60 }
61
62 fn to_wkt_fmt(&self, precision: usize) -> String;
81
82 fn check_coordinates(&self, crs: &CRS) -> Result<(), MyError>;
85
86 fn to_geos(&self) -> Result<Geometry, MyError>;
88}
89
90impl GTrait for G {
91 fn is_2d(&self) -> bool {
92 match self {
93 G::Point(x) => x.is_2d(),
94 G::Line(x) => x.is_2d(),
95 G::Polygon(x) => x.is_2d(),
96 G::Points(x) => x.is_2d(),
97 G::Lines(x) => x.is_2d(),
98 G::Polygons(x) => x.is_2d(),
99 G::Vec(x) => x.is_2d(),
100 G::BBox(x) => x.is_2d(),
101 }
102 }
103
104 fn to_wkt_fmt(&self, precision: usize) -> String {
105 match self {
106 G::Point(x) => x.to_wkt_fmt(precision),
107 G::Line(x) => x.to_wkt_fmt(precision),
108 G::Polygon(x) => x.to_wkt_fmt(precision),
109 G::Points(x) => x.to_wkt_fmt(precision),
110 G::Lines(x) => x.to_wkt_fmt(precision),
111 G::Polygons(x) => x.to_wkt_fmt(precision),
112 G::Vec(x) => x.to_wkt_fmt(precision),
113 G::BBox(x) => x.to_wkt_fmt(precision),
114 }
115 }
116
117 fn check_coordinates(&self, crs: &CRS) -> Result<(), MyError> {
118 match self {
119 G::Point(x) => x.check_coordinates(crs),
120 G::Line(x) => x.check_coordinates(crs),
121 G::Polygon(x) => x.check_coordinates(crs),
122 G::Points(x) => x.check_coordinates(crs),
123 G::Lines(x) => x.check_coordinates(crs),
124 G::Polygons(x) => x.check_coordinates(crs),
125 G::Vec(x) => x.check_coordinates(crs),
126 G::BBox(x) => x.check_coordinates(crs),
127 }
128 }
129
130 fn to_geos(&self) -> Result<Geometry, MyError> {
131 match self {
132 G::Point(x) => x.to_geos(),
133 G::Line(x) => x.to_geos(),
134 G::Polygon(x) => x.to_geos(),
135 G::Points(x) => x.to_geos(),
136 G::Lines(x) => x.to_geos(),
137 G::Polygons(x) => x.to_geos(),
138 G::Vec(x) => x.to_geos(),
139 G::BBox(x) => x.to_geos(),
140 }
141 }
142}
143
144impl G {
145 pub fn try_from_wkt(s: &str) -> Result<Self, MyError> {
148 let g = wkt(s).map_err(|x| MyError::Runtime(format!("Not WKT: {x}").into()))?;
149 Ok(g)
150 }
151}
152
153impl fmt::Display for G {
154 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
155 match self {
156 G::Point(x) => write!(f, "{x}"),
157 G::Line(x) => write!(f, "{x}"),
158 G::Polygon(x) => write!(f, "{x}"),
159 G::Points(x) => write!(f, "{x}"),
160 G::Lines(x) => write!(f, "{x}"),
161 G::Polygons(x) => write!(f, "{x}"),
162 G::Vec(x) => write!(f, "{x}"),
163 G::BBox(x) => write!(f, "{x}"),
164 }
165 }
166}
167
168#[derive(Debug, Clone, PartialEq, PartialOrd)]
172pub struct Point {
173 coord: Vec<f64>,
174}
175
176impl GTrait for Point {
177 fn is_2d(&self) -> bool {
178 self.coord.len() == 2
179 }
180
181 fn to_wkt_fmt(&self, precision: usize) -> String {
182 if self.is_2d() {
183 format!("POINT ({})", Self::coords_with_dp(self.as_2d(), precision))
184 } else {
185 format!(
186 "POINT Z ({})",
187 Self::coords_with_dp(self.as_3d(), precision)
188 )
189 }
190 }
191
192 fn check_coordinates(&self, crs: &CRS) -> Result<(), MyError> {
193 crs.check_point(&self.coord)
194 }
195
196 fn to_geos(&self) -> Result<Geometry, MyError> {
197 Self::to_geos_impl(&self.coord)
198 }
199}
200
201impl Point {
202 pub(crate) fn from(coord: Vec<f64>) -> Self {
203 let coord = Self::ensure_precision_impl(&coord);
207 Point { coord }
208 }
209
210 fn to_geos_impl(xy: &[f64]) -> Result<Geometry, MyError> {
211 let xy = CoordSeq::new_from_vec(&[xy]).map_err(MyError::Geos)?;
212 Geometry::create_point(xy).map_err(MyError::Geos)
213 }
214
215 pub(crate) fn coords_as_txt(coord: &[f64]) -> String {
217 Self::coords_with_dp(coord, config().default_precision())
218 }
219
220 fn coords_with_dp(coord: &[f64], precision: usize) -> String {
221 if coord.len() == 2 {
222 format!("{:.2$} {:.2$}", coord[0], coord[1], precision)
223 } else {
224 format!(
225 "{:.3$} {:.3$} {:.3$}",
226 coord[0], coord[1], coord[2], precision
227 )
228 }
229 }
230
231 fn as_2d(&self) -> &[f64; 2] {
233 self.coord
234 .as_slice()
235 .try_into()
236 .expect("Failed coercing Point to 2D")
237 }
238
239 fn as_3d(&self) -> &[f64; 3] {
241 self.coord
242 .as_slice()
243 .try_into()
244 .expect("Failed coercing Point to 3D")
245 }
246
247 fn ensure_precision_impl(coord: &[f64]) -> Vec<f64> {
248 coord.iter().map(ensure_precision).collect()
249 }
250
251 #[cfg(test)]
253 fn x(&self) -> f64 {
254 self.coord[0]
255 }
256
257 #[cfg(test)]
259 fn y(&self) -> f64 {
260 self.coord[1]
261 }
262
263 #[cfg(test)]
265 fn z(&self) -> Option<f64> {
266 if self.coord.len() == 2 {
267 None
268 } else {
269 Some(self.coord[2])
270 }
271 }
272}
273
274impl fmt::Display for Point {
275 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> fmt::Result {
276 write!(f, "Pt (...)")
277 }
278}
279
280#[derive(Debug, Clone, PartialEq, PartialOrd)]
284pub struct Line {
285 coord: Vec<Vec<f64>>,
286}
287
288impl GTrait for Line {
289 fn is_2d(&self) -> bool {
290 self.coord[0].len() == 2
291 }
292
293 fn to_wkt_fmt(&self, precision: usize) -> String {
294 if self.is_2d() {
295 format!(
296 "LINESTRING {}",
297 Self::coords_with_dp(&self.coord, precision)
298 )
299 } else {
300 format!(
301 "LINESTRING Z {}",
302 Self::coords_with_dp(&self.coord, precision)
303 )
304 }
305 }
306
307 fn check_coordinates(&self, crs: &CRS) -> Result<(), MyError> {
308 crs.check_line(&self.coord)
309 }
310
311 fn to_geos(&self) -> Result<Geometry, MyError> {
312 Self::to_geos_impl(&self.coord)
313 }
314}
315
316impl Line {
317 pub(crate) fn from(coord: Vec<Vec<f64>>) -> Self {
318 let coord = Self::ensure_precision_impl(&coord);
319 Line { coord }
320 }
321
322 fn to_geos_impl(xy: &[Vec<f64>]) -> Result<Geometry, MyError> {
323 let vertices: Vec<&[f64]> = xy.iter().map(|x| x.as_slice()).collect();
324 let xy = CoordSeq::new_from_vec(&vertices).map_err(MyError::Geos)?;
325 Geometry::create_line_string(xy).map_err(MyError::Geos)
326 }
327
328 fn ensure_precision_impl(coord: &[Vec<f64>]) -> Vec<Vec<f64>> {
329 coord
330 .iter()
331 .map(|x| Point::ensure_precision_impl(x))
332 .collect()
333 }
334
335 pub(crate) fn coords_as_txt(coord: &[Vec<f64>]) -> String {
336 Self::coords_with_dp(coord, config().default_precision())
337 }
338
339 fn coords_with_dp(coord: &[Vec<f64>], precision: usize) -> String {
340 let points: Vec<String> = coord
341 .iter()
342 .map(|x| Point::coords_with_dp(x, precision))
343 .collect();
344 format!("({})", points.join(", "))
345 }
346
347 fn size(&self) -> usize {
349 self.coord.len()
350 }
351
352 fn is_closed(&self) -> bool {
355 self.coord.first() == self.coord.last()
356 }
357
358 fn is_ring(&self) -> bool {
361 self.size() > 3 && self.is_closed()
362 }
363
364 fn first(&self) -> Option<&Vec<f64>> {
365 self.coord.first()
366 }
367
368 fn last(&self) -> Option<&Vec<f64>> {
369 self.coord.last()
370 }
371}
372
373impl fmt::Display for Line {
374 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> fmt::Result {
375 write!(f, "LINE (...)")
376 }
377}
378
379#[derive(Debug, Clone, PartialEq, PartialOrd)]
383pub struct Polygon {
384 rings: Vec<Vec<Vec<f64>>>,
385}
386
387impl GTrait for Polygon {
388 fn is_2d(&self) -> bool {
389 self.rings[0][0].len() == 2
390 }
391
392 fn to_wkt_fmt(&self, precision: usize) -> String {
393 if self.is_2d() {
394 format!("POLYGON {}", Self::coords_with_dp(&self.rings, precision))
395 } else {
396 format!("POLYGON Z {}", Self::coords_with_dp(&self.rings, precision))
397 }
398 }
399
400 fn check_coordinates(&self, crs: &CRS) -> Result<(), MyError> {
401 crs.check_polygon(&self.rings)
402 }
403
404 fn to_geos(&self) -> Result<Geometry, MyError> {
405 Self::to_geos_impl(&self.rings)
406 }
407}
408
409impl Polygon {
410 pub(crate) fn from(rings: Vec<Vec<Vec<f64>>>) -> Self {
411 let rings = Self::ensure_precision_impl(&rings);
412 Polygon { rings }
413 }
414
415 fn ensure_precision_impl(rings: &[Vec<Vec<f64>>]) -> Vec<Vec<Vec<f64>>> {
416 rings
417 .iter()
418 .map(|r| Line::ensure_precision_impl(r))
419 .collect()
420 }
421
422 pub(crate) fn coords_as_txt(rings: &[Vec<Vec<f64>>]) -> String {
423 Self::coords_with_dp(rings, config().default_precision())
424 }
425
426 fn coords_with_dp(rings: &[Vec<Vec<f64>>], precision: usize) -> String {
427 let rings: Vec<String> = rings
428 .iter()
429 .map(|x| Line::coords_with_dp(x, precision))
430 .collect();
431 format!("({})", rings.join(", "))
432 }
433
434 fn to_geos_impl(rings: &[Vec<Vec<f64>>]) -> Result<Geometry, MyError> {
435 let vertices: Vec<&[f64]> = rings[0].iter().map(|x| x.as_slice()).collect();
436 let xy = CoordSeq::new_from_vec(&vertices).map_err(MyError::Geos)?;
437 let exterior = Geometry::create_linear_ring(xy).map_err(MyError::Geos)?;
438
439 let mut interiors = vec![];
440 for hole in &rings[1..] {
441 let vertices: Vec<&[f64]> = hole.iter().map(|x| x.as_slice()).collect();
442 let xy = CoordSeq::new_from_vec(&vertices).map_err(MyError::Geos)?;
443 let hole = Geometry::create_linear_ring(xy).map_err(MyError::Geos)?;
444 interiors.push(hole);
445 }
446
447 Geometry::create_polygon(exterior, interiors).map_err(MyError::Geos)
448 }
449
450 fn outer(&self) -> &Vec<Vec<f64>> {
452 self.rings[0].as_ref()
453 }
454
455 fn outer_as_ring(&self) -> Line {
456 Line::from(self.outer().to_vec())
457 }
458
459 fn has_holes(&self) -> bool {
462 self.rings.len() > 1
463 }
464
465 fn inners(&self) -> &[Vec<Vec<f64>>] {
467 &self.rings.as_slice()[1..]
468 }
469}
470
471impl fmt::Display for Polygon {
472 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> fmt::Result {
473 write!(f, "POLYGON (...)")
474 }
475}
476
477#[derive(Debug, Clone, PartialEq, PartialOrd)]
481pub struct Points {
482 points: Vec<Vec<f64>>,
483}
484
485impl GTrait for Points {
486 fn is_2d(&self) -> bool {
487 self.points[0].len() == 2
488 }
489
490 fn to_wkt_fmt(&self, precision: usize) -> String {
491 if self.is_2d() {
492 format!(
493 "MULTIPOINT {}",
494 Self::coords_with_dp(&self.points, precision)
495 )
496 } else {
497 format!(
498 "MULTIPOINT Z {}",
499 Self::coords_with_dp(&self.points, precision)
500 )
501 }
502 }
503
504 fn check_coordinates(&self, crs: &CRS) -> Result<(), MyError> {
505 if self.points.iter().all(|p| crs.check_point(p).is_ok()) {
506 Ok(())
507 } else {
508 Err(MyError::Runtime(
509 "At least one point has invalid coordinates".into(),
510 ))
511 }
512 }
513
514 fn to_geos(&self) -> Result<Geometry, MyError> {
515 let mut points: Vec<Geometry> = vec![];
516 for p in &self.points {
517 let g = Point::to_geos_impl(p)?;
518 points.push(g);
519 }
520 Geometry::create_multipoint(points).map_err(MyError::Geos)
521 }
522}
523
524impl Points {
525 pub(crate) fn from(points: Vec<Vec<f64>>) -> Self {
526 let points = points
527 .iter()
528 .map(|x| Point::ensure_precision_impl(x))
529 .collect();
530 Points { points }
531 }
532
533 pub(crate) fn coords_as_txt(points: &[Vec<f64>]) -> String {
534 Self::coords_with_dp(points, config().default_precision())
535 }
536
537 fn coords_with_dp(points: &[Vec<f64>], precision: usize) -> String {
538 let points: Vec<String> = points
539 .iter()
540 .map(|x| Point::coords_with_dp(x, precision))
541 .collect();
542 format!("({})", points.join(", "))
543 }
544
545 #[cfg(test)]
546 pub(crate) fn size(&self) -> usize {
548 self.points.len()
549 }
550}
551
552impl fmt::Display for Points {
553 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> fmt::Result {
554 write!(f, "POINTS (...)")
555 }
556}
557
558#[derive(Debug, Clone, PartialEq, PartialOrd)]
562pub struct Lines {
563 lines: Vec<Vec<Vec<f64>>>,
564}
565
566impl GTrait for Lines {
567 fn is_2d(&self) -> bool {
568 self.lines[0][0].len() == 2
569 }
570
571 fn to_wkt_fmt(&self, precision: usize) -> String {
572 if self.is_2d() {
573 format!(
574 "MULTILINESTRING {}",
575 Self::coords_with_dp(self.lines.as_slice(), precision)
576 )
577 } else {
578 format!(
579 "MULTILINESTRING Z {}",
580 Self::coords_with_dp(self.lines.as_slice(), precision)
581 )
582 }
583 }
584
585 fn check_coordinates(&self, crs: &CRS) -> Result<(), MyError> {
586 if self.lines.iter().all(|l| crs.check_line(l).is_ok()) {
587 Ok(())
588 } else {
589 Err(MyError::Runtime(
590 "At least one line has invalid coordinates".into(),
591 ))
592 }
593 }
594
595 fn to_geos(&self) -> Result<Geometry, MyError> {
596 let mut lines: Vec<Geometry> = vec![];
597 for l in &self.lines {
598 let g = Line::to_geos_impl(l)?;
599 lines.push(g);
600 }
601 Geometry::create_multiline_string(lines).map_err(MyError::Geos)
602 }
603}
604
605impl Lines {
606 pub(crate) fn from(lines: Vec<Vec<Vec<f64>>>) -> Self {
607 let lines = lines
608 .iter()
609 .map(|x| Line::ensure_precision_impl(x))
610 .collect();
611 Lines { lines }
612 }
613
614 pub(crate) fn coords_as_txt(lines: &[Vec<Vec<f64>>]) -> String {
615 Self::coords_with_dp(lines, config().default_precision())
616 }
617
618 fn coords_with_dp(lines: &[Vec<Vec<f64>>], precision: usize) -> String {
619 let lines: Vec<String> = lines
620 .iter()
621 .map(|x| Line::coords_with_dp(x.as_slice(), precision))
622 .collect();
623 format!("({})", lines.join(", "))
624 }
625
626 #[cfg(test)]
628 fn size(&self) -> usize {
629 self.lines.len()
630 }
631
632 #[cfg(test)]
634 fn lines(&self) -> &[Vec<Vec<f64>>] {
635 self.lines.as_slice()
636 }
637}
638
639impl fmt::Display for Lines {
640 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> fmt::Result {
641 write!(f, "LINES (...)")
642 }
643}
644
645#[derive(Debug, Clone, PartialEq, PartialOrd)]
649pub struct Polygons {
650 polygons: Vec<Vec<Vec<Vec<f64>>>>,
651}
652
653impl GTrait for Polygons {
654 fn is_2d(&self) -> bool {
655 self.polygons[0][0][0].len() == 2
656 }
657
658 fn to_wkt_fmt(&self, precision: usize) -> String {
659 if self.is_2d() {
660 format!(
661 "MULTIPOLYGON {}",
662 Self::coords_with_dp(self.polygons.as_slice(), precision)
663 )
664 } else {
665 format!(
666 "MULTIPOLYGON Z {}",
667 Self::coords_with_dp(self.polygons.as_slice(), precision)
668 )
669 }
670 }
671
672 fn check_coordinates(&self, crs: &CRS) -> Result<(), MyError> {
673 if self
674 .polygons
675 .iter()
676 .all(|poly| crs.check_polygon(poly).is_ok())
677 {
678 Ok(())
679 } else {
680 Err(MyError::Runtime(
681 "At least one polygon has invalid coordinates".into(),
682 ))
683 }
684 }
685
686 fn to_geos(&self) -> Result<Geometry, MyError> {
687 let mut polygons: Vec<Geometry> = vec![];
688 for p in &self.polygons {
689 let g = Polygon::to_geos_impl(p)?;
690 polygons.push(g);
691 }
692 Geometry::create_multipolygon(polygons).map_err(MyError::Geos)
693 }
694}
695
696impl Polygons {
697 pub(crate) fn from(polygons: Vec<Vec<Vec<Vec<f64>>>>) -> Self {
698 let polygons = polygons
699 .iter()
700 .map(|x| Polygon::ensure_precision_impl(x))
701 .collect();
702 Polygons { polygons }
703 }
704
705 pub(crate) fn coords_as_txt(polygons: &[Vec<Vec<Vec<f64>>>]) -> String {
706 Self::coords_with_dp(polygons, config().default_precision())
707 }
708
709 fn coords_with_dp(polygons: &[Vec<Vec<Vec<f64>>>], precision: usize) -> String {
710 let polygons: Vec<String> = polygons
711 .iter()
712 .map(|x| Polygon::coords_with_dp(x.as_slice(), precision))
713 .collect();
714 format!("({})", polygons.join(", "))
715 }
716}
717
718impl fmt::Display for Polygons {
719 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> fmt::Result {
720 write!(f, "POLYGONS (...)")
721 }
722}
723
724#[derive(Debug, Clone, PartialEq, PartialOrd)]
728pub struct Geometries {
729 items: Vec<G>,
730}
731
732impl GTrait for Geometries {
733 fn is_2d(&self) -> bool {
734 match &self.items[0] {
735 G::Point(x) => x.is_2d(),
736 G::Line(x) => x.is_2d(),
737 G::Polygon(x) => x.is_2d(),
738 G::Points(x) => x.is_2d(),
739 G::Lines(x) => x.is_2d(),
740 G::Polygons(x) => x.is_2d(),
741 _ => {
742 error!("Unexpected geometries item");
743 false
744 }
745 }
746 }
747
748 fn to_wkt_fmt(&self, precision: usize) -> String {
749 let items: Vec<String> = self
750 .items
751 .iter()
752 .map(|x| match x {
753 G::Point(x) => x.to_wkt_fmt(precision),
754 G::Line(x) => x.to_wkt_fmt(precision),
755 G::Polygon(x) => x.to_wkt_fmt(precision),
756 G::Points(x) => x.to_wkt_fmt(precision),
757 G::Lines(x) => x.to_wkt_fmt(precision),
758 G::Polygons(x) => x.to_wkt_fmt(precision),
759 G::BBox(x) => x.to_wkt_fmt(precision),
760 _ => panic!("Unexpected geometries item"),
761 })
762 .collect();
763 if self.is_2d() {
764 format!("GEOMETRYCOLLECTION ({})", items.join(", "))
765 } else {
766 format!("GEOMETRYCOLLECTION Z ({})", items.join(", "))
767 }
768 }
769
770 fn check_coordinates(&self, crs: &CRS) -> Result<(), MyError> {
771 if self.items.iter().all(|x| match x {
772 G::Point(x) => x.check_coordinates(crs).is_ok(),
773 G::Line(x) => x.check_coordinates(crs).is_ok(),
774 G::Polygon(x) => x.check_coordinates(crs).is_ok(),
775 G::Points(x) => x.check_coordinates(crs).is_ok(),
776 G::Lines(x) => x.check_coordinates(crs).is_ok(),
777 G::Polygons(x) => x.check_coordinates(crs).is_ok(),
778 _ => {
779 error!("Unexpected geometries item");
780 false
781 }
782 }) {
783 Ok(())
784 } else {
785 Err(MyError::Runtime(
786 "At least one geometry has invalid coordinates".into(),
787 ))
788 }
789 }
790
791 fn to_geos(&self) -> Result<Geometry, MyError> {
792 let items: Result<Vec<Geometry>, MyError> = self
793 .items
794 .iter()
795 .map(|x| match x {
796 G::Point(x) => x.to_geos(),
797 G::Line(x) => x.to_geos(),
798 G::Polygon(x) => x.to_geos(),
799 G::Points(x) => x.to_geos(),
800 G::Lines(x) => x.to_geos(),
801 G::Polygons(x) => x.to_geos(),
802 _ => panic!("Unexpected geometries item"),
803 })
804 .collect();
805 Geometry::create_geometry_collection(items?).map_err(MyError::Geos)
806 }
807}
808
809impl Geometries {
810 pub(crate) fn from(items: Vec<G>) -> Self {
811 Geometries { items }
812 }
813}
814
815impl fmt::Display for Geometries {
816 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> fmt::Result {
817 write!(f, "GEOMETRIES (...)")
818 }
819}
820
821#[derive(Debug, Clone, PartialEq, PartialOrd)]
825pub struct BBox {
826 w: f64, s: f64, z_min: Option<f64>, e: f64, n: f64, z_max: Option<f64>, }
833
834impl GTrait for BBox {
835 fn is_2d(&self) -> bool {
836 self.z_min.is_none()
837 }
838
839 fn to_wkt_fmt(&self, precision: usize) -> String {
840 if self.z_min.is_none() {
841 format!(
842 "BBOX ({:.4$}, {:.4$}, {:.4$}, {:.4$})",
843 self.w, self.s, self.e, self.n, precision
844 )
845 } else {
846 format!(
847 "BBOX ({:.6$}, {:.6$}, {:.6$}, {:.6$}, {:.6$}, {:.6$})",
848 self.w,
849 self.s,
850 self.z_min.unwrap(),
851 self.e,
852 self.n,
853 self.z_max.unwrap(),
854 precision
855 )
856 }
857 }
858
859 fn check_coordinates(&self, crs: &CRS) -> Result<(), MyError> {
860 crs.check_point([self.w, self.s].as_ref())?;
861 crs.check_point([self.e, self.n].as_ref())?;
862 Ok(())
863 }
864
865 fn to_geos(&self) -> Result<Geometry, MyError> {
866 self.to_geos_impl()
867 }
868}
869
870impl BBox {
871 pub(crate) fn from(xy: Vec<f64>) -> Self {
876 if xy.len() == 4 {
877 BBox {
878 w: ensure_precision(&xy[0]),
879 s: ensure_precision(&xy[1]),
880 z_min: None,
881 e: ensure_precision(&xy[2]),
882 n: ensure_precision(&xy[3]),
883 z_max: None,
884 }
885 } else {
886 BBox {
888 w: ensure_precision(&xy[0]),
889 s: ensure_precision(&xy[1]),
890 z_min: Some(ensure_precision(&xy[2])),
891 e: ensure_precision(&xy[3]),
892 n: ensure_precision(&xy[4]),
893 z_max: Some(ensure_precision(&xy[5])),
894 }
895 }
896 }
897
898 fn to_geos_impl(&self) -> Result<Geometry, MyError> {
901 let x1 = self.w;
902 let y1 = self.s;
903 let x2 = self.e;
904 let y2 = self.n;
905
906 if x1 < x2 {
908 let cs =
909 CoordSeq::new_from_vec(&[&[x1, y1], &[x2, y1], &[x2, y2], &[x1, y2], &[x1, y1]])
910 .map_err(|x| {
911 error!("Failed creating BBOX outer ring coordinates: {x}");
912 MyError::Geos(x)
913 })?;
914
915 let outer = Geometry::create_linear_ring(cs).map_err(|x| {
916 error!("Failed creating BBOX outer ring: {x}");
917 MyError::Geos(x)
918 })?;
919
920 Geometry::create_polygon(outer, vec![]).map_err(|x| {
921 error!("Failed creating BBOX polygon: {x}");
922 MyError::Geos(x)
923 })
924 } else {
925 let cs1 = CoordSeq::new_from_vec(&[
926 &[x1, y1],
927 &[180.0, y1],
928 &[180.0, y2],
929 &[x1, y2],
930 &[x1, y1],
931 ])
932 .map_err(|x| {
933 error!("Failed creating BBOX 1st outer ring coordinates: {x}");
934 MyError::Geos(x)
935 })?;
936
937 let cs2 = CoordSeq::new_from_vec(&[
938 &[x2, y1],
939 &[x2, y2],
940 &[-180.0, y2],
941 &[-180.0, y1],
942 &[x2, y1],
943 ])
944 .map_err(|x| {
945 error!("Failed creating BBOX 2nd outer ring coordinates: {x}");
946 MyError::Geos(x)
947 })?;
948
949 let outer1 = Geometry::create_linear_ring(cs1).map_err(|x| {
950 error!("Failed creating BBOX 1st outer ring: {x}");
951 MyError::Geos(x)
952 })?;
953
954 let outer2 = Geometry::create_linear_ring(cs2).map_err(|x| {
955 error!("Failed creating BBOX 2nd outer ring: {x}");
956 MyError::Geos(x)
957 })?;
958
959 let p1 = Geometry::create_polygon(outer1, vec![]).map_err(|x| {
960 error!("Failed creating BBOX 1st polygon: {x}");
961 MyError::Geos(x)
962 })?;
963 let p2 = Geometry::create_polygon(outer2, vec![]).map_err(|x| {
964 error!("Failed creating BBOX 1st polygon: {x}");
965 MyError::Geos(x)
966 })?;
967
968 Geometry::create_multipolygon(vec![p1, p2]).map_err(|x| {
969 error!("Failed creating BBOX multi-polygon: {x}");
970 MyError::Geos(x)
971 })
972 }
973 }
974
975 #[cfg(test)]
977 fn west(&self) -> f64 {
978 self.w
979 }
980
981 #[cfg(test)]
983 fn east(&self) -> f64 {
984 self.e
985 }
986
987 #[cfg(test)]
989 fn south(&self) -> f64 {
990 self.s
991 }
992
993 #[cfg(test)]
995 fn north(&self) -> f64 {
996 self.n
997 }
998
999 #[cfg(test)]
1001 fn z_min(&self) -> Option<f64> {
1002 self.z_min
1003 }
1004
1005 #[cfg(test)]
1007 fn z_max(&self) -> Option<f64> {
1008 self.z_max
1009 }
1010}
1011
1012impl fmt::Display for BBox {
1013 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1014 write!(f, "BBox (...)")
1015 }
1016}
1017
1018#[cfg(test)]
1019mod tests {
1020 use super::*;
1021 use crate::{expr::E, text::cql2};
1022 use approx::assert_relative_eq;
1023 use geos::Geom;
1024 use std::error::Error;
1025
1026 const TOLERANCE: f64 = 1.0E-3;
1027
1028 #[test]
1029 fn test_pt_equality() {
1030 let p1 = Point {
1031 coord: vec![1., 1.],
1032 };
1033 let p2 = Point {
1034 coord: vec![1.0, 1.0],
1035 };
1036 let p3 = Point {
1037 coord: vec![1.0, 1.1],
1038 };
1039 let p4 = Point {
1040 coord: vec![1.1, 1.0],
1041 };
1042
1043 assert_eq!(p1, p2);
1044 assert_ne!(p1, p3);
1045 assert_ne!(p1, p4);
1046 assert!(p1 == p2);
1047 assert!(p1 != p3);
1048 assert!(p2 != p4);
1049 assert!(p3 != p4);
1050 }
1051
1052 #[test]
1053 fn test_pt_comparison() {
1054 let p1 = Point {
1055 coord: vec![1.0, 1.0],
1056 };
1057 let p2 = Point {
1058 coord: vec![1.0, 1.1],
1059 };
1060 let p3 = Point {
1061 coord: vec![1.1, 1.0],
1062 };
1063
1064 assert!(p1 < p2);
1065 assert!(p1 < p3);
1066 assert!(p2 < p3);
1067 }
1068
1069 #[test]
1070 fn test_point() {
1072 const G: &str = r#"point (-3.508362 -1.754181)"#;
1073
1074 let exp = cql2::geom_expression(G);
1075 assert!(exp.is_ok());
1076 let spa = exp.unwrap();
1078 let g = match spa {
1079 E::Spatial(G::Point(x)) => x,
1080 _ => panic!("Not a Point..."),
1081 };
1082 assert_eq!(g.is_2d(), true);
1083 assert!(g.z().is_none());
1085 assert_relative_eq!(g.x(), -3.508, epsilon = TOLERANCE);
1086 assert_relative_eq!(g.y(), -1.754, epsilon = TOLERANCE);
1087 }
1088
1089 #[test]
1090 fn test_line() {
1092 const G: &str = r#"LineString(43.72992 -79.2998, 43.73005 -79.2991, 43.73006 -79.2984,
1093 43.73140 -79.2956, 43.73259 -79.2950, 43.73266 -79.2945,
1094 43.73320 -79.2936, 43.73378 -79.2936, 43.73486 -79.2917)"#;
1095
1096 let exp = cql2::geom_expression(G);
1097 assert!(exp.is_ok());
1098 let spa = exp.unwrap();
1100 let g = match spa {
1101 E::Spatial(G::Line(x)) => x,
1102 _ => panic!("Not a Line..."),
1103 };
1104 assert_eq!(g.is_2d(), true);
1105 assert_eq!(g.size(), 9);
1106
1107 assert!(g.first().is_some());
1108 let first = g.first().unwrap();
1109 assert_relative_eq!(first[0], 43.729, epsilon = TOLERANCE);
1110 assert_relative_eq!(first[1], -79.299, epsilon = TOLERANCE);
1111 assert!(g.last().is_some());
1112 let last = g.last().unwrap();
1113 assert_relative_eq!(last[0], 43.734, epsilon = TOLERANCE);
1114 assert_relative_eq!(last[1], -79.291, epsilon = TOLERANCE);
1115 }
1116
1117 #[test]
1118 fn test_polygon2d() {
1120 const G: &str = r#"PolyGon ((-0.333333 89.0, -102.723546 -0.5, -179.0 -89.0, -1.9 89.0, -0.0 89.0, 2.00001 -1.9, -0.333333 89.0))"#;
1121
1122 let exp = cql2::geom_expression(G);
1123 assert!(exp.is_ok());
1124 let spa = exp.unwrap();
1126 let g = match spa {
1127 E::Spatial(G::Polygon(x)) => x,
1128 _ => panic!("Not a Polygon..."),
1129 };
1130 assert_eq!(g.is_2d(), true);
1131
1132 let outer_ring = g.outer_as_ring();
1133 assert!(outer_ring.is_ring());
1134 assert!(outer_ring.is_closed());
1135 assert_eq!(outer_ring.size(), 7);
1136
1137 assert!(!g.has_holes());
1139 assert!(g.inners().is_empty());
1140 }
1141
1142 #[test]
1143 fn test_polygon3d() {
1145 const G: &str = r#"POLYGON Z ((-49.88024 0.5 -75993.341684, -1.5 -0.99999 -100000.0, 0.0 0.5 -0.333333, -49.88024 0.5 -75993.341684), (-65.887123 2.00001 -100000.0, 0.333333 -53.017711 -79471.332949, 180.0 0.0 1852.616704, -65.887123 2.00001 -100000.0))"#;
1146
1147 let exp = cql2::geom_expression(G);
1148 assert!(exp.is_ok());
1149 let spa = exp.unwrap();
1151 let g = match spa {
1152 E::Spatial(G::Polygon(x)) => x,
1153 _ => panic!("Not a Polygon..."),
1154 };
1155 assert_eq!(g.is_2d(), false);
1156
1157 let outer_ring = g.outer_as_ring();
1158 assert!(outer_ring.is_ring());
1159 assert!(outer_ring.is_closed());
1160 assert_eq!(outer_ring.size(), 4);
1161
1162 assert!(g.has_holes());
1164 assert_eq!(g.inners().len(), 1);
1165 }
1166
1167 #[test]
1168 fn test_multiline() {
1170 const G: &str = r#"MultiLineString(
1171 ( -1.9 -0.99999, 75.292574 1.5, -0.5 -4.016458, -31.708594 -74.743801, 179.0 -90.0 ),
1172 (-1.9 -1.1, 1.5 8.547371))"#;
1173
1174 let exp = cql2::geom_expression(G);
1175 assert!(exp.is_ok());
1177 let spa = exp.unwrap();
1178 let g = match spa {
1179 E::Spatial(G::Lines(x)) => x,
1180 _ => panic!("Not a Polygon..."),
1181 };
1182 assert_eq!(g.is_2d(), true);
1183 let lines = g.lines();
1184 assert_eq!(lines.len(), 2);
1185 assert_eq!(lines.len(), g.size());
1187 }
1188
1189 #[test]
1190 #[tracing_test::traced_test]
1191 fn test_bbox() {
1192 const G1: &str = "bbox(-128.098193, -1.1, -99999.0, 180.0, 90.0, 100000.0)";
1193 const G2: &str = "bbox(-128.098193,-1.1, -99999.0,180.0 , \t90.0, \n 100000.0)";
1194
1195 let x = cql2::geom_expression(G1);
1196 assert!(x.is_ok());
1198 let g = x.unwrap();
1199 assert!(matches!(g, E::Spatial(G::BBox(_))));
1200 let bbox1 = match g {
1201 E::Spatial(G::BBox(x)) => x,
1202 _ => panic!("Not a BBox"),
1203 };
1204 assert!(!bbox1.is_2d());
1205
1206 let x = cql2::geom_expression(G2);
1209 assert!(x.is_ok());
1211 let g = x.unwrap();
1212 assert!(matches!(g, E::Spatial(G::BBox(_))));
1213 let bbox2 = match g {
1214 E::Spatial(G::BBox(x)) => x,
1215 _ => panic!("Not a BBox"),
1216 };
1217 assert!(!bbox2.is_2d());
1218
1219 assert_eq!(bbox1.west(), bbox2.west());
1220 assert_eq!(bbox1.east(), bbox2.east());
1221 assert_eq!(bbox1.south(), bbox2.south());
1222 assert_eq!(bbox1.north(), bbox2.north());
1223 assert_eq!(bbox1.z_min(), bbox2.z_min());
1224 assert_eq!(bbox1.z_max(), bbox2.z_max());
1225 }
1226
1227 #[test]
1228 fn test_bbox_to_polygon() {
1230 const G1: &str = "BBOX(-180,-90,180,90)";
1231 const WKT: &str = "POLYGON ((-180 -90, 180 -90, 180 90, -180 90, -180 -90))";
1232 const G2: &str = "bbox(-180.0,-90.,-99999.0,180.0,90.0,100000.0)";
1233
1234 let x1 = cql2::geom_expression(G1);
1235 assert!(x1.is_ok());
1237 let g1 = x1.unwrap();
1238 assert!(matches!(g1, E::Spatial(G::BBox(_))));
1239 let bbox1 = match g1 {
1240 E::Spatial(G::BBox(x)) => x,
1241 _ => panic!("Not a BBox"),
1242 };
1243 assert!(bbox1.is_2d());
1244 let g1 = bbox1.to_geos();
1245 assert!(g1.is_ok());
1246 let g1 = g1.unwrap();
1247 let wkt1 = g1.to_wkt().unwrap();
1249 assert_eq!(wkt1, WKT);
1250
1251 let x2 = cql2::geom_expression(G2);
1252 assert!(x2.is_ok());
1254 let g2 = x2.unwrap();
1255 assert!(matches!(g2, E::Spatial(G::BBox(_))));
1256 let bbox2 = match g2 {
1257 E::Spatial(G::BBox(x)) => x,
1258 _ => panic!("Not a BBox"),
1259 };
1260 assert!(!bbox2.is_2d());
1261 let g2 = bbox2.to_geos();
1262 assert!(g2.is_ok());
1263 let g2 = g2.unwrap();
1264 let wkt2 = g2.to_wkt().unwrap();
1266 assert_eq!(wkt2, WKT);
1267 }
1268
1269 #[test]
1270 fn test_to_wkt() {
1272 const G: &str = r#"Polygon Z (
1273 (
1274 -49.88024 0.5 -75993.341684,
1275 -1.5 -0.99999 -100000.0,
1276 0.0 0.5 -0.333333,
1277 -49.88024 0.5 -75993.341684
1278 ), (
1279 -65.887123 2.00001 -100000.0,
1280 0.333333 -53.017711 -79471.332949,
1281 180.0 0.0 1852.616704,
1282 -65.887123 2.00001 -100000.0
1283 ))"#;
1284 const WKT: &str = "POLYGON Z ((-49.880240 0.500000 -75993.341684, -1.500000 -0.999990 -100000.000000, 0.000000 0.500000 -0.333333, -49.880240 0.500000 -75993.341684), (-65.887123 2.000010 -100000.000000, 0.333333 -53.017711 -79471.332949, 180.000000 0.000000 1852.616704, -65.887123 2.000010 -100000.000000))";
1285
1286 let exp = cql2::geom_expression(G);
1287 let spa = exp.expect("Failed parsing Polygon WKT");
1289 let g = match spa {
1290 E::Spatial(G::Polygon(x)) => x,
1291 _ => panic!("Not a Polygon..."),
1292 };
1293 assert_eq!(g.is_2d(), false);
1295
1296 let wkt = g.to_wkt();
1297 assert_eq!(WKT, wkt);
1299 }
1300
1301 #[test]
1302 fn test_to_geos() -> Result<(), Box<dyn Error>> {
1304 let g = G::try_from_wkt("POINT(17.03 45.87)")?;
1305 assert!(matches!(g, G::Point(_)));
1307 let gg = g.to_geos()?;
1309 assert_eq!(gg.get_type()?, "Point");
1310 assert_eq!(gg.get_x()?, 17.03);
1311 assert_eq!(gg.get_y()?, 45.87);
1312 assert!(!gg.has_z()?);
1313
1314 let g = G::try_from_wkt("LINESTRING(-49.85 0.5, -1.5 -0.999, 0.0 0.5, -49.88 0.5)")?;
1315 assert!(matches!(g, G::Line(_)));
1317 let gg = g.to_geos()?;
1319 assert_eq!(gg.get_type()?, "LineString");
1320 assert_eq!(gg.get_num_points()?, 4);
1321 assert_eq!(gg.get_start_point()?.get_x()?, -49.85);
1322 assert_eq!(gg.get_end_point()?.get_y()?, 0.5);
1323
1324 let g = G::try_from_wkt(
1325 r#"PolyGon ((
1326 -0.333333 89.0,
1327 -102.723546 -0.5,
1328 -179.0 -89.0,
1329 -1.9 89.0,
1330 -0.0 89.0,
1331 2.00001 -1.9,
1332 -0.333333 89.0))"#,
1333 )?;
1334 assert!(matches!(g, G::Polygon(_)));
1336 let gg = g.to_geos()?;
1338 assert_eq!(gg.get_type()?, "Polygon");
1339 assert_eq!(gg.get_num_interior_rings()?, 0);
1340 assert_eq!(gg.get_exterior_ring()?.get_num_coordinates()?, 7);
1341
1342 let g = G::try_from_wkt("MULTIPOINT(17.03 45.87, -0.33 89.02)")?;
1345 assert!(matches!(g, G::Points(_)));
1347 let gg = g.to_geos()?;
1349 assert_eq!(gg.get_type()?, "MultiPoint");
1350 assert_eq!(gg.get_num_geometries()?, 2);
1351 assert_eq!(gg.get_geometry_n(0)?.get_x()?, 17.03);
1352 assert_eq!(gg.get_geometry_n(1)?.get_y()?, 89.02);
1353
1354 let g = G::try_from_wkt(
1355 r#"MULTILINESTRING(
1356 (-49.85 0.5, -1.5 -0.999, 0.0 0.5),
1357 (34.3 3.2, 0.1 0.2))"#,
1358 )?;
1359 assert!(matches!(g, G::Lines(_)));
1361 let gg = g.to_geos()?;
1363 assert_eq!(gg.get_type()?, "MultiLineString");
1364 assert_eq!(gg.get_num_geometries()?, 2);
1365 assert_eq!(gg.get_geometry_n(0)?.get_start_point()?.get_x()?, -49.85);
1366 assert_eq!(gg.get_geometry_n(1)?.get_end_point()?.get_y()?, 0.2);
1367
1368 let g = G::try_from_wkt(
1369 r#"MULTIPOLYGON (
1370 ((
1371 180.0 -16.0671326636424,
1372 180.0 -16.5552165666392,
1373 179.364142661964 -16.8013540769469,
1374 178.725059362997 -17.012041674368,
1375 178.596838595117 -16.63915,
1376 179.096609362997 -16.4339842775474,
1377 179.413509362997 -16.3790542775474,
1378 180.0 -16.0671326636424
1379 )),((
1380 178.12557 -17.50481,
1381 178.3736 -17.33992,
1382 178.71806 -17.62846,
1383 178.55271 -18.15059,
1384 177.93266 -18.28799,
1385 177.38146 -18.16432,
1386 177.28504 -17.72465,
1387 177.67087 -17.38114,
1388 178.12557 -17.50481
1389 )),((
1390 -179.793320109049 -16.0208822567412,
1391 -179.917369384765 -16.5017831356494,
1392 -180 -16.5552165666392,
1393 -180 -16.0671326636424,
1394 -179.793320109049 -16.0208822567412
1395 ))
1396 )"#,
1397 )?;
1398 assert!(matches!(g, G::Polygons(_)));
1400 let gg = g.to_geos()?;
1402 assert_eq!(gg.get_type()?, "MultiPolygon");
1403 assert_eq!(gg.get_num_geometries()?, 3);
1404 let p1 = gg.get_geometry_n(0)?;
1405 assert_eq!(p1.get_type()?, "Polygon");
1406 assert_eq!(p1.get_exterior_ring()?.get_num_coordinates()?, 8);
1407 assert_eq!(p1.get_num_interior_rings()?, 0);
1408
1409 let g = G::try_from_wkt(
1410 r#"GEOMETRYCOLLECTION(
1411 POINT(17.03 45.87),
1412 LINESTRING(-49.85 0.5, -1.5 -0.999, 0.0 0.5, -49.88 0.5)
1413 )"#,
1414 )?;
1415 assert!(matches!(g, G::Vec(_)));
1417 let gg = g.to_geos()?;
1419 assert_eq!(gg.get_type()?, "GeometryCollection");
1420 assert_eq!(gg.get_num_geometries()?, 2);
1421 Ok(())
1422 }
1423
1424 #[test]
1425 fn test_geos() -> Result<(), Box<dyn Error>> {
1427 const G: &str = r#"MultiLineString(
1428 (-49.85 0.5, -1.5 -0.999, 0.0 0.5, -49.88 0.5 ),
1429 (-65.87 2.01, 0.33 -53.07, 180.0 0)
1430 )"#;
1431
1432 let exp = cql2::geom_expression(G);
1433 let spa = exp.expect("Failed parsing Polygon WKT");
1435 let g = match spa {
1436 E::Spatial(G::Lines(x)) => x,
1437 _ => panic!("Not a Lines..."),
1438 };
1439 assert_eq!(g.is_2d(), true);
1440 assert_eq!(g.size(), 2);
1441
1442 let geos = g.to_geos().expect("Failed converting to GEOS geometry");
1443 assert_eq!(geos.get_num_geometries()?, g.size());
1444 let l1 = geos.get_geometry_n(0)?;
1445 assert_eq!(l1.get_num_coordinates()?, 4);
1446 let l2 = geos.get_geometry_n(1)?;
1447 assert_eq!(l2.get_num_coordinates()?, 3);
1448
1449 Ok(())
1450 }
1451
1452 #[test]
1453 fn test_new_from_wkt() -> Result<(), Box<dyn Error>> {
1455 const PT: &str = "POINT (-46.03556 -7.5325)";
1456 const LS: &str = "LINESTRING (-180 -45, 0 -45)";
1457 const P: &str = "POLYGON ((-180 -90, -90 -90, -90 90, -180 90, -180 -90), (-120 -50, -100 -50, -100 -40, -120 -40, -120 -50))";
1458 const MPT: &str = "MULTIPOINT ((7.02 49.92), (90 180))";
1459 const MLS: &str = "MULTILINESTRING ((-180 -45, 0 -45), (0 45, 180 45))";
1460 const MP: &str = r#"MULTIPOLYGON(
1461 ((-180 -90, -90 -90, -90 90, -180 90, -180 -90),
1462 (-120 -50, -100 -50, -100 -40, -120 -40, -120 -50)),
1463 ((0 0, 10 0, 10 10, 0 10, 0 0))
1464 )"#;
1465 const MG: &str = r#"GEOMETRYCOLLECTION(
1466 POINT(7.02 49.92),
1467 POLYGON((0 0, 10 0, 10 10, 0 10, 0 0))
1468 )"#;
1469
1470 let pt = Geometry::new_from_wkt(PT);
1471 assert!(pt.is_ok());
1472 assert_eq!(pt?.to_wkt()?, PT);
1473
1474 let ls = Geometry::new_from_wkt(LS);
1475 assert!(ls.is_ok());
1476 assert_eq!(ls?.to_wkt()?, LS);
1478
1479 let poly = Geometry::new_from_wkt(P);
1480 assert!(poly.is_ok());
1481 assert_eq!(poly?.to_wkt()?, P);
1483
1484 let points = Geometry::new_from_wkt(MPT);
1485 assert!(points.is_ok());
1486 assert_eq!(points?.to_wkt()?, MPT);
1488
1489 let lines = Geometry::new_from_wkt(MLS);
1490 assert!(lines.is_ok());
1491 assert_eq!(lines?.to_wkt()?, MLS);
1493
1494 let polys = Geometry::new_from_wkt(MP);
1495 assert!(polys.is_ok());
1496 assert_eq!(polys?.get_type()?, "MultiPolygon");
1499
1500 let geometries = Geometry::new_from_wkt(MG);
1501 assert!(geometries.is_ok());
1502 assert_eq!(geometries?.get_type()?, "GeometryCollection");
1505
1506 Ok(())
1507 }
1508
1509 #[test]
1510 fn test_point_in_polygon() -> Result<(), Box<dyn Error>> {
1512 const WKT1: &str = "POINT(-46.03556 -7.5325)";
1513 const WKT2: &str =
1514 "POLYGON((-65.887123 2.00001, 0.333333 -53.017711, 180.0 0.0, -65.887123 2.00001))";
1515
1516 let pt = Geometry::new_from_wkt(WKT1).expect("Failed parsing point");
1517 let polygon = Geometry::new_from_wkt(WKT2).expect("Failed parsing polygon");
1518
1519 pt.within(&polygon)?;
1520 polygon.contains(&pt)?;
1522
1523 Ok(())
1524 }
1525
1526 #[test]
1527 fn test_bbox_antimeridian() -> Result<(), Box<dyn Error>> {
1529 const WKT: &str = "MULTIPOLYGON (((150 -90, 180 -90, 180 90, 150 90, 150 -90)), ((-150 -90, -150 90, -180 90, -180 -90, -150 -90)))";
1530
1531 let bbox = BBox::from(vec![150.0, -90.0, -150.0, 90.0]);
1532 let mp = bbox.to_geos()?;
1533 assert_eq!(mp.get_type()?, "MultiPolygon");
1534
1535 let wkt = mp.to_wkt()?;
1536 assert_eq!(wkt, WKT);
1537
1538 let pt = Geometry::new_from_wkt("POINT(152 10)")?;
1539
1540 pt.within(&mp)?;
1541 mp.contains(&pt)?;
1542
1543 Ok(())
1544 }
1545
1546 #[test]
1547 #[should_panic]
1548 fn test_invalid_pt_xy() {
1549 let pt = Point::from(vec![90.0, 180.0]);
1550 let crs = CRS::default();
1551 pt.check_coordinates(&crs).unwrap();
1552 }
1553
1554 #[test]
1555 #[should_panic]
1556 fn test_invalid_line_xy() {
1557 let line = Line::from(vec![vec![0.0, 45.0], vec![90.0, 180.0], vec![45.0, 45.0]]);
1558 let crs = CRS::default();
1559 line.check_coordinates(&crs).unwrap();
1560 }
1561
1562 #[test]
1563 fn test_touches() -> Result<(), Box<dyn Error>> {
1565 const WKT1: &str = "POLYGON ((0 -90, 0 0, 180 0, 180 -90, 0 -90))";
1566 const WKT2: &str = "POLYGON ((-180 -90, -180 90, 180 90, 180 -90, -180 -90))";
1567
1568 let p1 = Geometry::new_from_wkt(WKT1)?;
1569 let p2 = Geometry::new_from_wkt(WKT2)?;
1570
1571 assert!(!p1.touches(&p2)?);
1575
1576 Ok(())
1577 }
1578
1579 #[test]
1580 fn test_point_precision() -> Result<(), Box<dyn Error>> {
1582 const XYZ: [f64; 3] = [-16.0671326636424, -17.012041674368, 179.096609362997];
1583 const WKT: &str = "POINT Z (-16.067133 -17.012042 179.096609)";
1584
1585 let pt = Point::from(XYZ.to_vec());
1586 let wkt = pt.to_wkt();
1588 assert_eq!(wkt, WKT);
1590
1591 Ok(())
1592 }
1593
1594 #[test]
1595 fn test_line_precision() -> Result<(), Box<dyn Error>> {
1597 const WKT: &str = "LINESTRING (82.400480 30.411477, 82.722734 30.365046)";
1598
1599 let line_xy = vec![
1600 vec![82.400479770847, 30.4114773625851],
1601 vec![82.7227340026191, 30.3650460881709],
1602 ];
1603
1604 let line = Line::from(line_xy);
1605 let wkt = line.to_wkt();
1607 assert_eq!(wkt, WKT);
1609
1610 Ok(())
1611 }
1612
1613 #[test]
1614 fn test_bbox_precision() -> Result<(), Box<dyn Error>> {
1616 const WKT: &str = "BBOX (6.043073, 50.128052, 6.242751, 49.902226)";
1618
1619 let bbox_xy = vec![
1620 6.043073357781111,
1621 50.128051662794235,
1622 6.242751092156993,
1623 49.90222565367873,
1624 ];
1625
1626 let bbox = BBox::from(bbox_xy);
1627 let wkt = bbox.to_wkt();
1629 assert_eq!(wkt, WKT);
1631
1632 Ok(())
1633 }
1634
1635 #[test]
1636 fn test_try_from_wkt() -> Result<(), Box<dyn Error>> {
1637 #[rustfmt::skip]
1640 const TV: [(&str, &str, usize); 8] = [
1641 (
1642 "POINT(-46.035560 -7.532500)",
1643 "POINT (-46.03556 -7.53250)",
1644 5
1645 ), (
1646 "LINESTRING (-180 -45, 0 -45)",
1647 "LINESTRING (-180.0 -45.0, 0.0 -45.0)",
1648 1
1649 ), (
1650 r#"POLYGON (
1651 (-180 -90, -90 -90, -90 90, -180 90, -180 -90),
1652 (-120 -50, -100 -50, -100 -40, -120 -40, -120 -50)
1653 )"#,
1654 "POLYGON ((-180 -90, -90 -90, -90 90, -180 90, -180 -90), (-120 -50, -100 -50, -100 -40, -120 -40, -120 -50))",
1655 0
1656 ), (
1657 "MULTIPOINT ((7.02 49.92), (90 180))",
1658 "MULTIPOINT (7.02 49.92, 90.00 180.00)",
1659 2
1660 ), (
1661 "MULTILINESTRING ((-180 -45, 0 -45), (0 45, 180 45))",
1662 "MULTILINESTRING ((-180.0 -45.0, 0.0 -45.0), (0.0 45.0, 180.0 45.0))",
1663 1
1664 ), (
1665 r#"MULTIPOLYGON((
1666 (-180 -90, -90 -90, -90 90, -180 90, -180 -90),
1667 (-120 -50, -100 -50, -100 -40, -120 -40, -120 -50)
1668 ), (
1669 (0 0, 10 0, 10 10, 0 10, 0 0)
1670 ))"#,
1671 "MULTIPOLYGON (((-180 -90, -90 -90, -90 90, -180 90, -180 -90), (-120 -50, -100 -50, -100 -40, -120 -40, -120 -50)), ((0 0, 10 0, 10 10, 0 10, 0 0)))",
1672 0
1673 ), (
1674 "GEOMETRYCOLLECTION(POINT(7.02 49.92),POLYGON((0 0, 10 0, 10 10, 0 10, 0 0)))",
1675 "GEOMETRYCOLLECTION (POINT (7.0 49.9), POLYGON ((0.0 0.0, 10.0 0.0, 10.0 10.0, 0.0 10.0, 0.0 0.0)))",
1676 1
1677 ), (
1678 "BBOX(51.43,2.54,55.77,6.40)",
1679 "BBOX (51.43, 2.54, 55.77, 6.40)",
1680 2
1681 ),
1682 ];
1683
1684 for (ndx, (wkt, expected, precision)) in TV.iter().enumerate() {
1685 if let Ok(g) = G::try_from_wkt(wkt) {
1686 let actual = g.to_wkt_fmt(*precision);
1687 assert_eq!(actual, *expected);
1688 } else {
1689 panic!("Failed parsing WKT at index #{ndx}")
1690 }
1691 }
1692
1693 Ok(())
1694 }
1695}