diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 07a930d5e9..ddf68ef018 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -118,6 +118,32 @@ jobs: # we don't want to test `proj-network` because it only enables the `proj` feature - run: cargo test --features "use-proj use-serde" + geo_traits: + name: geo-traits + runs-on: ubuntu-latest + if: "!contains(github.event.head_commit.message, '[skip ci]')" + defaults: + run: + working-directory: geo-traits + strategy: + matrix: + container_image: + # We aim to support rust-stable plus (at least) the prior 3 releases, + # giving us about 6 months of coverage. + # + # Minimum supported rust version (MSRV) + - "ghcr.io/georust/geo-ci:proj-9.4.0-rust-1.75" + # Two most recent releases - we omit older ones for expedient CI + - "ghcr.io/georust/geo-ci:proj-9.4.0-rust-1.78" + - "ghcr.io/georust/geo-ci:proj-9.4.0-rust-1.79" + container: + image: ${{ matrix.container_image }} + steps: + - name: Checkout repository + uses: actions/checkout@v2 + - run: cargo check --all-targets + - run: cargo test + geo_postgis: name: geo-postgis runs-on: ubuntu-latest @@ -184,4 +210,3 @@ jobs: - name: Checkout repository uses: actions/checkout@v3 - run: RUSTDOCFLAGS="-D warnings" cargo doc --all-features --no-deps - diff --git a/Cargo.toml b/Cargo.toml index 232f863983..f6c018b1a4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,11 +2,12 @@ resolver = "2" members = [ "geo", - "geo-types", + "geo-bool-ops-benches", "geo-postgis", "geo-test-fixtures", + "geo-traits", + "geo-types", "jts-test-runner", - "geo-bool-ops-benches", ] [patch.crates-io] diff --git a/geo-traits/Cargo.toml b/geo-traits/Cargo.toml new file mode 100644 index 0000000000..a9c3c48b34 --- /dev/null +++ b/geo-traits/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "geo-traits" +version = "0.1.0" +license = "MIT OR Apache-2.0" +repository = "https://github.com/georust/geo" +documentation = "https://docs.rs/geo-traits/" +readme = "../README.md" +keywords = ["gis", "geo", "geography", "geospatial"] +description = "Geospatial traits" +rust-version = "1.65" +edition = "2021" + +[dependencies] +geo-types = "0.7" + +[dev-dependencies] diff --git a/geo-traits/src/coord.rs b/geo-traits/src/coord.rs new file mode 100644 index 0000000000..053f8874e3 --- /dev/null +++ b/geo-traits/src/coord.rs @@ -0,0 +1,141 @@ +use std::marker::PhantomData; + +use geo_types::{Coord, CoordNum}; + +use crate::Dimensions; + +/// A trait for accessing data from a generic Coord. +/// +/// Refer to [geo_types::Coord] for information about semantics and validity. +pub trait CoordTrait { + /// The coordinate type of this geometry + type T: CoordNum; + + /// Dimensions of the coordinate tuple + fn dim(&self) -> Dimensions; + + /// Access the n'th (0-based) element of the CoordinateTuple. + /// Returns `None` if `n >= DIMENSION`. + /// See also [`nth_unchecked()`](Self::nth_unchecked). + fn nth(&self, n: usize) -> Option { + if n < self.dim().size() { + Some(self.nth_unchecked(n)) + } else { + None + } + } + + /// x component of this coord. + fn x(&self) -> Self::T; + + /// y component of this coord. + fn y(&self) -> Self::T; + + /// Returns a tuple that contains the x/horizontal & y/vertical component of the coord. + fn x_y(&self) -> (Self::T, Self::T) { + (self.x(), self.y()) + } + + /// Access the n'th (0-based) element of the CoordinateTuple. + /// May panic if n >= DIMENSION. + /// See also [`nth()`](Self::nth). + fn nth_unchecked(&self, n: usize) -> Self::T; +} + +impl CoordTrait for Coord { + type T = T; + + fn nth_unchecked(&self, n: usize) -> Self::T { + match n { + 0 => self.x(), + 1 => self.y(), + _ => panic!("Coord only supports 2 dimensions"), + } + } + + fn dim(&self) -> Dimensions { + Dimensions::Xy + } + + fn x(&self) -> Self::T { + self.x + } + + fn y(&self) -> Self::T { + self.y + } +} + +impl CoordTrait for &Coord { + type T = T; + + fn nth_unchecked(&self, n: usize) -> Self::T { + match n { + 0 => self.x(), + 1 => self.y(), + _ => panic!("Coord only supports 2 dimensions"), + } + } + + fn dim(&self) -> Dimensions { + Dimensions::Xy + } + + fn x(&self) -> Self::T { + self.x + } + + fn y(&self) -> Self::T { + self.y + } +} + +impl CoordTrait for (T, T) { + type T = T; + + fn nth_unchecked(&self, n: usize) -> Self::T { + match n { + 0 => self.x(), + 1 => self.y(), + _ => panic!("(T, T) only supports 2 dimensions"), + } + } + + fn dim(&self) -> Dimensions { + Dimensions::Xy + } + + fn x(&self) -> Self::T { + self.0 + } + + fn y(&self) -> Self::T { + self.1 + } +} + +/// An empty struct that implements [CoordTrait]. +/// +/// This can be used as the `CoordType` of the `GeometryTrait` by implementations that don't have a +/// Coord concept +pub struct UnimplementedCoord(PhantomData); + +impl CoordTrait for UnimplementedCoord { + type T = T; + + fn dim(&self) -> Dimensions { + unimplemented!() + } + + fn nth_unchecked(&self, _n: usize) -> Self::T { + unimplemented!() + } + + fn x(&self) -> Self::T { + unimplemented!() + } + + fn y(&self) -> Self::T { + unimplemented!() + } +} diff --git a/geo-traits/src/dimension.rs b/geo-traits/src/dimension.rs new file mode 100644 index 0000000000..d7eee3a01a --- /dev/null +++ b/geo-traits/src/dimension.rs @@ -0,0 +1,33 @@ +/// The logical dimension of the geometry. +/// +/// +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum Dimensions { + /// A two-dimensional geometry with X and Y values + Xy, + + /// A three-dimensional geometry with X, Y, and Z values + Xyz, + + /// A three-dimensional geometry with X, Y, and M values + Xym, + + /// A four-dimensional geometry with X, Y, Z, and M values + Xyzm, + + /// A geometry with unknown logical type. The contained `usize` value represents the number of + /// physical dimensions. + Unknown(usize), +} + +impl Dimensions { + /// The physical number of dimensions in this geometry. + pub fn size(&self) -> usize { + match self { + Self::Xy => 2, + Self::Xyz | Self::Xym => 3, + Self::Xyzm => 4, + Self::Unknown(val) => *val, + } + } +} diff --git a/geo-traits/src/geometry.rs b/geo-traits/src/geometry.rs new file mode 100644 index 0000000000..4e46d15b8c --- /dev/null +++ b/geo-traits/src/geometry.rs @@ -0,0 +1,308 @@ +use geo_types::{ + CoordNum, Geometry, GeometryCollection, Line, LineString, MultiLineString, MultiPoint, + MultiPolygon, Point, Polygon, Rect, Triangle, +}; + +use crate::{ + Dimensions, GeometryCollectionTrait, LineStringTrait, LineTrait, MultiLineStringTrait, + MultiPointTrait, MultiPolygonTrait, PointTrait, PolygonTrait, RectTrait, TriangleTrait, +}; + +/// A trait for accessing data from a generic Geometry. +#[allow(clippy::type_complexity)] +pub trait GeometryTrait { + /// The coordinate type of this geometry + type T: CoordNum; + + /// The type of each underlying Point, which implements [PointTrait] + type PointType<'a>: 'a + PointTrait + where + Self: 'a; + + /// The type of each underlying LineString, which implements [LineStringTrait] + type LineStringType<'a>: 'a + LineStringTrait + where + Self: 'a; + + /// The type of each underlying Polygon, which implements [PolygonTrait] + type PolygonType<'a>: 'a + PolygonTrait + where + Self: 'a; + + /// The type of each underlying MultiPoint, which implements [MultiPointTrait] + type MultiPointType<'a>: 'a + MultiPointTrait + where + Self: 'a; + + /// The type of each underlying MultiLineString, which implements [MultiLineStringTrait] + type MultiLineStringType<'a>: 'a + MultiLineStringTrait + where + Self: 'a; + + /// The type of each underlying MultiPolygon, which implements [MultiPolygonTrait] + type MultiPolygonType<'a>: 'a + MultiPolygonTrait + where + Self: 'a; + + /// The type of each underlying GeometryCollection, which implements [GeometryCollectionTrait] + type GeometryCollectionType<'a>: 'a + GeometryCollectionTrait + where + Self: 'a; + + /// The type of each underlying Rect, which implements [RectTrait] + type RectType<'a>: 'a + RectTrait + where + Self: 'a; + + /// The type of each underlying Triangle, which implements [TriangleTrait] + type TriangleType<'a>: 'a + TriangleTrait + where + Self: 'a; + + /// The type of each underlying Line, which implements [LineTrait] + type LineType<'a>: 'a + LineTrait + where + Self: 'a; + + /// The dimension of this geometry + fn dim(&self) -> Dimensions; + + /// Cast this geometry to a [`GeometryType`] enum, which allows for downcasting to a specific + /// type + fn as_type( + &self, + ) -> GeometryType< + '_, + Self::PointType<'_>, + Self::LineStringType<'_>, + Self::PolygonType<'_>, + Self::MultiPointType<'_>, + Self::MultiLineStringType<'_>, + Self::MultiPolygonType<'_>, + Self::GeometryCollectionType<'_>, + Self::RectType<'_>, + Self::TriangleType<'_>, + Self::LineType<'_>, + >; +} + +/// An enumeration of all geometry types that can be contained inside a [GeometryTrait]. This is +/// used for extracting concrete geometry types out of a [GeometryTrait]. +#[derive(Debug)] +pub enum GeometryType<'a, P, LS, Y, MP, ML, MY, GC, R, T, L> +where + P: PointTrait, + LS: LineStringTrait, + Y: PolygonTrait, + MP: MultiPointTrait, + ML: MultiLineStringTrait, + MY: MultiPolygonTrait, + GC: GeometryCollectionTrait, + R: RectTrait, + T: TriangleTrait, + L: LineTrait, +{ + /// A Point, which implements [PointTrait] + Point(&'a P), + /// A LineString, which implements [LineStringTrait] + LineString(&'a LS), + /// A Polygon, which implements [PolygonTrait] + Polygon(&'a Y), + /// A MultiPoint, which implements [MultiPointTrait] + MultiPoint(&'a MP), + /// A MultiLineString, which implements [MultiLineStringTrait] + MultiLineString(&'a ML), + /// A MultiPolygon, which implements [MultiPolygonTrait] + MultiPolygon(&'a MY), + /// A GeometryCollection, which implements [GeometryCollectionTrait] + GeometryCollection(&'a GC), + /// A Rect, which implements [RectTrait] + Rect(&'a R), + /// A Triangle, which implements [TriangleTrait] + Triangle(&'a T), + /// A Line, which implements [LineTrait] + Line(&'a L), +} + +impl<'a, T: CoordNum + 'a> GeometryTrait for Geometry { + type T = T; + type PointType<'b> = Point where Self: 'b; + type LineStringType<'b> = LineString where Self: 'b; + type PolygonType<'b> = Polygon where Self: 'b; + type MultiPointType<'b> = MultiPoint where Self: 'b; + type MultiLineStringType<'b> = MultiLineString where Self: 'b; + type MultiPolygonType<'b> = MultiPolygon where Self: 'b; + type GeometryCollectionType<'b> = GeometryCollection where Self: 'b; + type RectType<'b> = Rect where Self: 'b; + type TriangleType<'b> = Triangle where Self: 'b; + type LineType<'b> = Line where Self: 'b; + + fn dim(&self) -> Dimensions { + Dimensions::Xy + } + + fn as_type( + &self, + ) -> GeometryType< + '_, + Point, + LineString, + Polygon, + MultiPoint, + MultiLineString, + MultiPolygon, + GeometryCollection, + Rect, + Triangle, + Line, + > { + match self { + Geometry::Point(p) => GeometryType::Point(p), + Geometry::LineString(p) => GeometryType::LineString(p), + Geometry::Polygon(p) => GeometryType::Polygon(p), + Geometry::MultiPoint(p) => GeometryType::MultiPoint(p), + Geometry::MultiLineString(p) => GeometryType::MultiLineString(p), + Geometry::MultiPolygon(p) => GeometryType::MultiPolygon(p), + Geometry::GeometryCollection(p) => GeometryType::GeometryCollection(p), + Geometry::Rect(p) => GeometryType::Rect(p), + Geometry::Triangle(p) => GeometryType::Triangle(p), + Geometry::Line(p) => GeometryType::Line(p), + } + } +} + +impl<'a, T: CoordNum + 'a> GeometryTrait for &'a Geometry { + type T = T; + type PointType<'b> = Point where Self: 'b; + type LineStringType<'b> = LineString where Self: 'b; + type PolygonType<'b> = Polygon where Self: 'b; + type MultiPointType<'b> = MultiPoint where Self: 'b; + type MultiLineStringType<'b> = MultiLineString where Self: 'b; + type MultiPolygonType<'b> = MultiPolygon where Self: 'b; + type GeometryCollectionType<'b> = GeometryCollection where Self: 'b; + type RectType<'b> = Rect where Self: 'b; + type TriangleType<'b> = Triangle where Self: 'b; + type LineType<'b> = Line where Self: 'b; + + fn dim(&self) -> Dimensions { + Dimensions::Xy + } + + fn as_type( + &self, + ) -> GeometryType< + '_, + Point, + LineString, + Polygon, + MultiPoint, + MultiLineString, + MultiPolygon, + GeometryCollection, + Rect, + Triangle, + Line, + > { + match self { + Geometry::Point(p) => GeometryType::Point(p), + Geometry::LineString(p) => GeometryType::LineString(p), + Geometry::Polygon(p) => GeometryType::Polygon(p), + Geometry::MultiPoint(p) => GeometryType::MultiPoint(p), + Geometry::MultiLineString(p) => GeometryType::MultiLineString(p), + Geometry::MultiPolygon(p) => GeometryType::MultiPolygon(p), + Geometry::GeometryCollection(p) => GeometryType::GeometryCollection(p), + Geometry::Rect(p) => GeometryType::Rect(p), + Geometry::Triangle(p) => GeometryType::Triangle(p), + Geometry::Line(p) => GeometryType::Line(p), + } + } +} + +// Specialized implementations on each geo-types concrete type. + +macro_rules! impl_specialization { + ($geometry_type:ident) => { + impl GeometryTrait for $geometry_type { + type T = T; + type PointType<'b> = Point where Self: 'b; + type LineStringType<'b> = LineString where Self: 'b; + type PolygonType<'b> = Polygon where Self: 'b; + type MultiPointType<'b> = MultiPoint where Self: 'b; + type MultiLineStringType<'b> = MultiLineString where Self: 'b; + type MultiPolygonType<'b> = MultiPolygon where Self: 'b; + type GeometryCollectionType<'b> = GeometryCollection where Self: 'b; + type RectType<'b> = Rect where Self: 'b; + type TriangleType<'b> = Triangle where Self: 'b; + type LineType<'b> = Line where Self: 'b; + + fn dim(&self) -> Dimensions { + Dimensions::Xy + } + + fn as_type( + &self, + ) -> GeometryType< + '_, + Point, + LineString, + Polygon, + MultiPoint, + MultiLineString, + MultiPolygon, + GeometryCollection, + Rect, + Triangle, + Line, + > { + GeometryType::$geometry_type(self) + } + } + + impl<'a, T: CoordNum + 'a> GeometryTrait for &'a $geometry_type { + type T = T; + type PointType<'b> = Point where Self: 'b; + type LineStringType<'b> = LineString where Self: 'b; + type PolygonType<'b> = Polygon where Self: 'b; + type MultiPointType<'b> = MultiPoint where Self: 'b; + type MultiLineStringType<'b> = MultiLineString where Self: 'b; + type MultiPolygonType<'b> = MultiPolygon where Self: 'b; + type GeometryCollectionType<'b> = GeometryCollection where Self: 'b; + type RectType<'b> = Rect where Self: 'b; + type TriangleType<'b> = Triangle where Self: 'b; + type LineType<'b> = Line where Self: 'b; + + fn dim(&self) -> Dimensions { + Dimensions::Xy + } + + fn as_type( + &self, + ) -> GeometryType< + '_, + Point, + LineString, + Polygon, + MultiPoint, + MultiLineString, + MultiPolygon, + GeometryCollection, + Rect, + Triangle, + Line, + > { + GeometryType::$geometry_type(self) + } + } + }; +} + +impl_specialization!(Point); +impl_specialization!(LineString); +impl_specialization!(Polygon); +impl_specialization!(MultiPoint); +impl_specialization!(MultiLineString); +impl_specialization!(MultiPolygon); +impl_specialization!(GeometryCollection); +impl_specialization!(Rect); +impl_specialization!(Triangle); +impl_specialization!(Line); diff --git a/geo-traits/src/geometry_collection.rs b/geo-traits/src/geometry_collection.rs new file mode 100644 index 0000000000..997bfa54ad --- /dev/null +++ b/geo-traits/src/geometry_collection.rs @@ -0,0 +1,83 @@ +use crate::iterator::GeometryCollectionIterator; +use crate::{Dimensions, GeometryTrait}; +use geo_types::{CoordNum, Geometry, GeometryCollection}; + +/// A trait for accessing data from a generic GeometryCollection. +/// +/// A GeometryCollection is a collection of [Geometry][GeometryTrait] types. +pub trait GeometryCollectionTrait: Sized { + /// The coordinate type of this geometry + type T: CoordNum; + + /// The type of each underlying geometry, which implements [GeometryTrait] + type GeometryType<'a>: 'a + GeometryTrait + where + Self: 'a; + + /// The dimension of this geometry + fn dim(&self) -> Dimensions; + + /// An iterator over the geometries in this GeometryCollection + fn geometries( + &self, + ) -> impl DoubleEndedIterator + ExactSizeIterator> { + GeometryCollectionIterator::new(self, 0, self.num_geometries()) + } + + /// The number of geometries in this GeometryCollection + fn num_geometries(&self) -> usize; + + /// Access to a specified geometry in this GeometryCollection + /// Will return None if the provided index is out of bounds + fn geometry(&self, i: usize) -> Option> { + if i >= self.num_geometries() { + None + } else { + unsafe { Some(self.geometry_unchecked(i)) } + } + } + + /// Access to a specified geometry in this GeometryCollection + /// + /// # Safety + /// + /// Accessing an index out of bounds is UB. + unsafe fn geometry_unchecked(&self, i: usize) -> Self::GeometryType<'_>; +} + +impl GeometryCollectionTrait for GeometryCollection { + type T = T; + type GeometryType<'a> = &'a Geometry + where + Self: 'a; + + fn dim(&self) -> Dimensions { + Dimensions::Xy + } + + fn num_geometries(&self) -> usize { + self.0.len() + } + + unsafe fn geometry_unchecked(&self, i: usize) -> Self::GeometryType<'_> { + self.0.get_unchecked(i) + } +} + +impl<'a, T: CoordNum> GeometryCollectionTrait for &'a GeometryCollection { + type T = T; + type GeometryType<'b> = &'a Geometry where + Self: 'b; + + fn dim(&self) -> Dimensions { + Dimensions::Xy + } + + fn num_geometries(&self) -> usize { + self.0.len() + } + + unsafe fn geometry_unchecked(&self, i: usize) -> Self::GeometryType<'_> { + self.0.get_unchecked(i) + } +} diff --git a/geo-traits/src/iterator.rs b/geo-traits/src/iterator.rs new file mode 100644 index 0000000000..1edc8b8187 --- /dev/null +++ b/geo-traits/src/iterator.rs @@ -0,0 +1,131 @@ +use crate::CoordTrait; + +use super::{ + GeometryCollectionTrait, GeometryTrait, LineStringTrait, MultiLineStringTrait, MultiPointTrait, + MultiPolygonTrait, PointTrait, PolygonTrait, +}; +use geo_types::CoordNum; + +macro_rules! impl_iterator { + ($struct_name:ident, $self_trait:ident, $item_trait:ident, $access_method:ident, $item_type:ident) => { + /// An iterator over the parts of this geometry. + pub(crate) struct $struct_name< + 'a, + T: CoordNum, + $item_type: 'a + $item_trait, + G: $self_trait = $item_type>, + > { + geom: &'a G, + index: usize, + end: usize, + } + + impl< + 'a, + T: CoordNum, + $item_type: 'a + $item_trait, + G: $self_trait = $item_type>, + > $struct_name<'a, T, $item_type, G> + { + /// Create a new iterator + pub fn new(geom: &'a G, index: usize, end: usize) -> Self { + Self { geom, index, end } + } + } + + impl< + 'a, + T: CoordNum, + $item_type: 'a + $item_trait, + G: $self_trait = $item_type>, + > Iterator for $struct_name<'a, T, $item_type, G> + { + type Item = $item_type; + + #[inline] + fn next(&mut self) -> Option { + if self.index == self.end { + return None; + } + let old = self.index; + self.index += 1; + unsafe { Some(self.geom.$access_method(old)) } + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + (self.end - self.index, Some(self.end - self.index)) + } + } + + impl< + 'a, + T: CoordNum, + $item_type: 'a + $item_trait, + G: $self_trait = $item_type>, + > ExactSizeIterator for $struct_name<'a, T, $item_type, G> + { + } + + impl< + 'a, + T: CoordNum, + $item_type: 'a + $item_trait, + G: $self_trait = $item_type>, + > DoubleEndedIterator for $struct_name<'a, T, $item_type, G> + { + #[inline] + fn next_back(&mut self) -> Option { + if self.index == self.end { + None + } else { + self.end -= 1; + unsafe { Some(self.geom.$access_method(self.end)) } + } + } + } + }; +} + +impl_iterator!( + LineStringIterator, + LineStringTrait, + CoordTrait, + coord_unchecked, + CoordType +); +impl_iterator!( + PolygonInteriorIterator, + PolygonTrait, + LineStringTrait, + interior_unchecked, + RingType +); +impl_iterator!( + MultiPointIterator, + MultiPointTrait, + PointTrait, + point_unchecked, + PointType +); +impl_iterator!( + MultiLineStringIterator, + MultiLineStringTrait, + LineStringTrait, + line_string_unchecked, + LineStringType +); +impl_iterator!( + MultiPolygonIterator, + MultiPolygonTrait, + PolygonTrait, + polygon_unchecked, + PolygonType +); +impl_iterator!( + GeometryCollectionIterator, + GeometryCollectionTrait, + GeometryTrait, + geometry_unchecked, + GeometryType +); diff --git a/geo-traits/src/lib.rs b/geo-traits/src/lib.rs new file mode 100644 index 0000000000..de24bd2638 --- /dev/null +++ b/geo-traits/src/lib.rs @@ -0,0 +1,47 @@ +//! A trait-based interface for geospatial vector data interchange in Rust. +//! +//! This crate contains a set of traits based on the Simple Features standard for geospatial vector +//! data. These traits are designed to make it easy to operate on and consume geometries throughout +//! the Rust ecosystem without knowing library-specific APIs or memory layouts. +//! +//! It is expected that accessing any individual coordinate or value from a geometry is +//! **constant-time**. This means that when implementing these traits on a format like WKB that +//! requires linear-time search to locate coordinates, the WKB wrapper should have already +//! undergone an initial pass to find the relevant byte offsets where coordinate sequences start +//! and end. +//! +//! This interface will usually but not always be zero-copy. Coordinate access is expected to be +//! constant-time but not necessarily _free_. For example, WKB is not aligned and may use a +//! different endianness than the current machine, so individual values may need to be cloned on +//! read. + +#![deny(missing_docs)] + +pub use coord::{CoordTrait, UnimplementedCoord}; +pub use dimension::Dimensions; +pub use geometry::{GeometryTrait, GeometryType}; +pub use geometry_collection::GeometryCollectionTrait; +pub use line::{LineTrait, UnimplementedLine}; +pub use line_string::{LineStringTrait, UnimplementedLineString}; +pub use multi_line_string::{MultiLineStringTrait, UnimplementedMultiLineString}; +pub use multi_point::{MultiPointTrait, UnimplementedMultiPoint}; +pub use multi_polygon::{MultiPolygonTrait, UnimplementedMultiPolygon}; +pub use point::{PointTrait, UnimplementedPoint}; +pub use polygon::{PolygonTrait, UnimplementedPolygon}; +pub use rect::{RectTrait, UnimplementedRect}; +pub use triangle::{TriangleTrait, UnimplementedTriangle}; + +mod coord; +mod dimension; +mod geometry; +mod geometry_collection; +mod iterator; +mod line; +mod line_string; +mod multi_line_string; +mod multi_point; +mod multi_polygon; +mod point; +mod polygon; +mod rect; +mod triangle; diff --git a/geo-traits/src/line.rs b/geo-traits/src/line.rs new file mode 100644 index 0000000000..b67b573ef2 --- /dev/null +++ b/geo-traits/src/line.rs @@ -0,0 +1,90 @@ +use std::marker::PhantomData; + +use crate::{CoordTrait, Dimensions, UnimplementedCoord}; +use geo_types::{Coord, CoordNum, Line}; + +/// A trait for accessing data from a generic Line. +/// +/// A Line is a line segment made up of exactly two [coordinates][CoordTrait]. +/// +/// Refer to [geo_types::Line] for information about semantics and validity. +pub trait LineTrait: Sized { + /// The coordinate type of this geometry + type T: CoordNum; + + /// The type of each underlying coordinate, which implements [CoordTrait] + type CoordType<'a>: 'a + CoordTrait + where + Self: 'a; + + /// The dimension of this geometry + fn dim(&self) -> Dimensions; + + /// Access the start coordinate in this Line + fn start(&self) -> Self::CoordType<'_>; + + /// Access the start coordinate in this Line + fn end(&self) -> Self::CoordType<'_>; + + /// Access the two underlying coordinates + fn coords(&self) -> [Self::CoordType<'_>; 2] { + [self.start(), self.end()] + } +} + +impl LineTrait for Line { + type T = T; + type CoordType<'a> = &'a Coord where Self: 'a; + + fn dim(&self) -> Dimensions { + Dimensions::Xy + } + + fn start(&self) -> Self::CoordType<'_> { + &self.start + } + + fn end(&self) -> Self::CoordType<'_> { + &self.end + } +} + +impl<'a, T: CoordNum> LineTrait for &'a Line { + type T = T; + type CoordType<'b> = &'a Coord where Self: 'b; + + fn dim(&self) -> Dimensions { + Dimensions::Xy + } + + fn start(&self) -> Self::CoordType<'_> { + &self.start + } + + fn end(&self) -> Self::CoordType<'_> { + &self.end + } +} + +/// An empty struct that implements [LineTrait]. +/// +/// This can be used as the `LineType` of the `GeometryTrait` by implementations that don't +/// have a Line concept +pub struct UnimplementedLine(PhantomData); + +impl LineTrait for UnimplementedLine { + type T = T; + type CoordType<'a> = UnimplementedCoord where Self: 'a; + + fn dim(&self) -> Dimensions { + unimplemented!() + } + + fn start(&self) -> Self::CoordType<'_> { + unimplemented!() + } + + fn end(&self) -> Self::CoordType<'_> { + unimplemented!() + } +} diff --git a/geo-traits/src/line_string.rs b/geo-traits/src/line_string.rs new file mode 100644 index 0000000000..28eb9b3ca0 --- /dev/null +++ b/geo-traits/src/line_string.rs @@ -0,0 +1,107 @@ +use std::marker::PhantomData; + +use crate::iterator::LineStringIterator; +use crate::{CoordTrait, Dimensions, UnimplementedCoord}; +use geo_types::{Coord, CoordNum, LineString}; + +/// A trait for accessing data from a generic LineString. +/// +/// A LineString is an ordered collection of two or more [points][CoordTrait], representing a path +/// between locations. +/// +/// Refer to [geo_types::LineString] for information about semantics and validity. +pub trait LineStringTrait: Sized { + /// The coordinate type of this geometry + type T: CoordNum; + + /// The type of each underlying coordinate, which implements [CoordTrait] + type CoordType<'a>: 'a + CoordTrait + where + Self: 'a; + + /// The dimension of this geometry + fn dim(&self) -> Dimensions; + + /// An iterator over the coordinates in this LineString + fn coords(&self) -> impl DoubleEndedIterator + ExactSizeIterator> { + LineStringIterator::new(self, 0, self.num_coords()) + } + + /// The number of coordinates in this LineString + fn num_coords(&self) -> usize; + + /// Access to a specified coordinate in this LineString + /// Will return None if the provided index is out of bounds + #[inline] + fn coord(&self, i: usize) -> Option> { + if i >= self.num_coords() { + None + } else { + unsafe { Some(self.coord_unchecked(i)) } + } + } + + /// Access to a specified coordinate in this LineString + /// + /// # Safety + /// + /// Accessing an index out of bounds is UB. + unsafe fn coord_unchecked(&self, i: usize) -> Self::CoordType<'_>; +} + +impl LineStringTrait for LineString { + type T = T; + type CoordType<'a> = &'a Coord where Self: 'a; + + fn dim(&self) -> Dimensions { + Dimensions::Xy + } + + fn num_coords(&self) -> usize { + self.0.len() + } + + unsafe fn coord_unchecked(&self, i: usize) -> Self::CoordType<'_> { + self.0.get_unchecked(i) + } +} + +impl<'a, T: CoordNum> LineStringTrait for &'a LineString { + type T = T; + type CoordType<'b> = &'a Coord where Self: 'b; + + fn dim(&self) -> Dimensions { + Dimensions::Xy + } + + fn num_coords(&self) -> usize { + self.0.len() + } + + unsafe fn coord_unchecked(&self, i: usize) -> Self::CoordType<'_> { + self.0.get_unchecked(i) + } +} + +/// An empty struct that implements [LineStringTrait]. +/// +/// This can be used as the `LineStringType` of the `GeometryTrait` by implementations that don't +/// have a LineString concept +pub struct UnimplementedLineString(PhantomData); + +impl LineStringTrait for UnimplementedLineString { + type T = T; + type CoordType<'a> = UnimplementedCoord where Self: 'a; + + fn dim(&self) -> Dimensions { + unimplemented!() + } + + fn num_coords(&self) -> usize { + unimplemented!() + } + + unsafe fn coord_unchecked(&self, _i: usize) -> Self::CoordType<'_> { + unimplemented!() + } +} diff --git a/geo-traits/src/multi_line_string.rs b/geo-traits/src/multi_line_string.rs new file mode 100644 index 0000000000..7e946ff440 --- /dev/null +++ b/geo-traits/src/multi_line_string.rs @@ -0,0 +1,108 @@ +use std::marker::PhantomData; + +use crate::iterator::MultiLineStringIterator; +use crate::line_string::UnimplementedLineString; +use crate::{Dimensions, LineStringTrait}; +use geo_types::{CoordNum, LineString, MultiLineString}; + +/// A trait for accessing data from a generic MultiLineString. +/// +/// A MultiLineString is a collection of [`LineString`s][LineStringTrait]. +/// +/// Refer to [geo_types::MultiLineString] for information about semantics and validity. +pub trait MultiLineStringTrait: Sized { + /// The coordinate type of this geometry + type T: CoordNum; + + /// The type of each underlying LineString, which implements [LineStringTrait] + type LineStringType<'a>: 'a + LineStringTrait + where + Self: 'a; + + /// The dimension of this geometry + fn dim(&self) -> Dimensions; + + /// An iterator over the LineStrings in this MultiLineString + fn line_strings( + &self, + ) -> impl DoubleEndedIterator + ExactSizeIterator> { + MultiLineStringIterator::new(self, 0, self.num_line_strings()) + } + + /// The number of line_strings in this MultiLineString + fn num_line_strings(&self) -> usize; + + /// Access to a specified line_string in this MultiLineString + /// Will return None if the provided index is out of bounds + fn line_string(&self, i: usize) -> Option> { + if i >= self.num_line_strings() { + None + } else { + unsafe { Some(self.line_string_unchecked(i)) } + } + } + + /// Access to a specified line_string in this MultiLineString + /// + /// # Safety + /// + /// Accessing an index out of bounds is UB. + unsafe fn line_string_unchecked(&self, i: usize) -> Self::LineStringType<'_>; +} + +impl MultiLineStringTrait for MultiLineString { + type T = T; + type LineStringType<'a> = &'a LineString where Self: 'a; + + fn dim(&self) -> Dimensions { + Dimensions::Xy + } + + fn num_line_strings(&self) -> usize { + self.0.len() + } + + unsafe fn line_string_unchecked(&self, i: usize) -> Self::LineStringType<'_> { + self.0.get_unchecked(i) + } +} + +impl<'a, T: CoordNum> MultiLineStringTrait for &'a MultiLineString { + type T = T; + type LineStringType<'b> = &'a LineString where Self: 'b; + + fn dim(&self) -> Dimensions { + Dimensions::Xy + } + + fn num_line_strings(&self) -> usize { + self.0.len() + } + + unsafe fn line_string_unchecked(&self, i: usize) -> Self::LineStringType<'_> { + self.0.get_unchecked(i) + } +} + +/// An empty struct that implements [MultiLineStringTrait]. +/// +/// This can be used as the `MultiLineStringType` of the `GeometryTrait` by implementations that +/// don't have a MultiLineString concept +pub struct UnimplementedMultiLineString(PhantomData); + +impl MultiLineStringTrait for UnimplementedMultiLineString { + type T = T; + type LineStringType<'a> = UnimplementedLineString where Self: 'a; + + fn dim(&self) -> Dimensions { + unimplemented!() + } + + fn num_line_strings(&self) -> usize { + unimplemented!() + } + + unsafe fn line_string_unchecked(&self, _i: usize) -> Self::LineStringType<'_> { + unimplemented!() + } +} diff --git a/geo-traits/src/multi_point.rs b/geo-traits/src/multi_point.rs new file mode 100644 index 0000000000..0d8c711e62 --- /dev/null +++ b/geo-traits/src/multi_point.rs @@ -0,0 +1,105 @@ +use std::marker::PhantomData; + +use crate::iterator::MultiPointIterator; +use crate::{Dimensions, PointTrait, UnimplementedPoint}; +use geo_types::{CoordNum, MultiPoint, Point}; + +/// A trait for accessing data from a generic MultiPoint. +/// +/// A MultiPoint is a collection of [`Point`s][PointTrait]. +/// +/// Refer to [geo_types::MultiPoint] for information about semantics and validity. +pub trait MultiPointTrait: Sized { + /// The coordinate type of this geometry + type T: CoordNum; + + /// The type of each underlying Point, which implements [PointTrait] + type PointType<'a>: 'a + PointTrait + where + Self: 'a; + + /// The dimension of this geometry + fn dim(&self) -> Dimensions; + + /// An iterator over the points in this MultiPoint + fn points(&self) -> impl DoubleEndedIterator + ExactSizeIterator> { + MultiPointIterator::new(self, 0, self.num_points()) + } + + /// The number of points in this MultiPoint + fn num_points(&self) -> usize; + + /// Access to a specified point in this MultiPoint + /// Will return None if the provided index is out of bounds + fn point(&self, i: usize) -> Option> { + if i >= self.num_points() { + None + } else { + unsafe { Some(self.point_unchecked(i)) } + } + } + + /// Access to a specified point in this MultiPoint + /// + /// # Safety + /// + /// Accessing an index out of bounds is UB. + unsafe fn point_unchecked(&self, i: usize) -> Self::PointType<'_>; +} + +impl MultiPointTrait for MultiPoint { + type T = T; + type PointType<'a> = &'a Point where Self: 'a; + + fn dim(&self) -> Dimensions { + Dimensions::Xy + } + + fn num_points(&self) -> usize { + self.0.len() + } + + unsafe fn point_unchecked(&self, i: usize) -> Self::PointType<'_> { + self.0.get_unchecked(i) + } +} + +impl<'a, T: CoordNum> MultiPointTrait for &'a MultiPoint { + type T = T; + type PointType<'b> = &'a Point where Self: 'b; + + fn dim(&self) -> Dimensions { + Dimensions::Xy + } + + fn num_points(&self) -> usize { + self.0.len() + } + + unsafe fn point_unchecked(&self, i: usize) -> Self::PointType<'_> { + self.0.get_unchecked(i) + } +} + +/// An empty struct that implements [MultiPointTrait]. +/// +/// This can be used as the `MultiPointType` of the `GeometryTrait` by implementations that don't +/// have a MultiPoint concept +pub struct UnimplementedMultiPoint(PhantomData); + +impl MultiPointTrait for UnimplementedMultiPoint { + type T = T; + type PointType<'a> = UnimplementedPoint where Self: 'a; + + fn dim(&self) -> Dimensions { + unimplemented!() + } + + fn num_points(&self) -> usize { + unimplemented!() + } + + unsafe fn point_unchecked(&self, _i: usize) -> Self::PointType<'_> { + unimplemented!() + } +} diff --git a/geo-traits/src/multi_polygon.rs b/geo-traits/src/multi_polygon.rs new file mode 100644 index 0000000000..fc559bd230 --- /dev/null +++ b/geo-traits/src/multi_polygon.rs @@ -0,0 +1,106 @@ +use std::marker::PhantomData; + +use crate::iterator::MultiPolygonIterator; +use crate::polygon::UnimplementedPolygon; +use crate::{Dimensions, PolygonTrait}; +use geo_types::{CoordNum, MultiPolygon, Polygon}; + +/// A trait for accessing data from a generic MultiPolygon. +/// +/// Refer to [geo_types::MultiPolygon] for information about semantics and validity. +pub trait MultiPolygonTrait: Sized { + /// The coordinate type of this geometry + type T: CoordNum; + + /// The type of each underlying Polygon, which implements [PolygonTrait] + type PolygonType<'a>: 'a + PolygonTrait + where + Self: 'a; + + /// The dimension of this geometry + fn dim(&self) -> Dimensions; + + /// An iterator over the Polygons in this MultiPolygon + fn polygons( + &self, + ) -> impl DoubleEndedIterator + ExactSizeIterator> { + MultiPolygonIterator::new(self, 0, self.num_polygons()) + } + + /// The number of polygons in this MultiPolygon + fn num_polygons(&self) -> usize; + + /// Access to a specified polygon in this MultiPolygon + /// Will return None if the provided index is out of bounds + fn polygon(&self, i: usize) -> Option> { + if i >= self.num_polygons() { + None + } else { + unsafe { Some(self.polygon_unchecked(i)) } + } + } + + /// Access to a specified polygon in this MultiPolygon + /// + /// # Safety + /// + /// Accessing an index out of bounds is UB. + unsafe fn polygon_unchecked(&self, i: usize) -> Self::PolygonType<'_>; +} + +impl MultiPolygonTrait for MultiPolygon { + type T = T; + type PolygonType<'a> = &'a Polygon where Self: 'a; + + fn dim(&self) -> Dimensions { + Dimensions::Xy + } + + fn num_polygons(&self) -> usize { + self.0.len() + } + + unsafe fn polygon_unchecked(&self, i: usize) -> Self::PolygonType<'_> { + self.0.get_unchecked(i) + } +} + +impl<'a, T: CoordNum> MultiPolygonTrait for &'a MultiPolygon { + type T = T; + type PolygonType<'b> = &'a Polygon where Self: 'b; + + fn dim(&self) -> Dimensions { + Dimensions::Xy + } + + fn num_polygons(&self) -> usize { + self.0.len() + } + + unsafe fn polygon_unchecked(&self, i: usize) -> Self::PolygonType<'_> { + self.0.get_unchecked(i) + } +} + +/// An empty struct that implements [MultiPolygonTrait]. +/// +/// This can be used as the `MultiPolygonType` of the `GeometryTrait` by implementations that don't +/// have a MultiPolygon concept +pub struct UnimplementedMultiPolygon(PhantomData); + +impl MultiPolygonTrait for UnimplementedMultiPolygon { + type T = T; + type PolygonType<'a> = UnimplementedPolygon where Self: 'a; + + fn dim(&self) -> Dimensions { + unimplemented!() + } + + fn num_polygons(&self) -> usize { + unimplemented!() + } + + unsafe fn polygon_unchecked(&self, _i: usize) -> Self::PolygonType<'_> { + unimplemented!() + } +} diff --git a/geo-traits/src/point.rs b/geo-traits/src/point.rs new file mode 100644 index 0000000000..883315e507 --- /dev/null +++ b/geo-traits/src/point.rs @@ -0,0 +1,78 @@ +use std::marker::PhantomData; + +use geo_types::{Coord, CoordNum, Point}; + +use crate::{CoordTrait, Dimensions, UnimplementedCoord}; + +/// A trait for accessing data from a generic Point. +/// +/// Refer to [geo_types::Point] for information about semantics and validity. +pub trait PointTrait { + /// The coordinate type of this geometry + type T: CoordNum; + + /// The type of the underlying coordinate, which implements [CoordTrait] + type CoordType<'a>: 'a + CoordTrait + where + Self: 'a; + + /// Dimensions of the coordinate tuple + fn dim(&self) -> Dimensions; + + /// Whether this point is `empty` or not. + /// + /// According to Simple Features, a Point can have zero coordinates and be considered `empty`. + /// + /// If `is_empty` returns `true`, then the values of `x()`, `y()`, `nth()` and `nth_unchecked` + /// have no semantic meaning. + /// + /// Only a top-level geometry can be empty. That is, when this point is contained within + /// another geometry, such as a [`LineStringTrait`][crate::LineStringTrait], those points + /// can never be empty, and a consumer does not need to check this method. + fn coord(&self) -> Option>; +} + +impl PointTrait for Point { + type T = T; + type CoordType<'a> = &'a Coord where Self: 'a; + + fn coord(&self) -> Option> { + Some(&self.0) + } + + fn dim(&self) -> Dimensions { + Dimensions::Xy + } +} + +impl PointTrait for &Point { + type T = T; + type CoordType<'a> = &'a Coord where Self: 'a; + + fn coord(&self) -> Option> { + Some(&self.0) + } + + fn dim(&self) -> Dimensions { + Dimensions::Xy + } +} + +/// An empty struct that implements [PointTrait]. +/// +/// This can be used as the `PointType` of the `GeometryTrait` by implementations that don't have a +/// Point concept +pub struct UnimplementedPoint(PhantomData); + +impl PointTrait for UnimplementedPoint { + type T = T; + type CoordType<'a> = UnimplementedCoord where Self: 'a; + + fn coord(&self) -> Option> { + unimplemented!() + } + + fn dim(&self) -> Dimensions { + unimplemented!() + } +} diff --git a/geo-traits/src/polygon.rs b/geo-traits/src/polygon.rs new file mode 100644 index 0000000000..18669e0083 --- /dev/null +++ b/geo-traits/src/polygon.rs @@ -0,0 +1,134 @@ +use std::marker::PhantomData; + +use crate::iterator::PolygonInteriorIterator; +use crate::line_string::UnimplementedLineString; +use crate::{Dimensions, LineStringTrait}; +use geo_types::{CoordNum, LineString, Polygon}; + +/// A trait for accessing data from a generic Polygon. +/// +/// A `Polygon`’s outer boundary (_exterior ring_) is represented by a +/// [`LineString`][LineStringTrait]. It may contain zero or more holes (_interior rings_), also +/// represented by `LineString`s. +/// +/// Refer to [geo_types::Polygon] for information about semantics and validity. +pub trait PolygonTrait: Sized { + /// The coordinate type of this geometry + type T: CoordNum; + + /// The type of each underlying ring, which implements [LineStringTrait] + type RingType<'a>: 'a + LineStringTrait + where + Self: 'a; + + /// The dimension of this geometry + fn dim(&self) -> Dimensions; + + /// The exterior ring of the polygon + fn exterior(&self) -> Option>; + + /// An iterator of the interior rings of this Polygon + fn interiors(&self) -> impl DoubleEndedIterator + ExactSizeIterator> { + PolygonInteriorIterator::new(self, 0, self.num_interiors()) + } + + /// The number of interior rings in this Polygon + fn num_interiors(&self) -> usize; + + /// Access to a specified interior ring in this Polygon + /// Will return None if the provided index is out of bounds + fn interior(&self, i: usize) -> Option> { + if i >= self.num_interiors() { + None + } else { + unsafe { Some(self.interior_unchecked(i)) } + } + } + + /// Access to a specified interior ring in this Polygon + /// + /// # Safety + /// + /// Accessing an index out of bounds is UB. + unsafe fn interior_unchecked(&self, i: usize) -> Self::RingType<'_>; +} + +impl PolygonTrait for Polygon { + type T = T; + type RingType<'a> = &'a LineString where Self: 'a; + + fn dim(&self) -> Dimensions { + Dimensions::Xy + } + + fn exterior(&self) -> Option> { + let ext_ring = Polygon::exterior(self); + if LineStringTrait::num_coords(&ext_ring) == 0 { + None + } else { + Some(ext_ring) + } + } + + fn num_interiors(&self) -> usize { + Polygon::interiors(self).len() + } + + unsafe fn interior_unchecked(&self, i: usize) -> Self::RingType<'_> { + unsafe { Polygon::interiors(self).get_unchecked(i) } + } +} + +impl<'a, T: CoordNum> PolygonTrait for &'a Polygon { + type T = T; + type RingType<'b> = &'a LineString where + Self: 'b; + + fn dim(&self) -> Dimensions { + Dimensions::Xy + } + + fn exterior(&self) -> Option> { + let ext_ring = Polygon::exterior(self); + if LineStringTrait::num_coords(&ext_ring) == 0 { + None + } else { + Some(ext_ring) + } + } + + fn num_interiors(&self) -> usize { + Polygon::interiors(self).len() + } + + unsafe fn interior_unchecked(&self, i: usize) -> Self::RingType<'_> { + unsafe { Polygon::interiors(self).get_unchecked(i) } + } +} + +/// An empty struct that implements [PolygonTrait]. +/// +/// This can be used as the `PolygonType` of the `GeometryTrait` by implementations that don't have a +/// Polygon concept +pub struct UnimplementedPolygon(PhantomData); + +impl PolygonTrait for UnimplementedPolygon { + type T = T; + type RingType<'a> = UnimplementedLineString where Self: 'a; + + fn dim(&self) -> Dimensions { + unimplemented!() + } + + fn exterior(&self) -> Option> { + unimplemented!() + } + + fn num_interiors(&self) -> usize { + unimplemented!() + } + + unsafe fn interior_unchecked(&self, _i: usize) -> Self::RingType<'_> { + unimplemented!() + } +} diff --git a/geo-traits/src/rect.rs b/geo-traits/src/rect.rs new file mode 100644 index 0000000000..a0b60832f1 --- /dev/null +++ b/geo-traits/src/rect.rs @@ -0,0 +1,85 @@ +use std::marker::PhantomData; + +use geo_types::{Coord, CoordNum, Rect}; + +use crate::{CoordTrait, Dimensions, UnimplementedCoord}; + +/// A trait for accessing data from a generic Rect. +/// +/// A Rect is an _axis-aligned_ bounded 2D rectangle whose area is +/// defined by minimum and maximum [`Point`s][CoordTrait]. +pub trait RectTrait { + /// The coordinate type of this geometry + type T: CoordNum; + + /// The type of each underlying coordinate, which implements [CoordTrait] + type CoordType<'a>: 'a + CoordTrait + where + Self: 'a; + + /// The dimension of this geometry + fn dim(&self) -> Dimensions; + + /// The minimum coordinate of this Rect + fn min(&self) -> Self::CoordType<'_>; + + /// The maximum coordinate of this Rect + fn max(&self) -> Self::CoordType<'_>; +} + +impl<'a, T: CoordNum + 'a> RectTrait for Rect { + type T = T; + type CoordType<'b> = Coord where Self: 'b; + + fn dim(&self) -> Dimensions { + Dimensions::Xy + } + + fn min(&self) -> Self::CoordType<'_> { + Rect::min(*self) + } + + fn max(&self) -> Self::CoordType<'_> { + Rect::max(*self) + } +} + +impl<'a, T: CoordNum + 'a> RectTrait for &'a Rect { + type T = T; + type CoordType<'b> = Coord where Self: 'b; + + fn dim(&self) -> Dimensions { + Dimensions::Xy + } + + fn min(&self) -> Self::CoordType<'_> { + Rect::min(**self) + } + + fn max(&self) -> Self::CoordType<'_> { + Rect::max(**self) + } +} + +/// An empty struct that implements [RectTrait]. +/// +/// This can be used as the `RectType` of the `GeometryTrait` by implementations that don't +/// have a Rect concept +pub struct UnimplementedRect(PhantomData); + +impl RectTrait for UnimplementedRect { + type T = T; + type CoordType<'a> = UnimplementedCoord where Self: 'a; + + fn dim(&self) -> Dimensions { + unimplemented!() + } + + fn min(&self) -> Self::CoordType<'_> { + unimplemented!() + } + + fn max(&self) -> Self::CoordType<'_> { + unimplemented!() + } +} diff --git a/geo-traits/src/triangle.rs b/geo-traits/src/triangle.rs new file mode 100644 index 0000000000..8934e4e3f8 --- /dev/null +++ b/geo-traits/src/triangle.rs @@ -0,0 +1,105 @@ +use std::marker::PhantomData; + +use crate::{CoordTrait, Dimensions, UnimplementedCoord}; +use geo_types::{Coord, CoordNum, Triangle}; + +/// A trait for accessing data from a generic Triangle. +/// +/// A triangle is a bounded area whose three vertices are defined by [coordinates][CoordTrait]. +/// +/// Refer to [geo_types::Triangle] for information about semantics and validity. +pub trait TriangleTrait: Sized { + /// The coordinate type of this geometry + type T: CoordNum; + + /// The type of each underlying coordinate, which implements [CoordTrait] + type CoordType<'a>: 'a + CoordTrait + where + Self: 'a; + + /// The dimension of this geometry + fn dim(&self) -> Dimensions; + + /// Access the first coordinate in this Triangle + fn first(&self) -> Self::CoordType<'_>; + + /// Access the second coordinate in this Triangle + fn second(&self) -> Self::CoordType<'_>; + + /// Access the third coordinate in this Triangle + fn third(&self) -> Self::CoordType<'_>; + + /// Access the three underlying coordinates + fn coords(&self) -> [Self::CoordType<'_>; 3] { + [self.first(), self.second(), self.third()] + } +} + +impl TriangleTrait for Triangle { + type T = T; + type CoordType<'a> = &'a Coord where Self: 'a; + + fn dim(&self) -> Dimensions { + Dimensions::Xy + } + + fn first(&self) -> Self::CoordType<'_> { + &self.0 + } + + fn second(&self) -> Self::CoordType<'_> { + &self.0 + } + + fn third(&self) -> Self::CoordType<'_> { + &self.0 + } +} + +impl<'a, T: CoordNum> TriangleTrait for &'a Triangle { + type T = T; + type CoordType<'b> = &'a Coord where Self: 'b; + + fn dim(&self) -> Dimensions { + Dimensions::Xy + } + + fn first(&self) -> Self::CoordType<'_> { + &self.0 + } + + fn second(&self) -> Self::CoordType<'_> { + &self.0 + } + + fn third(&self) -> Self::CoordType<'_> { + &self.0 + } +} + +/// An empty struct that implements [TriangleTrait]. +/// +/// This can be used as the `TriangleType` of the `GeometryTrait` by implementations that don't +/// have a Triangle concept +pub struct UnimplementedTriangle(PhantomData); + +impl TriangleTrait for UnimplementedTriangle { + type T = T; + type CoordType<'a> = UnimplementedCoord where Self: 'a; + + fn dim(&self) -> Dimensions { + unimplemented!() + } + + fn first(&self) -> Self::CoordType<'_> { + unimplemented!() + } + + fn second(&self) -> Self::CoordType<'_> { + unimplemented!() + } + + fn third(&self) -> Self::CoordType<'_> { + unimplemented!() + } +}