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 point-linestring nearest points #681

Merged
Show file tree
Hide file tree
Changes from 96 commits
Commits
Show all changes
103 commits
Select commit Hold shift + click to select a range
de78310
Initial to atomics refactoring
isVoid Jun 14, 2022
6011900
Initial refactoring geometry utilities
isVoid Jun 14, 2022
9761eab
Add header only api
isVoid Jun 14, 2022
2ed60eb
Add cudf column API
isVoid Jun 14, 2022
e3ec12d
cmake updates
isVoid Jun 14, 2022
a72ac10
inline atomics to avoid compiling RDC
isVoid Jun 14, 2022
31b1e78
Rename to proper naming convention
isVoid Jun 14, 2022
f55af2f
make all atomics inline
isVoid Jun 14, 2022
e874f3b
Merge branch 'improvement/device_atomics_refactor' into feature/point…
isVoid Jun 14, 2022
e9b2ca8
Merge branch 'improvement/geometry_utilities' into feature/point_line…
isVoid Jun 14, 2022
f2277f0
Changes header only API to accept only single offset iterator
isVoid Jun 15, 2022
7bc0914
Add header only API tests
isVoid Jun 15, 2022
a8ec5ff
Update cudf column API calls
isVoid Jun 15, 2022
1fdfd59
Initial add utility method
isVoid Jun 15, 2022
0274875
Add counting transform iterators
isVoid Jun 15, 2022
df286a0
Revert "Update cudf column API calls"
isVoid Jun 15, 2022
688fbb5
Revert "Add cudf column API"
isVoid Jun 15, 2022
d0c0c4f
Merge branch 'branch-22.08' of https://github.com/rapidsai/cuspatial …
isVoid Aug 1, 2022
1c4ad61
Update refactored helper locations
isVoid Aug 1, 2022
e4038a5
Add cudf column API
isVoid Aug 1, 2022
5004bbe
Add equality test utility and fix header only tests
isVoid Aug 1, 2022
986dbb2
minor docstring updates
isVoid Aug 1, 2022
2feab4b
Add cudf column API documentation
isVoid Aug 1, 2022
5e07194
Include point-linestring distance files in docs.
isVoid Aug 1, 2022
4834aa2
Apply suggestions from code review
isVoid Aug 1, 2022
de0feb7
Address review comments
isVoid Aug 11, 2022
c897974
Merge branch 'feature/header_only_point_linestring_distance' of githu…
isVoid Aug 11, 2022
6dc34e0
Merge branch 'branch-22.10' of https://github.com/rapidsai/cuspatial …
isVoid Aug 16, 2022
624a97f
Fix broken builds
isVoid Aug 16, 2022
7d8b4b6
Initial scaffolding for nearest point
isVoid Aug 17, 2022
488ab7a
refactor primitives
isVoid Aug 17, 2022
26a6c6a
Initial implementation of kernel
isVoid Aug 17, 2022
8165c68
Check CUDA Error after launch
isVoid Aug 17, 2022
37de419
Assert multi-point, multi-linestring format
isVoid Aug 19, 2022
924ef59
Add `interleaved_iterator_to_vec_2d_iterator`
isVoid Aug 19, 2022
999d5d7
Multi-geom implementation
isVoid Aug 19, 2022
164fd68
Flesh out cudf column API
isVoid Aug 20, 2022
faeff0e
rename header only API iterators
isVoid Sep 6, 2022
4729e4d
Merge branch 'branch-22.10' of https://github.com/rapidsai/cuspatial …
isVoid Sep 6, 2022
d34ee82
Fixes tuple construction in helper
isVoid Sep 8, 2022
59a2207
Variable renames, adds static asserts
isVoid Sep 8, 2022
aa39cdc
Adds vec2d to interleaved transformer
isVoid Sep 8, 2022
529d7b2
Variable renames, use interleaved iterator transformer
isVoid Sep 8, 2022
7e1dffe
Adds simple test, vector of vec2d equivalence test utility
isVoid Sep 8, 2022
50bfd25
fix end of segment access, writes intra-index not global index to res…
isVoid Sep 9, 2022
fde05b8
Merge branch 'branch-22.10' of https://github.com/rapidsai/cuspatial …
isVoid Sep 13, 2022
afae694
docstring for column API
isVoid Sep 13, 2022
55411a4
Update to also return multipoint index when needed
isVoid Sep 13, 2022
35a661b
Add column API impl to cmake
isVoid Sep 13, 2022
ff8be26
Update header only API tests to accomodate the new return type
isVoid Sep 13, 2022
50a2aea
use `constexpr` instead
isVoid Sep 13, 2022
a2240ed
Make column API device span const access
isVoid Sep 13, 2022
b0c3665
Add column API tests
isVoid Sep 13, 2022
93d6c5f
Add invalid input tests
isVoid Sep 13, 2022
281b972
Bug: min distance squared should be initialized for all points of a m…
isVoid Sep 13, 2022
b4e1c47
Bug: return coordinate array should be npairs*2; geometry array lengt…
isVoid Sep 13, 2022
9f260f7
MInor test fixes
isVoid Sep 13, 2022
7863a3e
Rename launch functor
isVoid Sep 13, 2022
633cdaa
Write throws in docstring
isVoid Sep 13, 2022
979611b
Update header only API docs
isVoid Sep 13, 2022
f3f52dc
Add docstring for kernel
isVoid Sep 13, 2022
6701d64
Typo
isVoid Sep 13, 2022
6e3d785
Rename the function as `nearest_points`
isVoid Sep 14, 2022
15af211
Merge branch 'branch-22.10' of https://github.com/rapidsai/cuspatial …
isVoid Sep 15, 2022
6c13809
use latest vector<vec_2d> matcher
isVoid Sep 15, 2022
946bd10
Merge branch 'branch-22.10' of https://github.com/rapidsai/cuspatial …
isVoid Sep 19, 2022
fd3b32c
Initial python scaffoldings
isVoid Sep 19, 2022
cb80449
replace tuple with custom struct
isVoid Sep 19, 2022
1c8ea50
rename column API file and add API to doc page
isVoid Sep 19, 2022
255fcdf
Merge branch 'feature/pairwise_point_to_nearest_linestring_point' int…
isVoid Sep 19, 2022
8c3160c
second iteration on scaffolding
isVoid Sep 19, 2022
fceafaf
fix tests after rename
isVoid Sep 19, 2022
48be3ca
Merge branch 'feature/pairwise_point_to_nearest_linestring_point' int…
isVoid Sep 19, 2022
effcbf1
initial implementation of python API
isVoid Sep 20, 2022
79f4bc8
smooth out geocolumn construction and move feature_enum to geometa
isVoid Sep 20, 2022
3bf01c9
smooth out geocolumn construction and move feature_enum to geometa
isVoid Sep 20, 2022
ce1d8b7
address review comments in bulk
isVoid Sep 20, 2022
0e36d84
Merge branch 'feature/pairwise_point_to_nearest_linestring_point' int…
isVoid Sep 20, 2022
ad36467
Merge branch 'feature/python_point_linestring_nearest_points' of gith…
isVoid Sep 20, 2022
24b1769
fix cython compilation errors
isVoid Sep 21, 2022
76cf3b6
initial
isVoid Sep 21, 2022
e2ed345
Merge branch 'branch-22.10' of https://github.com/rapidsai/cuspatial …
isVoid Sep 21, 2022
92ccd90
Merge branch 'feature/geoseries_list' into feature/python_point_lines…
isVoid Sep 21, 2022
f4a4612
fix _from_points_xy factory
isVoid Sep 21, 2022
9dd1a1e
revert the use of thrust::tuple
isVoid Sep 21, 2022
f72ef3d
Add two tests for nearest end points
isVoid Sep 21, 2022
3fba1c4
add more corner case tests
isVoid Sep 21, 2022
b2ce3a4
Merge branch 'feature/pairwise_point_to_nearest_linestring_point' int…
isVoid Sep 21, 2022
ad7beef
Add randomized test
isVoid Sep 22, 2022
7bcf609
python style
isVoid Sep 22, 2022
fdf272a
cython style fixes
isVoid Sep 22, 2022
95a07df
remove index condition code that was accidentily introduced
isVoid Sep 22, 2022
c9f90c1
remove stale breakpoint
isVoid Sep 22, 2022
4400eb4
Merge branch 'branch-22.10' of https://github.com/rapidsai/cuspatial …
isVoid Sep 22, 2022
91d9687
Merge branch 'feature/geoseries_list' into feature/python_point_lines…
isVoid Sep 22, 2022
6ca0d85
add documentation entry
isVoid Sep 22, 2022
c13c092
generalize python column unwrapping
isVoid Sep 26, 2022
17044ef
test failure cases
isVoid Sep 26, 2022
3c0e27d
Merge branch 'branch-22.10' of https://github.com/rapidsai/cuspatial …
isVoid Sep 27, 2022
0d2a1b3
use obj.buffer for approximate point on line check
isVoid Sep 27, 2022
2c804fd
update docs
isVoid Sep 27, 2022
44fcf0c
style
isVoid Sep 27, 2022
7fd80d0
style
isVoid Sep 27, 2022
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
5 changes: 5 additions & 0 deletions docs/source/api_docs/spatial.rst
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ Measurement Functions
.. autofunction:: cuspatial.haversine_distance
.. autofunction:: cuspatial.pairwise_linestring_distance

