Skip to content

Commit

Permalink
Implement 3D and Measure support for geo types
Browse files Browse the repository at this point in the history
This PR focuses on `geo-types` only, making it possible to merge without conflicts because it no longer uses local relative paths. This is a part of georust#742. It also bumps geo-types requirements to 0.7.4.

This PR changes the underlying geo-type data structures to support 3D data and measurement values (M and Z values). The PR attempts to cause relatively minor disruptions to the existing users (see breaking changes below). My knowledge of the actual geo algorithms is limited, so please ping me for any specific algo change.

Many other pending PRs are included here - most of them can be merged without affecting existing users, and will make reviewing this PR easier. See the list below.

All geo type structs have been renamed from `Foo<T>(...)` to `FooTZM<T,Z,M>(...)`, and several type aliases were added:

```rust
// old
pub struct LineString<T: CoordNum>(pub Vec<Coordinate<T>>);

// new
pub struct LineStringTZM<T: CoordNum, Z: ZCoord, M: Measure>(pub Vec<CoordTZM<T, Z, M>>);
pub type LineString<T> = LineStringTZM<T, NoValue, NoValue>;
pub type LineStringM<T, M> = LineStringTZM<T, NoValue, M>;
pub type LineStringZ<T> = LineStringTZM<T, T, NoValue>;
pub type LineStringZM<T, M> = LineStringTZM<T, T, M>;
```

`NoValue` is an empty struct that behaves just like a number. It supports all math and comparison operations. This means that a `Z` or `M` value can be manipulated without checking if it is actually there.  This code works for Z and M being either a number or a NoValue:

```rust
pub struct NoValue;

impl<T: CoordNum, Z: ZCoord, M: Measure> Sub for CoordTZM<T, Z, M> {
    type Output = Self;

    fn sub(self, rhs: Self) -> Self {
        coord! {
            x: self.x - rhs.x,
            y: self.y - rhs.y,
            z: self.z - rhs.z,
            m: self.m - rhs.m,
        }
    }
}
```

