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<Self::T> {
+        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<T: CoordNum> CoordTrait for Coord<T> {
+    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<T: CoordNum> CoordTrait for &Coord<T> {
+    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<T: CoordNum> 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<T: CoordNum>(PhantomData<T>);
+
+impl<T: CoordNum> CoordTrait for UnimplementedCoord<T> {
+    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<T = Self::T>
+    where
+        Self: 'a;
+
+    /// The type of each underlying LineString, which implements [LineStringTrait]
+    type LineStringType<'a>: 'a + LineStringTrait<T = Self::T>
+    where
+        Self: 'a;
+
+    /// The type of each underlying Polygon, which implements [PolygonTrait]
+    type PolygonType<'a>: 'a + PolygonTrait<T = Self::T>
+    where
+        Self: 'a;
+
+    /// The type of each underlying MultiPoint, which implements [MultiPointTrait]
+    type MultiPointType<'a>: 'a + MultiPointTrait<T = Self::T>
+    where
+        Self: 'a;
+
+    /// The type of each underlying MultiLineString, which implements [MultiLineStringTrait]
+    type MultiLineStringType<'a>: 'a + MultiLineStringTrait<T = Self::T>
+    where
+        Self: 'a;
+
+    /// The type of each underlying MultiPolygon, which implements [MultiPolygonTrait]
+    type MultiPolygonType<'a>: 'a + MultiPolygonTrait<T = Self::T>
+    where
+        Self: 'a;
+
+    /// The type of each underlying GeometryCollection, which implements [GeometryCollectionTrait]
+    type GeometryCollectionType<'a>: 'a + GeometryCollectionTrait<T = Self::T>
+    where
+        Self: 'a;
+
+    /// The type of each underlying Rect, which implements [RectTrait]
+    type RectType<'a>: 'a + RectTrait<T = Self::T>
+    where
+        Self: 'a;
+
+    /// The type of each underlying Triangle, which implements [TriangleTrait]
+    type TriangleType<'a>: 'a + TriangleTrait<T = Self::T>
+    where
+        Self: 'a;
+
+    /// The type of each underlying Line, which implements [LineTrait]
+    type LineType<'a>: 'a + LineTrait<T = Self::T>
+    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<T> {
+    type T = T;
+    type PointType<'b> = Point<Self::T> where Self: 'b;
+    type LineStringType<'b> = LineString<Self::T> where Self: 'b;
+    type PolygonType<'b> = Polygon<Self::T> where Self: 'b;
+    type MultiPointType<'b> = MultiPoint<Self::T> where Self: 'b;
+    type MultiLineStringType<'b> = MultiLineString<Self::T> where Self: 'b;
+    type MultiPolygonType<'b> = MultiPolygon<Self::T> where Self: 'b;
+    type GeometryCollectionType<'b> = GeometryCollection<Self::T> where Self: 'b;
+    type RectType<'b> = Rect<Self::T> where Self: 'b;
+    type TriangleType<'b> = Triangle<Self::T> where Self: 'b;
+    type LineType<'b> = Line<Self::T> where Self: 'b;
+
+    fn dim(&self) -> Dimensions {
+        Dimensions::Xy
+    }
+
+    fn as_type(
+        &self,
+    ) -> GeometryType<
+        '_,
+        Point<T>,
+        LineString<T>,
+        Polygon<T>,
+        MultiPoint<T>,
+        MultiLineString<T>,
+        MultiPolygon<T>,
+        GeometryCollection<T>,
+        Rect<T>,
+        Triangle<T>,
+        Line<T>,
+    > {
+        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<T> {
+    type T = T;
+    type PointType<'b> = Point<Self::T> where Self: 'b;
+    type LineStringType<'b> = LineString<Self::T> where Self: 'b;
+    type PolygonType<'b> = Polygon<Self::T> where Self: 'b;
+    type MultiPointType<'b> = MultiPoint<Self::T> where Self: 'b;
+    type MultiLineStringType<'b> = MultiLineString<Self::T> where Self: 'b;
+    type MultiPolygonType<'b> = MultiPolygon<Self::T> where Self: 'b;
+    type GeometryCollectionType<'b> = GeometryCollection<Self::T> where Self: 'b;
+    type RectType<'b> = Rect<Self::T> where Self: 'b;
+    type TriangleType<'b> = Triangle<Self::T> where Self: 'b;
+    type LineType<'b> = Line<Self::T> where Self: 'b;
+
+    fn dim(&self) -> Dimensions {
+        Dimensions::Xy
+    }
+
+    fn as_type(
+        &self,
+    ) -> GeometryType<
+        '_,
+        Point<T>,
+        LineString<T>,
+        Polygon<T>,
+        MultiPoint<T>,
+        MultiLineString<T>,
+        MultiPolygon<T>,
+        GeometryCollection<T>,
+        Rect<T>,
+        Triangle<T>,
+        Line<T>,
+    > {
+        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<T: CoordNum> GeometryTrait for $geometry_type<T> {
+            type T = T;
+            type PointType<'b> = Point<Self::T> where Self: 'b;
+            type LineStringType<'b> = LineString<Self::T> where Self: 'b;
+            type PolygonType<'b> = Polygon<Self::T> where Self: 'b;
+            type MultiPointType<'b> = MultiPoint<Self::T> where Self: 'b;
+            type MultiLineStringType<'b> = MultiLineString<Self::T> where Self: 'b;
+            type MultiPolygonType<'b> = MultiPolygon<Self::T> where Self: 'b;
+            type GeometryCollectionType<'b> = GeometryCollection<Self::T> where Self: 'b;
+            type RectType<'b> = Rect<Self::T> where Self: 'b;
+            type TriangleType<'b> = Triangle<Self::T> where Self: 'b;
+            type LineType<'b> = Line<Self::T> where Self: 'b;
+
+            fn dim(&self) -> Dimensions {
+                Dimensions::Xy
+            }
+
+            fn as_type(
+                &self,
+            ) -> GeometryType<
+                '_,
+                Point<T>,
+                LineString<T>,
+                Polygon<T>,
+                MultiPoint<T>,
+                MultiLineString<T>,
+                MultiPolygon<T>,
+                GeometryCollection<T>,
+                Rect<T>,
+                Triangle<T>,
+                Line<T>,
+            > {
+                GeometryType::$geometry_type(self)
+            }
+        }
+
+        impl<'a, T: CoordNum + 'a> GeometryTrait for &'a $geometry_type<T> {
+            type T = T;
+            type PointType<'b> = Point<Self::T> where Self: 'b;
+            type LineStringType<'b> = LineString<Self::T> where Self: 'b;
+            type PolygonType<'b> = Polygon<Self::T> where Self: 'b;
+            type MultiPointType<'b> = MultiPoint<Self::T> where Self: 'b;
+            type MultiLineStringType<'b> = MultiLineString<Self::T> where Self: 'b;
+            type MultiPolygonType<'b> = MultiPolygon<Self::T> where Self: 'b;
+            type GeometryCollectionType<'b> = GeometryCollection<Self::T> where Self: 'b;
+            type RectType<'b> = Rect<Self::T> where Self: 'b;
+            type TriangleType<'b> = Triangle<Self::T> where Self: 'b;
+            type LineType<'b> = Line<Self::T> where Self: 'b;
+
+            fn dim(&self) -> Dimensions {
+                Dimensions::Xy
+            }
+
+            fn as_type(
+                &self,
+            ) -> GeometryType<
+                '_,
+                Point<T>,
+                LineString<T>,
+                Polygon<T>,
+                MultiPoint<T>,
+                MultiLineString<T>,
+                MultiPolygon<T>,
+                GeometryCollection<T>,
+                Rect<T>,
+                Triangle<T>,
+                Line<T>,
+            > {
+                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<T = Self::T>
+    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<Item = Self::GeometryType<'_>> {
+        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<Self::GeometryType<'_>> {
+        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<T: CoordNum> GeometryCollectionTrait for GeometryCollection<T> {
+    type T = T;
+    type GeometryType<'a> = &'a Geometry<Self::T>
+    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<T> {
+    type T = T;
+    type GeometryType<'b> = &'a Geometry<Self::T> 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<T = T>,
+            G: $self_trait<T = T, $item_type<'a> = $item_type>,
+        > {
+            geom: &'a G,
+            index: usize,
+            end: usize,
+        }
+
+        impl<
+                'a,
+                T: CoordNum,
+                $item_type: 'a + $item_trait<T = T>,
+                G: $self_trait<T = T, $item_type<'a> = $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<T = T>,
+                G: $self_trait<T = T, $item_type<'a> = $item_type>,
+            > Iterator for $struct_name<'a, T, $item_type, G>
+        {
+            type Item = $item_type;
+
+            #[inline]
+            fn next(&mut self) -> Option<Self::Item> {
+                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<usize>) {
+                (self.end - self.index, Some(self.end - self.index))
+            }
+        }
+
+        impl<
+                'a,
+                T: CoordNum,
+                $item_type: 'a + $item_trait<T = T>,
+                G: $self_trait<T = T, $item_type<'a> = $item_type>,
+            > ExactSizeIterator for $struct_name<'a, T, $item_type, G>
+        {
+        }
+
+        impl<
+                'a,
+                T: CoordNum,
+                $item_type: 'a + $item_trait<T = T>,
+                G: $self_trait<T = T, $item_type<'a> = $item_type>,
+            > DoubleEndedIterator for $struct_name<'a, T, $item_type, G>
+        {
+            #[inline]
+            fn next_back(&mut self) -> Option<Self::Item> {
+                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<T = Self::T>
+    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<T: CoordNum> LineTrait for Line<T> {
+    type T = T;
+    type CoordType<'a> = &'a Coord<Self::T> 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<T> {
+    type T = T;
+    type CoordType<'b> = &'a Coord<Self::T> 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<T: CoordNum>(PhantomData<T>);
+
+impl<T: CoordNum> LineTrait for UnimplementedLine<T> {
+    type T = T;
+    type CoordType<'a> = UnimplementedCoord<Self::T> 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<T = Self::T>
+    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<Item = Self::CoordType<'_>> {
+        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<Self::CoordType<'_>> {
+        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<T: CoordNum> LineStringTrait for LineString<T> {
+    type T = T;
+    type CoordType<'a> = &'a Coord<Self::T> 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<T> {
+    type T = T;
+    type CoordType<'b> = &'a Coord<Self::T> 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<T: CoordNum>(PhantomData<T>);
+
+impl<T: CoordNum> LineStringTrait for UnimplementedLineString<T> {
+    type T = T;
+    type CoordType<'a> = UnimplementedCoord<Self::T> 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<T = Self::T>
+    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<Item = Self::LineStringType<'_>> {
+        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<Self::LineStringType<'_>> {
+        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<T: CoordNum> MultiLineStringTrait for MultiLineString<T> {
+    type T = T;
+    type LineStringType<'a> = &'a LineString<Self::T> 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<T> {
+    type T = T;
+    type LineStringType<'b> = &'a LineString<Self::T> 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<T: CoordNum>(PhantomData<T>);
+
+impl<T: CoordNum> MultiLineStringTrait for UnimplementedMultiLineString<T> {
+    type T = T;
+    type LineStringType<'a> = UnimplementedLineString<Self::T> 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<T = Self::T>
+    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<Item = Self::PointType<'_>> {
+        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<Self::PointType<'_>> {
+        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<T: CoordNum> MultiPointTrait for MultiPoint<T> {
+    type T = T;
+    type PointType<'a> = &'a Point<Self::T> 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<T> {
+    type T = T;
+    type PointType<'b> = &'a Point<Self::T> 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<T: CoordNum>(PhantomData<T>);
+
+impl<T: CoordNum> MultiPointTrait for UnimplementedMultiPoint<T> {
+    type T = T;
+    type PointType<'a> = UnimplementedPoint<Self::T> 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<T = Self::T>
+    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<Item = Self::PolygonType<'_>> {
+        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<Self::PolygonType<'_>> {
+        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<T: CoordNum> MultiPolygonTrait for MultiPolygon<T> {
+    type T = T;
+    type PolygonType<'a> = &'a Polygon<Self::T> 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<T> {
+    type T = T;
+    type PolygonType<'b> = &'a Polygon<Self::T> 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<T: CoordNum>(PhantomData<T>);
+
+impl<T: CoordNum> MultiPolygonTrait for UnimplementedMultiPolygon<T> {
+    type T = T;
+    type PolygonType<'a> = UnimplementedPolygon<Self::T> 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<T = Self::T>
+    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<Self::CoordType<'_>>;
+}
+
+impl<T: CoordNum> PointTrait for Point<T> {
+    type T = T;
+    type CoordType<'a> = &'a Coord<Self::T> where Self: 'a;
+
+    fn coord(&self) -> Option<Self::CoordType<'_>> {
+        Some(&self.0)
+    }
+
+    fn dim(&self) -> Dimensions {
+        Dimensions::Xy
+    }
+}
+
+impl<T: CoordNum> PointTrait for &Point<T> {
+    type T = T;
+    type CoordType<'a> = &'a Coord<Self::T> where Self: 'a;
+
+    fn coord(&self) -> Option<Self::CoordType<'_>> {
+        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<T: CoordNum>(PhantomData<T>);
+
+impl<T: CoordNum> PointTrait for UnimplementedPoint<T> {
+    type T = T;
+    type CoordType<'a> = UnimplementedCoord<Self::T> where Self: 'a;
+
+    fn coord(&self) -> Option<Self::CoordType<'_>> {
+        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<T = Self::T>
+    where
+        Self: 'a;
+
+    /// The dimension of this geometry
+    fn dim(&self) -> Dimensions;
+
+    /// The exterior ring of the polygon
+    fn exterior(&self) -> Option<Self::RingType<'_>>;
+
+    /// An iterator of the interior rings of this Polygon
+    fn interiors(&self) -> impl DoubleEndedIterator + ExactSizeIterator<Item = Self::RingType<'_>> {
+        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<Self::RingType<'_>> {
+        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<T: CoordNum> PolygonTrait for Polygon<T> {
+    type T = T;
+    type RingType<'a> = &'a LineString<Self::T> where Self: 'a;
+
+    fn dim(&self) -> Dimensions {
+        Dimensions::Xy
+    }
+
+    fn exterior(&self) -> Option<Self::RingType<'_>> {
+        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<T> {
+    type T = T;
+    type RingType<'b> = &'a LineString<Self::T> where
+        Self: 'b;
+
+    fn dim(&self) -> Dimensions {
+        Dimensions::Xy
+    }
+
+    fn exterior(&self) -> Option<Self::RingType<'_>> {
+        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<T: CoordNum>(PhantomData<T>);
+
+impl<T: CoordNum> PolygonTrait for UnimplementedPolygon<T> {
+    type T = T;
+    type RingType<'a> = UnimplementedLineString<Self::T> where Self: 'a;
+
+    fn dim(&self) -> Dimensions {
+        unimplemented!()
+    }
+
+    fn exterior(&self) -> Option<Self::RingType<'_>> {
+        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<T = Self::T>
+    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<T> {
+    type T = T;
+    type CoordType<'b> = Coord<T> 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<T> {
+    type T = T;
+    type CoordType<'b> = Coord<T> 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<T: CoordNum>(PhantomData<T>);
+
+impl<T: CoordNum> RectTrait for UnimplementedRect<T> {
+    type T = T;
+    type CoordType<'a> = UnimplementedCoord<Self::T> 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<T = Self::T>
+    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<T: CoordNum> TriangleTrait for Triangle<T> {
+    type T = T;
+    type CoordType<'a> = &'a Coord<Self::T> 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<T> {
+    type T = T;
+    type CoordType<'b> = &'a Coord<Self::T> 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<T: CoordNum>(PhantomData<T>);
+
+impl<T: CoordNum> TriangleTrait for UnimplementedTriangle<T> {
+    type T = T;
+    type CoordType<'a> = UnimplementedCoord<Self::T> 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!()
+    }
+}