Nearest Points Function
+++++++++++++++++++++++

.. autofunction:: cuspatial.pairwise_point_linestring_nearest_points

Bounding Boxes
++++++++++++++

Expand Down
1 change: 0 additions & 1 deletion docs/source/api_docs/trajectory.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,4 @@ Functions for identifying and grouping trajectories from point data.

.. autofunction:: cuspatial.derive_trajectories
.. autofunction:: cuspatial.trajectory_distances_and_speeds
.. autofunction:: cuspatial.directed_hausdorff_distance
.. autofunction:: cuspatial.trajectory_bounding_boxes
1 change: 1 addition & 0 deletions python/cuspatial/cuspatial/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
lonlat_to_cartesian,
pairwise_linestring_distance,
pairwise_point_linestring_distance,
pairwise_point_linestring_nearest_points,
polygon_bounding_boxes,
polyline_bounding_boxes,
point_in_polygon,
Expand Down
1 change: 1 addition & 0 deletions python/cuspatial/cuspatial/_lib/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
set(cython_sources
hausdorff.pyx
interpolate.pyx
nearest_points.pyx
point_in_polygon.pyx
polygon_bounding_boxes.pyx
polyline_bounding_boxes.pyx
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Copyright (c) 2022, NVIDIA CORPORATION.

from libcpp.memory cimport unique_ptr

from cudf._lib.column cimport column, column_view

from cuspatial._lib.cpp.optional cimport optional


cdef extern from "cuspatial/point_linestring_nearest_points.hpp" \
namespace "cuspatial" nogil:

cdef struct point_linestring_nearest_points_result:
optional[unique_ptr[column]] nearest_point_geometry_id
optional[unique_ptr[column]] nearest_linestring_geometry_id
unique_ptr[column] nearest_segment_id
unique_ptr[column] nearest_point_on_linestring_xy

cdef point_linestring_nearest_points_result \
pairwise_point_linestring_nearest_points(
const optional[column_view] multipoint_geometry_offsets,
const column_view points_xy,
const optional[column_view] multilinestring_geometry_offsets,
const column_view linestring_part_offsets,
const column_view linestring_points_xy,
) except +
78 changes: 78 additions & 0 deletions python/cuspatial/cuspatial/_lib/nearest_points.pyx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
from libcpp.memory cimport unique_ptr
from libcpp.utility cimport move

from cudf._lib.column cimport Column
from cudf._lib.cpp.column.column cimport column
from cudf._lib.cpp.column.column_view cimport column_view

from cuspatial._lib.cpp.optional cimport nullopt, optional
from cuspatial._lib.cpp.point_linestring_nearest_points cimport (
pairwise_point_linestring_nearest_points as c_func,
point_linestring_nearest_points_result,
)


def pairwise_point_linestring_nearest_points(
Column points_xy,
Column linestring_part_offsets,
Column linestring_points_xy,
multipoint_geometry_offset=None,
multilinestring_geometry_offset=None,
):
cdef Column multipoint_geometry_offset_column
cdef Column multilinestring_geometry_offset_column
cdef optional[column_view] c_multipoint_geometry_offset
cdef optional[column_view] c_multilinestring_geometry_offset

if multipoint_geometry_offset is not None:
multipoint_geometry_offset_column = multipoint_geometry_offset
c_multipoint_geometry_offset = multipoint_geometry_offset_column.view()
isVoid marked this conversation as resolved.
Show resolved Hide resolved
else:
c_multipoint_geometry_offset = nullopt

if multilinestring_geometry_offset is not None:
multilinestring_geometry_offset_column = (
multilinestring_geometry_offset
)
c_multilinestring_geometry_offset = (
multilinestring_geometry_offset_column.view()
)
isVoid marked this conversation as resolved.
Show resolved Hide resolved
else:
c_multilinestring_geometry_offset = nullopt

cdef column_view c_points_xy = points_xy.view()
cdef column_view c_linestring_offsets = linestring_part_offsets.view()
cdef column_view c_linestring_points_xy = linestring_points_xy.view()
cdef point_linestring_nearest_points_result c_result

with nogil:
c_result = move(c_func(
c_multipoint_geometry_offset,
c_points_xy,
c_multilinestring_geometry_offset,
c_linestring_offsets,
c_linestring_points_xy,
))

if multipoint_geometry_offset is not None:
point_geometry_id = Column.from_unique_ptr(
move(c_result.nearest_point_geometry_id.value()))
else:
point_geometry_id = None

if multilinestring_geometry_offset is not None:
linestring_geometry_id = Column.from_unique_ptr(
move(c_result.nearest_linestring_geometry_id.value()))
else:
linestring_geometry_id = None

segment_id = Column.from_unique_ptr(move(c_result.nearest_segment_id))
point_on_linestring_xy = Column.from_unique_ptr(
move(c_result.nearest_point_on_linestring_xy))

return (
point_geometry_id,
linestring_geometry_id,
segment_id,
point_on_linestring_xy
)
41 changes: 39 additions & 2 deletions python/cuspatial/cuspatial/core/_column/geocolumn.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
import pyarrow as pa

import cudf
from cudf.core.column import ColumnBase, as_column
from cudf.core.column import ColumnBase, as_column, build_list_column

from cuspatial.core._column.geometa import GeoMeta
from cuspatial.core._column.geometa import Feature_Enum, GeoMeta

T = TypeVar("T", bound="GeoColumn")

Expand Down Expand Up @@ -181,3 +181,40 @@ def copy(self, deep=True):
self.data.copy(),
)
return result