Function implementations can keep just the original 2D `<T>` variant, or add support for 3D `<Z>` and/or the Measure `<M>`. The above example works for all combinations of objects. This function only works for 2D objects with and without the Measure (Z is set to `NoValue` by `LineM` type alias.

```rust
impl<T: CoordNum, M: Measure> LineM<T, M> {
    /// Calculate the slope (Δy/Δx).
    pub fn slope(&self) -> T {
        self.dy() / self.dx()
    }
}
```

It will not be possible to use implicit type constructor for tuples because type aliases do not support them. Most geo types will have a `new(...)` fn if they didn't have one before.
```rust
// old
MultiPoint(vec![...]);
...iter().map(MultiPoint);
// new
MultiPoint::new(vec![...]);
...iter().map(MultiPoint::new);
```
Destructuring assignments seem to require real type rather than an alias.
```rust
// old
let Point(c) = p;
// new
let PointTZM(c) = p;
```
  • Loading branch information
nyurik committed Apr 6, 2022
1 parent 9ff2946 commit 35d5f67
Show file tree
Hide file tree
Showing 24 changed files with 879 additions and 651 deletions.
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ members = ["geo", "geo-types", "geo-postgis", "geo-test-fixtures", "jts-test-run

# Ensure any transitive dependencies also use the local geo/geo-types
geo = { path = "geo" }
geo-types = { path = "geo-types" }
# Uncomment this after geo-types 0.8+ is published with TZM support
# geo-types = { path = "geo-types" }
4 changes: 3 additions & 1 deletion geo-postgis/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,6 @@ edition = "2021"

[dependencies]
postgis = { version = ">=0.7.0, <0.9.0" }
geo-types = { version = "0.7", path = "../geo-types" }
geo-types = { version = "0.7.4" }
# Use this after geo-types 0.8+ is published with TZM support
#geo-types = { version = "0.8", path = "../geo-types" }
4 changes: 3 additions & 1 deletion geo-test-fixtures/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,6 @@ publish = false

[dependencies]
wkt = { version = "0.10.0", default-features = false }
geo-types = { path = "../geo-types" }
geo-types = "0.7.4"
# Use this after geo-types 0.8+ is published with TZM support
#geo-types = { version = "0.8", path = "../geo-types" }
24 changes: 24 additions & 0 deletions geo-types/CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,29 @@
# Changes

## Unreleased

* BREAKING: Remove deprecated functions on the `Geometry<T>`:
* `into_point` - Switch to `std::convert::TryInto<Point>`
* `into_line_string` - Switch to `std::convert::TryInto<LineString>`
* `into_line` - Switch to `std::convert::TryInto<Line>`
* `into_polygon` - Switch to `std::convert::TryInto<Polygon>`
* `into_multi_point` - Switch to `std::convert::TryInto<MultiPoint>`
* `into_multi_line_string` - Switch to `std::convert::TryInto<MultiLineString>`
* `into_multi_polygon` - Switch to `std::convert::TryInto<MultiPolygon>`
* BREAKING: Remove deprecated `CoordinateType` trait. Use `CoordFloat` or `CoordNum` instead.
* BREAKING: Remove deprecated functions from `LineString<T>`
* Remove `points_iter()` -- use `points()` instead.
* Remove `num_coords()` -- use `geo::algorithm::coords_iter::CoordsIter::coords_count` instead.
* BREAKING: Remove deprecated functions from `Point<T>`
* Remove `lng()` -- use `x()` instead.
* Remove `set_lng()` -- use `set_x()` instead.
* Remove `lat()` -- use `y()` instead.
* Remove `set_lat()` -- use `set_y()` instead.
* BREAKING: Remove deprecated `Polygon<T>::is_convex()` -- use `geo::is_convex` on `poly.exterior()` instead.
* BREAKING: Remove deprecated `Rect<T>::try_new()` -- use `Rect::new` instead, since `Rect::try_new` will never Error. Also removes corresponding `InvalidRectCoordinatesError`.
* BREAKING: Remove `GeometryCollection::new(value)`, replace it with `GeometryCollection::new(value)`, and deprecate `GeometryCollection::new_from(value)`.
* The old `GeometryCollection::new()` is no longer available, and should be replaced with `GeometryCollection::default()` if an empty `GeometryCollection` is needed.

## 0.7.4

* BREAKING: Make `Rect::to_lines` return lines in winding order for `Rect::to_polygon`.
Expand Down
80 changes: 54 additions & 26 deletions geo-types/src/arbitrary.rs
Original file line number Diff line number Diff line change
@@ -1,36 +1,45 @@
use crate::{
CoordFloat, Coordinate, Geometry, GeometryCollection, LineString, MultiLineString, MultiPoint,
MultiPolygon, Point, Polygon, Rect, Triangle,
CoordFloat, CoordTZM, GeometryCollectionTZM, GeometryTZM, LineStringTZM, Measure,
MultiLineStringTZM, MultiPointTZM, MultiPolygonTZM, PointTZM, PolygonTZM, RectTZM, TriangleTZM,
ZCoord,
};
use std::mem;

impl<'a, T> arbitrary::Arbitrary<'a> for Coordinate<T>
impl<'a, T, Z, M> arbitrary::Arbitrary<'a> for CoordTZM<T, Z, M>
where
T: arbitrary::Arbitrary<'a> + CoordFloat,
Z: arbitrary::Arbitrary<'a> + ZCoord,
M: arbitrary::Arbitrary<'a> + Measure,
{
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
Ok(coord! {
x: u.arbitrary::<T>()?,
y: u.arbitrary::<T>()?,
z: u.arbitrary::<Z>()?,
m: u.arbitrary::<M>()?,
})
}
}

impl<'a, T> arbitrary::Arbitrary<'a> for Point<T>
impl<'a, T, Z, M> arbitrary::Arbitrary<'a> for PointTZM<T, Z, M>
where
T: arbitrary::Arbitrary<'a> + CoordFloat,
Z: arbitrary::Arbitrary<'a> + ZCoord,
M: arbitrary::Arbitrary<'a> + Measure,
{
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
u.arbitrary::<Coordinate<T>>().map(Self)
u.arbitrary::<CoordTZM<T, Z, M>>().map(Self)
}
}

impl<'a, T> arbitrary::Arbitrary<'a> for LineString<T>
impl<'a, T, Z, M> arbitrary::Arbitrary<'a> for LineStringTZM<T, Z, M>
where
T: arbitrary::Arbitrary<'a> + CoordFloat,
Z: arbitrary::Arbitrary<'a> + ZCoord,
M: arbitrary::Arbitrary<'a> + Measure,
{
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
let coords = u.arbitrary::<Vec<Coordinate<T>>>()?;
let coords = u.arbitrary::<Vec<CoordTZM<T, Z, M>>>()?;
if coords.len() < 2 {
Err(arbitrary::Error::IncorrectFormat)
} else {
Expand All @@ -39,86 +48,105 @@ where
}

fn size_hint(_depth: usize) -> (usize, Option<usize>) {
(mem::size_of::<T>() * 2, None)
(
mem::size_of::<T>() * 2 + mem::size_of::<Z>() + mem::size_of::<M>(),
None,
)
}
}

