Skip to content
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

Python API for pairwise_polygon_distance #1074

Merged
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
0792c4e
initial
isVoid Apr 10, 2023
1f7c674
add range cast methods
isVoid Apr 10, 2023
c92656c
passing range cast tests
isVoid Apr 10, 2023
7092c3b
passing one-pair, no-hole simple polygon tests
isVoid Apr 11, 2023
211dca8
Merge branch 'branch-23.06' of https://github.com/rapidsai/cuspatial …
isVoid Apr 11, 2023
b74d180
add polygon test
isVoid Apr 11, 2023
0b77e38
adds single pair test that has holes in polygons
isVoid Apr 11, 2023
002ffa0
fix with_param fixture doc, add multipolygon test
isVoid Apr 11, 2023
137a567
add two pair tests
isVoid Apr 11, 2023
b77285c
style
isVoid Apr 11, 2023
9224c4a
address review comments
isVoid Apr 11, 2023
cc66aeb
add column API and tests, create host-device expect function
isVoid Apr 13, 2023
b77718b
initial addition of python test and API
isVoid Apr 13, 2023
a35ff22
Merge branch 'branch-23.06' of https://github.com/rapidsai/cuspatial …
isVoid Apr 26, 2023
6b89da3
remove experimental headers
isVoid Apr 26, 2023
a349d71
remove data tests
isVoid Apr 26, 2023
2e5a3ac
add docs
isVoid Apr 26, 2023
2c75052
Merge branch 'branch-23.06' of https://github.com/rapidsai/cuspatial …
isVoid May 1, 2023
674b4ea
Merge branch 'feature/polygon_distance_column' into feature/polygon_d…
isVoid May 2, 2023
fd6072e
use new .column() method
isVoid May 2, 2023
de8260f
Merge branch 'branch-23.06' of https://github.com/rapidsai/cuspatial …
isVoid May 2, 2023
e70aa25
address review comments
isVoid May 2, 2023
45cf81c
add missing license header to multiple test files
isVoid May 2, 2023
1932b9f
address review comments
isVoid May 3, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions python/cuspatial/cuspatial/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
pairwise_point_linestring_distance,
pairwise_point_linestring_nearest_points,
pairwise_point_polygon_distance,
pairwise_polygon_distance,
point_in_polygon,
points_in_spatial_window,
polygon_bounding_boxes,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Copyright (c) 2023, NVIDIA CORPORATION.

from libcpp.memory cimport unique_ptr

from cudf._lib.cpp.column.column cimport column

from cuspatial._lib.cpp.column.geometry_column_view cimport (
geometry_column_view,
)


cdef extern from "cuspatial/distance/polygon_distance.hpp" \
namespace "cuspatial" nogil:
cdef unique_ptr[column] pairwise_polygon_distance(
const geometry_column_view & lhs,
const geometry_column_view & rhs
) except +
26 changes: 26 additions & 0 deletions python/cuspatial/cuspatial/_lib/distance.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ from cuspatial._lib.cpp.distance.point_linestring_distance cimport (
from cuspatial._lib.cpp.distance.point_polygon_distance cimport (
pairwise_point_polygon_distance as c_pairwise_point_polygon_distance,
)
from cuspatial._lib.cpp.distance.polygon_distance cimport (
pairwise_polygon_distance as c_pairwise_polygon_distance,
)
from cuspatial._lib.cpp.optional cimport optional
from cuspatial._lib.cpp.types cimport collection_type_id, geometry_type_id
from cuspatial._lib.types cimport collection_type_py_to_c
Expand Down Expand Up @@ -172,3 +175,26 @@ def pairwise_linestring_polygon_distance(
))

return Column.from_unique_ptr(move(c_result))


def pairwise_polygon_distance(Column lhs, Column rhs):
cdef shared_ptr[geometry_column_view] c_lhs = \
make_shared[geometry_column_view](
lhs.view(),
collection_type_id.MULTI,
geometry_type_id.POLYGON)

cdef shared_ptr[geometry_column_view] c_rhs = \
make_shared[geometry_column_view](
rhs.view(),
collection_type_id.MULTI,
geometry_type_id.POLYGON)

cdef unique_ptr[column] c_result

with nogil:
c_result = move(c_pairwise_polygon_distance(
c_lhs.get()[0], c_rhs.get()[0]
))

return Column.from_unique_ptr(move(c_result))
2 changes: 2 additions & 0 deletions python/cuspatial/cuspatial/core/spatial/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
pairwise_point_distance,
pairwise_point_linestring_distance,
pairwise_point_polygon_distance,
pairwise_polygon_distance,
)
from .filtering import points_in_spatial_window
from .indexing import quadtree_on_points
Expand All @@ -32,6 +33,7 @@
"pairwise_point_polygon_distance",
"pairwise_point_linestring_distance",
"pairwise_point_linestring_nearest_points",
"pairwise_polygon_distance",
"polygon_bounding_boxes",
"linestring_bounding_boxes",
"point_in_polygon",
Expand Down
54 changes: 54 additions & 0 deletions python/cuspatial/cuspatial/core/spatial/distance.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
pairwise_point_distance as cpp_pairwise_point_distance,
pairwise_point_linestring_distance as c_pairwise_point_linestring_distance,
pairwise_point_polygon_distance as c_pairwise_point_polygon_distance,
pairwise_polygon_distance as c_pairwise_polygon_distance,
)
from cuspatial._lib.hausdorff import (
directed_hausdorff_distance as cpp_directed_hausdorff_distance,
Expand Down Expand Up @@ -555,6 +556,59 @@ def pairwise_linestring_polygon_distance(
)


def pairwise_polygon_distance(polygons1: GeoSeries, polygons2: GeoSeries):
"""Compute distance between pairs of (multi)polygons and (multi)polygons
isVoid marked this conversation as resolved.
Show resolved Hide resolved
The distance between a (multi)polygon and a (multi)polygon
is defined as the shortest distance between every edge of the
(multi)polygon pair. If the multipolygon and multipolygon intersects,
the distance is 0.
isVoid marked this conversation as resolved.
Show resolved Hide resolved

This algorithm computes distance pairwise. The ith row in the result is
the distance between the ith (multi)polygon in `polygons1` and the ith
(multi)polygon in `polygons2`.

Parameters
----------
polygons1 : GeoSeries
The (multi)polygons to compute the distance from.
polygons2 : GeoSeries
The (multi)polygons to compute the distance from.
Returns
-------
distance : cudf.Series

Notes
-----
The input `GeoSeries` must contain a single type geometry.
For example, `polygons1` series cannot contain both points and
polygons.
isVoid marked this conversation as resolved.
Show resolved Hide resolved

Examples
--------
Compute distance between a point and a polygon:
"""
isVoid marked this conversation as resolved.
Show resolved Hide resolved

if len(polygons1) != len(polygons2):
raise ValueError("Unmatched input geoseries length.")

if len(polygons1) == 0:
return cudf.Series(dtype=polygons1.lines.xy.dtype)

if not contains_only_polygons(polygons1):
raise ValueError("`polygons1` array must contain only polygons")

if not contains_only_polygons(polygons2):
raise ValueError("`polygons2` array must contain only polygons")

# Handle slicing and aligns in geoseries
isVoid marked this conversation as resolved.
Show resolved Hide resolved
polygon1_column = polygons1.polygons.column()
polygon2_column = polygons2.polygons.column()

return Series._from_data(
{None: c_pairwise_polygon_distance(polygon1_column, polygon2_column)}
)


def _flatten_point_series(
points: GeoSeries,
) -> Tuple[
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import geopandas as gpd
import pytest
from shapely.geometry import MultiPolygon, Polygon

import cudf
from cudf.testing import assert_series_equal

import cuspatial


def test_polygon_empty():
lhs = cuspatial.GeoSeries.from_polygons_xy([], [0], [0], [0])
rhs = cuspatial.GeoSeries.from_polygons_xy([], [0], [0], [0])

got = cuspatial.pairwise_polygon_distance(lhs, rhs)

expect = cudf.Series([], dtype="f8")

assert_series_equal(got, expect)


@pytest.mark.parametrize(
"polygons1",
[
[Polygon([(10, 11), (11, 10), (11, 11), (10, 11)])],
[
MultiPolygon(
[
Polygon([(12, 10), (11, 10), (11, 11), (12, 10)]),
Polygon([(11, 10), (12, 10), (11, 11), (11, 10)]),
]
)
],
],
)
@pytest.mark.parametrize(
"polygons2",
[
[Polygon([(0, 1), (1, 0), (-1, 0), (0, 1)])],
[
MultiPolygon(
[
Polygon([(-2, 0), (-1, 0), (-1, -1), (-2, 0)]),
Polygon([(1, 0), (2, 0), (1, -1), (1, 0)]),
]
)
],
],
)
def test_one_pair(polygons1, polygons2):
lhs = gpd.GeoSeries(polygons1)
rhs = gpd.GeoSeries(polygons2)

dlhs = cuspatial.GeoSeries(polygons1)
drhs = cuspatial.GeoSeries(polygons2)

expect = lhs.distance(rhs)
got = cuspatial.pairwise_polygon_distance(dlhs, drhs)

assert_series_equal(got, cudf.Series(expect))


@pytest.mark.parametrize(
"polygons1",
[
[
Polygon([(0, 1), (1, 0), (-1, 0), (0, 1)]),
Polygon([(-4, -4), (-4, -5), (-5, -5), (-5, -4), (-5, -5)]),
],
[
MultiPolygon(
[
Polygon([(0, 1), (1, 0), (-1, 0), (0, 1)]),
Polygon([(0, 1), (1, 0), (0, -1), (-1, 0), (0, 1)]),
]
),
MultiPolygon(
[
Polygon(
[(-4, -4), (-4, -5), (-5, -5), (-5, -4), (-5, -5)]
),
Polygon([(-2, 0), (-2, -2), (0, -2), (0, 0), (-2, 0)]),
]
),
],
],
)
@pytest.mark.parametrize(
"polygons2",
[
[
Polygon([(0, 1), (1, 0), (-1, 0), (0, 1)]),
Polygon([(-4, -4), (-4, -5), (-5, -5), (-5, -4), (-5, -5)]),
],
[
MultiPolygon(
[
Polygon([(0, 1), (1, 0), (-1, 0), (0, 1)]),
Polygon([(0, 1), (1, 0), (0, -1), (-1, 0), (0, 1)]),
]
),
MultiPolygon(
[
Polygon(
[(-4, -4), (-4, -5), (-5, -5), (-5, -4), (-5, -5)]
),
Polygon([(-2, 0), (-2, -2), (0, -2), (0, 0), (-2, 0)]),
]
),
],
],
)
def test_two_pair(polygons1, polygons2):
lhs = gpd.GeoSeries(polygons1)
rhs = gpd.GeoSeries(polygons2)

dlhs = cuspatial.GeoSeries(polygons1)
drhs = cuspatial.GeoSeries(polygons2)

expect = lhs.distance(rhs)
got = cuspatial.pairwise_polygon_distance(dlhs, drhs)

assert_series_equal(got, cudf.Series(expect))


def test_linestring_polygon_large(polygon_generator):
N = 100
polygons1 = gpd.GeoSeries(polygon_generator(N, 20.0, 5.0))
polygons2 = gpd.GeoSeries(polygon_generator(N, 10.0, 3.0))

dpolygons1 = cuspatial.from_geopandas(polygons1)
dpolygons2 = cuspatial.from_geopandas(polygons2)

expect = polygons1.distance(polygons2)
got = cuspatial.pairwise_polygon_distance(dpolygons1, dpolygons2)

assert_series_equal(got, cudf.Series(expect))


def test_point_polygon_geoboundaries(naturalearth_lowres):
N = 50

lhs = naturalearth_lowres.geometry[:N].reset_index(drop=True)
rhs = naturalearth_lowres.geometry[N : 2 * N].reset_index(drop=True)
expect = lhs.distance(rhs)
got = cuspatial.pairwise_polygon_distance(
cuspatial.GeoSeries(lhs), cuspatial.GeoSeries(rhs)
)
assert_series_equal(cudf.Series(expect), got)
isVoid marked this conversation as resolved.
Show resolved Hide resolved