Skip to content

Commit

Permalink
ENH: Added length and perimeter implementation (#77)
Browse files Browse the repository at this point in the history
* Added length and (broken) perimeter implementation

* Added radius parameter to functions

* Add new functions to documentation
  • Loading branch information
JoelJaeschke authored Nov 26, 2024
1 parent a650b7a commit 8e2d481
Show file tree
Hide file tree
Showing 4 changed files with 91 additions and 0 deletions.
2 changes: 2 additions & 0 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ Measurement

area
distance
length
perimeter

.. _api_predicates:

Expand Down
39 changes: 39 additions & 0 deletions src/accessors-geog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,14 @@ double area(PyObjectGeography a, double radius = EARTH_RADIUS_METERS) {
return s2geog::s2_area(a.as_geog_ptr()->geog()) * radius * radius;
}

double length(PyObjectGeography a, double radius = EARTH_RADIUS_METERS) {
return s2geog::s2_length(a.as_geog_ptr()->geog()) * radius;
}

double perimeter(PyObjectGeography a, double radius = EARTH_RADIUS_METERS) {
return s2geog::s2_perimeter(a.as_geog_ptr()->geog()) * radius;
}

void init_accessors(py::module& m) {
m.attr("EARTH_RADIUS_METERS") = py::float_(EARTH_RADIUS_METERS);

Expand Down Expand Up @@ -112,4 +120,35 @@ void init_accessors(py::module& m) {
Radius of Earth in meters, default 6,371,010
)pbdoc");

m.def("length",
py::vectorize(&length),
py::arg("a"),
py::arg("radius") = EARTH_RADIUS_METERS,
R"pbdoc(
Calculates the length of a line geography, returning zero for other types.
Parameters
----------
a : :py:class:`Geography` or array_like
Geography object
radius : float, optional
Radius of Earth in meters, default 6,371,010
)pbdoc");

m.def("perimeter",
py::vectorize(&perimeter),
py::arg("a"),
py::arg("radius") = EARTH_RADIUS_METERS,
R"pbdoc(
Calculates the perimeter of a polygon geography, returning zero for other types.
Parameters
----------
a : :py:class:`Geography` or array_like
Geography object
radius : float, optional
Radius of Earth in meters, default 6,371,010
)pbdoc");
}
2 changes: 2 additions & 0 deletions src/spherely.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,8 @@ convex_hull: _VFunc_Nin1_Nout1[
]
distance: _VFunc_Nin2optradius_Nout1[Literal["distance"], float, float]
area: _VFunc_Nin1optradius_Nout1[Literal["area"], float, float]
length: _VFunc_Nin1optradius_Nout1[Literal["length"], float, float]
perimeter: _VFunc_Nin1optradius_Nout1[Literal["perimeter"], float, float]

# io functions

Expand Down
48 changes: 48 additions & 0 deletions tests/test_accessors.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,3 +159,51 @@ def test_area():
)
def test_area_empty(geog):
assert spherely.area(spherely.from_wkt(geog)) == 0


def test_length():
geog = spherely.linestring([(0, 0), (1, 0)])
result = spherely.length(geog, radius=1)
assert isinstance(result, float)
expected = 1.0 * np.pi / 180.0
assert result == pytest.approx(expected, 1e-9)

actual = spherely.length([geog], radius=1)
assert isinstance(actual, np.ndarray)
actual = actual[0]
assert isinstance(actual, float)
assert actual == pytest.approx(expected, 1e-9)


@pytest.mark.parametrize(
"geog",
[
"POINT (0 0)",
"POINT EMPTY",
"POLYGON EMPTY",
"POLYGON ((0 0, 0 1, 1 0, 0 0))",
],
)
def test_length_invalid(geog):
assert spherely.length(spherely.from_wkt(geog)) == 0.0


def test_perimeter():
geog = spherely.polygon([(0, 0), (0, 90), (90, 90), (90, 0), (0, 0)])
result = spherely.perimeter(geog, radius=1)
assert isinstance(result, float)
expected = 3 * 90 * np.pi / 180.0
assert result == pytest.approx(expected, 1e-9)

actual = spherely.perimeter([geog], radius=1)
assert isinstance(actual, np.ndarray)
actual = actual[0]
assert isinstance(actual, float)
assert actual == pytest.approx(expected, 1e-9)


@pytest.mark.parametrize(
"geog", ["POINT (0 0)", "POINT EMPTY", "LINESTRING (0 0, 1 0)", "POLYGON EMPTY"]
)
def test_perimeter_invalid(geog):
assert spherely.perimeter(spherely.from_wkt(geog)) == 0.0

0 comments on commit 8e2d481

Please sign in to comment.