@classmethod
def _from_points_xy(cls, points_xy: ColumnBase):
"""
Create a GeoColumn of only single points from a cudf Series with
interleaved xy coordinates.
"""
if len(points_xy) % 2 != 0:
raise ValueError("points_xy must have an even number of elements")

num_points = len(points_xy) // 2
meta = GeoMeta(
{
"input_types": as_column(
cp.full(
num_points, Feature_Enum.POINT.value, dtype=cp.int8
)
),
"union_offsets": as_column(
cp.arange(num_points, dtype=cp.int32)
),
}
)

indices = as_column(cp.arange(0, num_points * 2 + 1, 2), dtype="int32")
point_col = build_list_column(
indices=indices, elements=points_xy, size=num_points
)
return cls(
(
cudf.Series(point_col),
cudf.Series(),
cudf.Series(),
cudf.Series(),
),
meta,
)
8 changes: 8 additions & 0 deletions python/cuspatial/cuspatial/core/_column/geometa.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,19 @@
# This allows GeoMeta as its own init type
from __future__ import annotations

from enum import Enum
from typing import Union

import cudf


class Feature_Enum(Enum):
POINT = 0
MULTIPOINT = 1
LINESTRING = 2
POLYGON = 3


class GeoMeta:
"""
Creates input_types and union_offsets for GeoColumns that are created
Expand Down
12 changes: 7 additions & 5 deletions python/cuspatial/cuspatial/core/geoseries.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from functools import cached_property
from numbers import Integral
from typing import Tuple, TypeVar, Union
from typing import Optional, Tuple, TypeVar, Union

import cupy as cp
import geopandas as gpd
Expand All @@ -22,8 +22,8 @@
import cudf

import cuspatial.io.pygeoarrow as pygeoarrow
from cuspatial.core._column.geocolumn import GeoColumn, GeoMeta
from cuspatial.io.geopandas_reader import Feature_Enum
from cuspatial.core._column.geocolumn import GeoColumn
from cuspatial.core._column.geometa import Feature_Enum, GeoMeta

T = TypeVar("T", bound="GeoSeries")

Expand All @@ -46,14 +46,16 @@ class GeoSeries(cudf.Series):

def __init__(
self,
data: Union[gpd.GeoSeries, Tuple, T, pd.Series, GeoColumn],
data: Optional[
Union[gpd.GeoSeries, Tuple, T, pd.Series, GeoColumn, list]
],
index: Union[cudf.Index, pd.Index] = None,
dtype=None,
name=None,
nan_as_null=True,
):
# Condition data
if isinstance(data, pd.Series):
if data is None or isinstance(data, (pd.Series, list)):
data = gpGeoSeries(data)
# Create column
if isinstance(data, GeoColumn):
Expand Down
4 changes: 4 additions & 0 deletions python/cuspatial/cuspatial/core/spatial/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,16 @@
lonlat_to_cartesian,
)

from .nearest_points import pairwise_point_linestring_nearest_points

__all__ = [
"directed_hausdorff_distance",
"haversine_distance",
"join_quadtree_and_bounding_boxes",
"lonlat_to_cartesian",
"pairwise_linestring_distance",
"pairwise_point_linestring_distance",
"pairwise_point_linestring_nearest_points",
"polygon_bounding_boxes",
"polyline_bounding_boxes",
"point_in_polygon",
Expand Down
107 changes: 107 additions & 0 deletions python/cuspatial/cuspatial/core/spatial/nearest_points.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import cupy as cp

import cuspatial._lib.nearest_points as nearest_points
from cuspatial.core._column.geocolumn import GeoColumn
from cuspatial.core.geodataframe import GeoDataFrame
from cuspatial.core.geoseries import GeoSeries
from cuspatial.utils.column_utils import (
contains_only_linestrings,
contains_only_points,
)


def pairwise_point_linestring_nearest_points(
points: GeoSeries, linestrings: GeoSeries
) -> GeoDataFrame:
"""Returns the nearest points between two GeoSeries of points and
isVoid marked this conversation as resolved.
Show resolved Hide resolved
linestrings.

