-
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
Changes from 4 commits
3d7dd92
496d6aa
4f65fdf
0f5fb03
1cd9376
82a7e71
3331305
5a5d714
8f5a025
11b0fbb
91ce98f
b899e07
c368271
22e1439
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
#include <s2geography.h> | ||
|
||
#include "geography.hpp" | ||
#include "pybind11.hpp" | ||
|
||
namespace py = pybind11; | ||
namespace s2geog = s2geography; | ||
using namespace spherely; | ||
|
||
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)); | ||
auto res_object = PyObjectGeography::as_py_object(std::move(geog_ptr)); | ||
return static_cast<PyObjectGeography&>(res_object); | ||
} | ||
|
||
void init_accessors(py::module& m) { | ||
m.def("convex_hull", py::vectorize(&convex_hull), py::arg("a"), | ||
R"pbdoc( | ||
Computes the convex hull of each geography. | ||
|
||
Parameters | ||
---------- | ||
a : :py:class:`Geography` or array_like | ||
Geography object | ||
|
||
)pbdoc"); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 commentThe reason will be displayed to describe this comment to others. Learn more. I need to clean this up, but I thought to have There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 commentThe 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 commentThe 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. ( |
||
|
||
// Just check whether the object is a Geography | ||
// | ||
bool is_geog_ptr() const { return check_type(false); } | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import numpy as np | ||
|
||
import spherely | ||
|
||
import pytest | ||
|
||
|
||
@pytest.mark.parametrize( | ||
"geog, expected", | ||
[ | ||
# (spherely.Point(0, 0), spherely.Point(0, 0)), | ||
( | ||
spherely.LineString([(0, 0), (0, 2), (2, 2)]), | ||
spherely.Polygon([(0, 0), (0, 2), (2, 2)]) | ||
), | ||
# ( | ||
# spherely.Polygon([(0, 0), (0, 2), (2, 2), (1, 0.5)]), | ||
# spherely.Polygon([(0, 0), (0, 2), (2, 2)]) | ||
# ), | ||
], | ||
) | ||
def test_convex_hull(geog, expected) -> None: | ||
# test array + scalar | ||
actual = spherely.convex_hull(geog) | ||
assert isinstance(actual, spherely.Geography) | ||
assert spherely.equals(actual, expected) | ||
|
||
actual = spherely.convex_hull([geog]) | ||
assert isinstance(actual, np.ndarray) | ||
actual = actual[0] | ||
assert isinstance(actual, spherely.Geography) | ||
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.
So currently, this function returns instances of the base class
Geography
. If I change this tostd::make_unique<Polygon>
, then it actually returns aPolygon
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()
anddimension()
methods, except maybe for empty geographies. That said, a specific method might be indeed convenient and slightly more efficient asdimension()
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 as2geog::Geography
pointer but it should probably return as2geog::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.
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 ofspherely::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.
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.