-
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 all 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,72 @@ | ||
#include <s2geography.h> | ||
|
||
#include "geography.hpp" | ||
#include "pybind11.hpp" | ||
|
||
namespace py = pybind11; | ||
namespace s2geog = s2geography; | ||
using namespace spherely; | ||
|
||
PyObjectGeography centroid(PyObjectGeography a) { | ||
const auto& a_ptr = a.as_geog_ptr()->geog(); | ||
auto s2_point = s2geog::s2_centroid(a_ptr); | ||
std::unique_ptr<Point> point = | ||
make_geography<s2geog::PointGeography, spherely::Point>(s2_point); | ||
return PyObjectGeography::from_geog(std::move(point)); | ||
} | ||
|
||
PyObjectGeography boundary(PyObjectGeography a) { | ||
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)); | ||
return PyObjectGeography::from_geog(std::move(geog_ptr)); | ||
} | ||
|
||
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<spherely::Polygon>(std::move(s2_obj)); | ||
return PyObjectGeography::from_geog(std::move(geog_ptr)); | ||
} | ||
|
||
void init_accessors(py::module& m) { | ||
m.def("centroid", | ||
py::vectorize(¢roid), | ||
py::arg("a"), | ||
R"pbdoc( | ||
Computes the centroid of each geography. | ||
|
||
Parameters | ||
---------- | ||
a : :py:class:`Geography` or array_like | ||
Geography object | ||
|
||
)pbdoc"); | ||
|
||
m.def("boundary", | ||
py::vectorize(&boundary), | ||
py::arg("a"), | ||
R"pbdoc( | ||
Computes the boundary of each geography. | ||
|
||
Parameters | ||
---------- | ||
a : :py:class:`Geography` or array_like | ||
Geography object | ||
|
||
)pbdoc"); | ||
|
||
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 |
---|---|---|
@@ -0,0 +1,83 @@ | ||
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)]), | ||
spherely.Point(0, 1), | ||
), | ||
( | ||
spherely.Polygon([(0, 0), (2, 0), (2, 2), (0, 2)]), | ||
spherely.Point(1, 1), | ||
), | ||
], | ||
) | ||
def test_centroid(geog, expected) -> None: | ||
# scalar | ||
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 commentThe 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
So either we need to have a version of 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 agree we need better helpers or features for testing (getting object coordinates would be useful too). This is short-term priority I think. |
||
|
||
# array | ||
actual = spherely.centroid([geog]) | ||
assert isinstance(actual, np.ndarray) | ||
actual = actual[0] | ||
assert isinstance(actual, spherely.Point) | ||
# assert spherely.equals(actual, expected) | ||
|
||
|
||
@pytest.mark.parametrize( | ||
"geog, expected", | ||
[ | ||
(spherely.Point(0, 0), "GEOMETRYCOLLECTION EMPTY"), | ||
(spherely.LineString([(0, 0), (0, 2), (2, 2)]), "MULTIPOINT ((0 0), (2 2))"), | ||
( | ||
spherely.Polygon([(0, 0), (2, 0), (2, 2), (1.5, 0.5)]), | ||
"LINESTRING (0.5 1.5, 2 2, 0 2, 0 0, 0.5 1.5)", | ||
), | ||
], | ||
) | ||
def test_boundary(geog, expected) -> None: | ||
# scalar | ||
actual = spherely.boundary(geog) | ||
assert str(actual) == expected | ||
|
||
# array | ||
actual = spherely.boundary([geog]) | ||
assert isinstance(actual, np.ndarray) | ||
assert str(actual[0]) == expected | ||
|
||
|
||
@pytest.mark.parametrize( | ||
"geog, expected", | ||
[ | ||
( | ||
spherely.LineString([(0, 0), (0, 2), (2, 2)]), | ||
spherely.Polygon([(0, 0), (0, 2), (2, 2)]), | ||
), | ||
( | ||
spherely.Polygon([(0, 0), (2, 0), (2, 2), (1.5, 0.5)]), | ||
spherely.Polygon([(0, 0), (2, 0), (2, 2)]), | ||
), | ||
], | ||
) | ||
def test_convex_hull(geog, expected) -> None: | ||
# scalar | ||
actual = spherely.convex_hull(geog) | ||
assert isinstance(actual, spherely.Polygon) | ||
assert spherely.equals(actual, expected) | ||
|
||
# array | ||
actual = spherely.convex_hull([geog]) | ||
assert isinstance(actual, np.ndarray) | ||
actual = actual[0] | ||
assert isinstance(actual, spherely.Polygon) | ||
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.
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.