impl<'a, T> arbitrary::Arbitrary<'a> for Polygon<T>
impl<'a, T, Z, M> arbitrary::Arbitrary<'a> for PolygonTZM<T, Z, M>
where
T: arbitrary::Arbitrary<'a> + CoordFloat,
Z: arbitrary::Arbitrary<'a> + ZCoord,
M: arbitrary::Arbitrary<'a> + Measure,
{
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
Ok(Self::new(
u.arbitrary::<LineString<T>>()?,
u.arbitrary::<Vec<LineString<T>>>()?,
u.arbitrary::<LineStringTZM<T, Z, M>>()?,
u.arbitrary::<Vec<LineStringTZM<T, Z, M>>>()?,
))
}
}

impl<'a, T> arbitrary::Arbitrary<'a> for MultiPoint<T>
impl<'a, T, Z, M> arbitrary::Arbitrary<'a> for MultiPointTZM<T, Z, M>
where
T: arbitrary::Arbitrary<'a> + CoordFloat,
Z: arbitrary::Arbitrary<'a> + ZCoord,
M: arbitrary::Arbitrary<'a> + Measure,
{
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
u.arbitrary::<Vec<Point<T>>>().map(Self)
u.arbitrary::<Vec<PointTZM<T, Z, M>>>().map(Self)
}
}

impl<'a, T> arbitrary::Arbitrary<'a> for MultiLineString<T>
impl<'a, T, Z, M> arbitrary::Arbitrary<'a> for MultiLineStringTZM<T, Z, M>
where
T: arbitrary::Arbitrary<'a> + CoordFloat,
Z: arbitrary::Arbitrary<'a> + ZCoord,
M: arbitrary::Arbitrary<'a> + Measure,
{
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
u.arbitrary::<Vec<LineString<T>>>().map(Self)
u.arbitrary::<Vec<LineStringTZM<T, Z, M>>>().map(Self)
}
}

impl<'a, T> arbitrary::Arbitrary<'a> for MultiPolygon<T>
impl<'a, T, Z, M> arbitrary::Arbitrary<'a> for MultiPolygonTZM<T, Z, M>
where
T: arbitrary::Arbitrary<'a> + CoordFloat,
Z: arbitrary::Arbitrary<'a> + ZCoord,
M: arbitrary::Arbitrary<'a> + Measure,
{
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
u.arbitrary::<Vec<Polygon<T>>>().map(Self)
u.arbitrary::<Vec<PolygonTZM<T, Z, M>>>().map(Self)
}
}

impl<'a, T> arbitrary::Arbitrary<'a> for GeometryCollection<T>
impl<'a, T, Z, M> arbitrary::Arbitrary<'a> for GeometryCollectionTZM<T, Z, M>
where
T: arbitrary::Arbitrary<'a> + CoordFloat,
Z: arbitrary::Arbitrary<'a> + ZCoord,
M: arbitrary::Arbitrary<'a> + Measure,
{
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
u.arbitrary()
}
}

impl<'a, T> arbitrary::Arbitrary<'a> for Rect<T>
impl<'a, T, Z, M> arbitrary::Arbitrary<'a> for RectTZM<T, Z, M>
where
T: arbitrary::Arbitrary<'a> + CoordFloat,
Z: arbitrary::Arbitrary<'a> + ZCoord,
M: arbitrary::Arbitrary<'a> + Measure,
{
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
Ok(Self::new(
u.arbitrary::<Coordinate<T>>()?,
u.arbitrary::<Coordinate<T>>()?,
u.arbitrary::<CoordTZM<T, Z, M>>()?,
u.arbitrary::<CoordTZM<T, Z, M>>()?,
))
}
}

impl<'a, T> arbitrary::Arbitrary<'a> for Triangle<T>
impl<'a, T, Z, M> arbitrary::Arbitrary<'a> for TriangleTZM<T, Z, M>
where
T: arbitrary::Arbitrary<'a> + CoordFloat,
Z: arbitrary::Arbitrary<'a> + ZCoord,
M: arbitrary::Arbitrary<'a> + Measure,
{
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
Ok(Self(
u.arbitrary::<Coordinate<T>>()?,
u.arbitrary::<Coordinate<T>>()?,
u.arbitrary::<Coordinate<T>>()?,
u.arbitrary::<CoordTZM<T, Z, M>>()?,
u.arbitrary::<CoordTZM<T, Z, M>>()?,
u.arbitrary::<CoordTZM<T, Z, M>>()?,
))
}
}

impl<'a, T> arbitrary::Arbitrary<'a> for Geometry<T>
impl<'a, T, Z, M> arbitrary::Arbitrary<'a> for GeometryTZM<T, Z, M>
where
T: arbitrary::Arbitrary<'a> + CoordFloat,
Z: arbitrary::Arbitrary<'a> + ZCoord,
M: arbitrary::Arbitrary<'a> + Measure,
{
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
let n = u.int_in_range(0..=8)?;
Expand Down
Loading

0 comments on commit 35d5f67

Please sign in to comment.