Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use new TZM geo-types instead the custom WKT ones #91

Open
nyurik opened this issue Apr 7, 2022 · 12 comments
Open

Use new TZM geo-types instead the custom WKT ones #91

nyurik opened this issue Apr 7, 2022 · 12 comments

Comments

@nyurik
Copy link
Member

nyurik commented Apr 7, 2022

Once (if) the new geo-types with 3dM support is merged, I think wkt can migrate to use them directly, instead of having its own duplicate set of types. This should improve performance (no extra transformation), and simplify code - one set of types for everything.

// current WKT type
pub struct Coord<T: WktFloat> {
    pub x: T,
    pub y: T,
    pub z: Option<T>,
    pub m: Option<T>,
}

// corresponding geo type
pub struct Coordinate<T: CoordNum, Z: ZCoord, M: Measure> {
    pub x: T,
    pub y: T,
    pub z: Z,
    pub m: M,
}

The 3dM types require proper generics (either concrete float type or a NoValue empty struct). We could represent it with a new Wkt enum instead of the existing pub struct Wkt<T> { pub item: Geometry<T> }. NoValue might need to be updated to support all CoordNum requirements.

// Usage:
match Wkt::from_str("LINESTRING (10 -20, -0 -0.5)").unwrap() {
  Wkt(g) => ..., // 2D value
  WktM(g) => ..., // 2D+M value
  Wkt3D(g) => ..., // 3D value
  Wkt3DM(g) => ..., // 3D+M value
  WktEmpty(g) => ..., // Empty value, e.g. "POINT EMPTY"
}

// Note that all T, Z, and M types of the 3dM geo-types in WKT are used as the same `T` generic type, except for the empty type
pub enum Wkt {
    Wkt(Geometry<T>),
    WktM(GeometryM<T>),
    Wkt3D(Geometry3D<T>),
    Wkt3DM(Geometry3DM<T>),
    WktEmpty(Geometry<NoValue>),
}
@michaelkirk
Copy link
Member

One point of friction is POINT EMPTY. It's not clear how to handle that with geo-types.

@nyurik
Copy link
Member Author

nyurik commented Apr 14, 2022

I think we can simply add another enum for that, treating an empty point, 2d point, and 3d point as separate cases. The only issue I see is if wkt has multiple types of points in the same geometry

@michaelkirk
Copy link
Member

Can you spell that out for me a bit more?

What would this deserialize to?

let empty_point: geo_types::Geometry = Wkt::from_str("POINT EMPTY");

@nyurik
Copy link
Member Author

nyurik commented Apr 15, 2022

let empty_point: geo_types::Geometry = Wkt::from_str("POINT EMPTY");

geo_types::Geometry<T,Z,M> would need to use a NoValue for all 3 generic params. That won't work with the proposed geotypes implementation because T has stricter requirements than Z and M -- T requires CoordNum. See related discussion.

To support empty types in WKT, we could either implement ToPrimitive, NumCast, and Num for the NoValue in geotypes (as the linked comment suggests), or we could let WKT implement its own version of NoValue with all CoordNum traits implemented. In either case, we would end up with this:

if let WktEmpty(g) = Wkt::from_str("POINT EMPTY").unwrap() {
   // g is a geo_types::Geometry<NoValue>(geo_types::Point<NoValue>(...))
}

@michaelkirk
Copy link
Member

michaelkirk commented Apr 28, 2022

Hmm... I'm not sure I understand your suggestion.

To dig in a bit, I kind of get how geo_types::Point<NoValue, NoValue, NoValue> could maybe be thought of as a corollary to POINT EMPTY.

But we need to know what all three of the generic params are before we start parsing.

let geom: Geometry<f32, NoValue, NoValue> = Wkt::from_str(str).unwrap();

We can't say both simultaneously:

// this if `str` == `POINT EMPTY`
let geom: Geometry<NoValue, NoValue, NoValue> = Wkt::from_str(str).unwrap();

// but otherwise this if `str` is a normal geometry
let geom: Geometry<f32, NoValue, NoValue> = Wkt::from_str(str).unwrap();

I think you understand that - I'm just not sure the entirety of what you're proposing. Is it a bigger api change, something like this?:

enum WktGeometryOrEmptyPoint {
   EmptyPoint,
   Geometry(Wkt)
}

Wkt::from_str(str) -> WktGeometryOrEmptyPoint

@nyurik
Copy link
Member Author

nyurik commented Apr 28, 2022

@michaelkirk you are correct - I am proposing a slightly bigger parsing API change - an enum wrapping generic typing variation. Naming is TBD.

pub enum Wkt<T: CoordNum> {
    Empty(Geometry<NoValue>),
    Geo2D(Geometry<T>),
    Geo2DM(Geometry<T, NoValue, T>),
    Geo3D(Geometry<T, T>),
    Geo3DM(Geometry<T, T, T>),
}

// This declaration would be identical to what it is now (slightly changed here for viewing)
impl<T: CoordNum + FromStr + Default> FromStr for Wkt<T> {
    type Err = &'static str;

    fn from_str(wkt_str: &str) -> Result<Self, Self::Err> {
        todo!()
    }
}

@michaelkirk
Copy link
Member

michaelkirk commented Apr 28, 2022

let g: Wkt = Wkt::from_str("GEOMETRYCOLLECTION(LINESTRING(10 20,20 30), LINESTRING EMPTY)")

What member of the Wkt enum would this be?

@nyurik
Copy link
Member Author

nyurik commented Apr 28, 2022

That's a good question - would this be a legal WKT and/or do we want to support such cases?

@nyurik
Copy link
Member Author

nyurik commented Apr 28, 2022

One (bad?) option could be some sort of a boxing variant, e.g. in case of mixed type. I am not a Rust expert just yet, so this is outside of my expertise/comfort zone for now.

pub enum Wkt<T: CoordNum> {
    Empty(Geometry<NoValue>),
    Geo2D(Geometry<T>),
    Geo2DM(Geometry<T, NoValue, T>),
    Geo3D(Geometry<T, T>),
    Geo3DM(Geometry<T, T, T>),
    Boxed(<magic>),  // <-- some sort of a trait-based boxed value?
}

@michaelkirk
Copy link
Member

That's a good question - would this be a legal WKT and/or do we want to support such cases?

Whoops, that example I gave was invalid WKT. I forgot that MultiLineStrings don't tag each individual LINESTRING...

I've updated it with a corollary that we do support today:

use crate::TryFromWkt;    
let g = geo_types::GeometryCollection::<f64>::try_from_wkt_str("GEOMETRYCOLLECTION(LINESTRING(10 20,20 30), LINESTRING EMPTY)").unwrap();    
println!(">>>>>: {:?}", g);  
>>>>> GeometryCollection([LineString(LineString([Coordinate { x: 10.0, y: 20.0 }, Coordinate { x: 20.0, y: 30.0 }])), LineString(LineString([]))])

It's kind of an edge case, but I do think these kinds of degenerate geometries exist in the wild, and that it's nice to support them where reasonable.

@michaelkirk
Copy link
Member

I guess my point is that it really seems like POINT EMPTY is a special beast in that it can't be expressed as a geo-type today. Any work-around might benefit from very narrowly only accounting for POINT EMPTY rather than devising some system for "all empty geometries" which, I think can be reasonably handled today.

@kkk25641463
Copy link

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants