Skip to content

Conversation

@yutannihilation
Copy link

@yutannihilation yutannihilation commented Oct 25, 2025

  • I agree to follow the project's code of conduct.
  • I added an entry to CHANGES.md if knowledge of this change could be valuable to users.

This pull request adds structs like Coord, Point, LineString, etc to geo-traits. These structs implement the corresponding trait (i.e. CoordTrait, PointTrait, LineStringTrait, etc) and the conversion from the objects implementing these traits so that we can use these structs to store the geometry data.

The code is mostly derived from the wkt crate. I don't know how to add attribution properly, sorry..., but I'm not sure if I can finish this anyway. The main purpose of this pull request is to sho what the implementation would be.

The main differences from wkt are:

  • Removed Display and conversion from text.
  • Added conversion from geo-traits (e.g. LineString::from_linestring(linestring: impl LineStringTrait<T = T>))
  • Added more constructors for Coord and Point
  • Added more From conversions for Coord and Point

Copy link
Member

@kylebarron kylebarron left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR! Some quick notes

let y = coord.y();

match coord.dim() {
Dimensions::Xyzm | Dimensions::Unknown(_) => Self {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know how people intend to use Dimensions::Unknown; I usually pass them into XY if 2 dimensions; into XYZ if 3 dimensions; into XYZM if 4 dimensions

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I'll follow the convention. How do you handle other irregular numbers? Should this function return Option<Self> to choose None for such cases?

Comment on lines 193 to 199
/// Convert from a tuple of (X, Y, Z). If you want to create a `Coord` of
/// `Dimensions::Xym`, use `Coord::from_xym`.
impl<T: Copy> From<(T, T, T)> for Coord<T> {
fn from((x, y, z): (T, T, T)) -> Self {
Self::new(x, y, Some(z), None)
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm inclined to remove this and only have from_xyz because it's non-obvious statically that this passes the third dimension as z

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. Actually I was wondering if I should add this or not. Let's remove.

}

/// Create a new Geometry from an objects implementing [GeometryTrait]. The
/// result is `None` if the geometry is `Line`, `Rect`, or `Triangle`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think either we could convert Line to LineString, Rect to Polygon, Triangle to Polygon, or potentially it would make sense to also have those concepts here

///
/// To handle empty input iterators, consider calling `unwrap_or` on the result and defaulting
/// to an [empty][Self::empty] geometry with specified dimension.
pub fn from_coords(coords: impl IntoIterator<Item = impl CoordTrait<T = T>>) -> Option<Self> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't need to be impl IntoIterator; it can just be impl Iterator, right?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you are right. I couldn't make it due to some compiler error, but I'll try again...

Copy link
Author

@yutannihilation yutannihilation Oct 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, probably I too was confused, but probably you meant we want .iter() rather than .into_iter() so that we don't need to consume the object, right? If so, it's not about IntoIterator vs Iterator, and currently it should already be possible because &Coord<T> implements CoordTrait.


/// Create a new LineString from an objects implementing [LineStringTrait].
pub fn from_linestring(linestring: &impl LineStringTrait<T = T>) -> Self {
Self::from_coords(linestring.coords()).unwrap()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this unwrap is ok, because empty linestrings are valid

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for catching!

.line_strings()
.map(|l| LineString::from_linestring(&l))
.collect::<Vec<_>>();
let dim = line_strings[0].dimension();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto, we can't assume input objects are non-empty

Copy link
Author

@yutannihilation yutannihilation left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for reviewing! I'll address the comments.

Comment on lines 193 to 199
/// Convert from a tuple of (X, Y, Z). If you want to create a `Coord` of
/// `Dimensions::Xym`, use `Coord::from_xym`.
impl<T: Copy> From<(T, T, T)> for Coord<T> {
fn from((x, y, z): (T, T, T)) -> Self {
Self::new(x, y, Some(z), None)
}
}
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. Actually I was wondering if I should add this or not. Let's remove.

///
/// To handle empty input iterators, consider calling `unwrap_or` on the result and defaulting
/// to an [empty][Self::empty] geometry with specified dimension.
pub fn from_coords(coords: impl IntoIterator<Item = impl CoordTrait<T = T>>) -> Option<Self> {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you are right. I couldn't make it due to some compiler error, but I'll try again...


/// Create a new LineString from an objects implementing [LineStringTrait].
pub fn from_linestring(linestring: &impl LineStringTrait<T = T>) -> Self {
Self::from_coords(linestring.coords()).unwrap()
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for catching!

let y = coord.y();

match coord.dim() {
Dimensions::Xyzm | Dimensions::Unknown(_) => Self {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I'll follow the convention. How do you handle other irregular numbers? Should this function return Option<Self> to choose None for such cases?

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

Successfully merging this pull request may close these issues.

2 participants