-
Notifications
You must be signed in to change notification settings - Fork 6
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
Add centroid, boundary and convex_hull function (returning new Geography) #20
Add centroid, boundary and convex_hull function (returning new Geography) #20
Conversation
src/accessors-geog.cpp
Outdated
PyObjectGeography convex_hull(PyObjectGeography a) { | ||
const auto& a_ptr = a.as_geog_ptr()->geog(); | ||
auto s2_obj = s2geog::s2_convex_hull(a_ptr); | ||
auto geog_ptr = std::make_unique<Geography>(std::move(s2_obj)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So currently, this function returns instances of the base class Geography
. If I change this to std::make_unique<Polygon>
, then it actually returns a Polygon
python subclass.
For the convex_hull case, I think I can be sure that this will always return a Polygon. But in general, do we need a method on s2geography::Geography
to know which subtype it actually is (a PolygonGeography) in this case), so we can assert / known to which subclass to downcast on our side?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the subtype could be inferred from both num_shapes()
and dimension()
methods, except maybe for empty geographies. That said, a specific method might be indeed convenient and slightly more efficient as dimension()
iterates over each shape in "multi" subclasses.
I'm not sure if we really need it, though. Do you have some cases in mind where this would need to be inferred at runtime?
For the convex hull case, s2geog::s2_convex_hull
returns a s2geog::Geography
pointer but it should probably return a s2geog::PolygonGeography
pointer since this is what S2ConvexHullAggregator::Finalize() returns anyway.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know if this is the same for s2, but with GEOS there are certainly geometry-returning functions that can return varying geometry types depending on the input (eg an intersection can basically return any geometry type)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know if this is the same for s2, but with GEOS there are certainly geometry-returning functions that can return varying geometry types depending on the input (eg an intersection can basically return any geometry type)
If you know the type of the input(s) can you always determine the type of the output(s), or is there some geometry-returning functions where the output geometry type depends on some internal processing logic?
In the 1st case, we could use spherely::Geography::geog_type()
returned from the inputs. Although I agree this might be worth of having such method available in s2geography, which would help getting rid of spherely::Geography
wrapper classes that makes things quite confused.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you know the type of the input(s) can you always determine the type of the output(s), or is there some geometry-returning functions where the output geometry type depends on some internal processing logic?
Yes, AFAIK that is the case. For example consider intersection of two polygons: if they overlap, you can get a Polygon or MultiPolygon, if they touch along a segment you get a LineString (or MultiLineString), and if they touch you get a Point (or MultiPoint), and if they both overlap in some area and touch in another area, you get a GeometryCollection.
src/pybind11.hpp
Outdated
@@ -90,6 +90,10 @@ class PyObjectGeography : public py::object { | |||
return py::cast(std::move(geog_ptr)); | |||
} | |||
|
|||
// static PyObjectGeography as_py_object(std::unique_ptr<T> geog_ptr) { | |||
// return static_cast<PyObjectGeography>(py::cast(std::move(geog_ptr))); | |||
// } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I need to clean this up, but I thought to have as_py_object
actually return PyObjectGeography instead of py::object, since it's currently not yet used, and in practice we need PyObjectGeography in the vectorized functions. But didn't get this to work ..
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Something like this should work:
template <class T, std::enable_if<...>>
static PyObjectGeography as_py_object(std::unique_ptr<T> geog_ptr) {
auto pyobj = py::cast(std::move(geog_ptr));
auto pyobj_geog = static_cast<PyObjectGeography&>(pyobj);
return std::move(pyobj_geog);
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, will give that a try. Are you OK with changing the existing as_py_object to do that? (I am not sure if we have a use case of the current version that returns py::object)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes sure, it is also more consistent to have a static method returning an instance of the same type IMO. (from_geog
could be a better name than as_py_object
).
Codecov ReportPatch coverage:
Additional details and impacted files@@ Coverage Diff @@
## main #20 +/- ##
==========================================
- Coverage 56.07% 54.61% -1.46%
==========================================
Files 5 6 +1
Lines 214 249 +35
Branches 95 114 +19
==========================================
+ Hits 120 136 +16
Misses 7 7
- Partials 87 106 +19
Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here. ☔ View full report in Codecov by Sentry. |
actual = spherely.centroid(geog) | ||
assert isinstance(actual, spherely.Point) | ||
# TODO add some way of testing almost equality | ||
# assert spherely.equals(actual, expected) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Testing is not super easy at the moment (we will have to develop some utilities to help with this).
Currently this gives failures like
> assert spherely.equals(actual, expected)
E assert False
E + where False = <built-in method equals of PyCapsule object at 0x7fea1738e790>(POINT (4.999999999999999 0), POINT (5 0))
E + where <built-in method equals of PyCapsule object at 0x7fea1738e790> = spherely.equals
So either we need to have a version of equals
that does some rounding/snapping of coordinates (I think S2 should provide some options for that, which we probably want to expose).
Or more generally test based on the WKT repr, but then we also need to have a way to specify / reduce the precision of the WKT repr (#8)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree we need better helpers or features for testing (getting object coordinates would be useful too). This is short-term priority I think.
const auto& a_ptr = a.as_geog_ptr()->geog(); | ||
auto s2_obj = s2geog::s2_boundary(a_ptr); | ||
// TODO return specific subclass | ||
auto geog_ptr = std::make_unique<spherely::Geography>(std::move(s2_obj)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Boundary can return various geography types (empty collection for point, multipoint for linestring, linestring for polygon), so for now using the generic Geography
, which will also give this parent class at the python level. We will have to add some utility to infer the type and cast to correct subclass.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, this can be done after #26.
@benbovy this should be ready I think. There are certainly some things to improve (easier testing, correct return class), but those can be left for follow-ups I think. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice, LGTM thanks @jorisvandenbossche!
Could you just add the new functions in the API reference, please? And then I think we can merge this right away.
const auto& a_ptr = a.as_geog_ptr()->geog(); | ||
auto s2_obj = s2geog::s2_boundary(a_ptr); | ||
// TODO return specific subclass | ||
auto geog_ptr = std::make_unique<spherely::Geography>(std::move(s2_obj)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, this can be done after #26.
actual = spherely.centroid(geog) | ||
assert isinstance(actual, spherely.Point) | ||
# TODO add some way of testing almost equality | ||
# assert spherely.equals(actual, expected) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree we need better helpers or features for testing (getting object coordinates would be useful too). This is short-term priority I think.
No description provided.