Parameters
----------
points : GeoSeries
A GeoSeries of points. Currently either only a series of points or
a series of multi-points is supported.
linestrings : GeoSeries
A GeoSeries of linestrings.
isVoid marked this conversation as resolved.
Show resolved Hide resolved

Returns
-------
GeoDataFrame
A GeoDataFrame with four columns.

- "point_geometry_id" contains index of the nearest point in the row.
If `points` consists of single-points, it is always 0.
- "linestring_geometry_id" contains the index of the linestring in the
multilinestring that contains the nearest point.
- "segment_id" contains the index of the segment in the linestring that
contains the nearest point.
- "geometry" contains the points of the nearest
point on the linestring.
"""

if len(points) != len(linestrings):
raise ValueError(
"The inputs should have the same number of geometries"
)

if len(points) == 0:
data = {
"point_geometry_id": [],
"linestring_geometry_id": [],
"segment_id": [],
"geometry": GeoSeries([]),
}
return GeoDataFrame._from_data(data)

if not contains_only_points(points):
raise ValueError("`points` must contain only point geometries.")

if not contains_only_linestrings(linestrings):
raise ValueError(
"`linestrings` must contain only linestring geometries."
)

if len(points.points.xy) > 0 and len(points.multipoints.xy) > 0:
raise NotImplementedError(
"Mixing points and multipoint geometries in `points` is not yet "
"supported."
)

points_xy = (
points.points.xy
if len(points.points.xy) > 0
else points.multipoints.xy
)
points_geometry_offset = (
None
if len(points.points.xy) > 0
else points.multipoints.geometry_offset._column
)

(
point_geometry_id,
linestring_geometry_id,
segment_id,
point_on_linestring_xy,
) = nearest_points.pairwise_point_linestring_nearest_points(
points_xy._column,
linestrings.lines.part_offset._column,
linestrings.lines.xy._column,
points_geometry_offset,
linestrings.lines.geometry_offset._column,
)

point_on_linestring = GeoColumn._from_points_xy(point_on_linestring_xy)
nearest_points_on_linestring = GeoSeries(point_on_linestring)

if not point_geometry_id:
point_geometry_id = cp.zeros(len(points), dtype=cp.int32)

data = {
"point_geometry_id": point_geometry_id,
"linestring_geometry_id": linestring_geometry_id,
"segment_id": segment_id,
"geometry": nearest_points_on_linestring,
}

return GeoDataFrame._from_data(data)
Loading