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?

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... I find it really annoying that there is a Dimensions::Unknown at all, but then again I only work with xy/xyz/xym/xyzm data

}

/// 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.

Copy link
Member

Choose a reason for hiding this comment

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

Yes, we don't need to consume the object. But taking in impl IntoIterator means that the input needs to be able to be consumed into an iterator. If we take in an impl Iterator instead, then the input doesn't need to be consumed

Copy link
Author

Choose a reason for hiding this comment

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

Sorry, I still don't understand your point. For example, if we have Vec<(f64, f64)>, I think we typically pass &[(f64, 64)] to impl IntoIterator argument because the slice implements IntoIterator. I think it's fine to have the slice consumed because we still hold the original Vec, no? Do you mean changing it to impl Iterator might save us from accidentally passing Vec itself...?

This might be because I'm not familiar with designing such interface. Do you have some good example of a signature that takes Iterator instead of IntoIterator?


/// 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.

///
/// 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?

@kylebarron
Copy link
Member

kylebarron commented Nov 22, 2025

I think the main question here is what the scope of geo-traits should be, and whether we should ship structs like this inside of geo-traits. As it is geo-traits only has an optional dependency on geo-types. This would create another optional feature that partially overlaps with geo-types.

Another possible worry is that if we add more structs to the geo-traits crate, then people might start writing APIs in terms of the structs thinking that those are part of the public consumption API of geo-traits, while this PR would be only intended to simplify life for the producers of geo-traits data.

Another option is to create and publish a separate crate with just these structs. That doesn't expand the maintenance burden of crates in the geo repo.

In my own work I don't find it that burdensome to use the wkt crate to manage these structs.

I'd like to hear from other maintainers too

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