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

Add column API for pairwise_point_polygon_distance #984

Merged
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
965b80a
initial
isVoid Feb 28, 2023
38af8e6
Merge branch 'branch-23.04' of https://github.com/rapidsai/cuspatial …
isVoid Mar 1, 2023
02f61fe
add pragma once for floating_point.cuh
isVoid Mar 7, 2023
7bd797f
add polygon_ref structure
isVoid Mar 7, 2023
0968c15
add multipolygon_ref class
isVoid Mar 7, 2023
a659eab
update multipolygon_range class
isVoid Mar 7, 2023
c274070
update multipoint_range class
isVoid Mar 7, 2023
12ffa53
update is_point_in_polygon usage with polygon_ref
isVoid Mar 7, 2023
7490333
update multilinestring_range
isVoid Mar 7, 2023
f665287
add point to polygon kernel
isVoid Mar 7, 2023
291f6e6
add segment deduction guide
isVoid Mar 7, 2023
efa6883
add owning object type to vector factories
isVoid Mar 7, 2023
23146ef
add tests
isVoid Mar 7, 2023
ead160a
add helper files
isVoid Mar 7, 2023
09bd35f
add more tests
isVoid Mar 8, 2023
92760d1
bug fixes
isVoid Mar 8, 2023
8acb5dc
cleanups
isVoid Mar 8, 2023
a2b94fe
fix tests
isVoid Mar 8, 2023
46a67fe
optimize single point range input
isVoid Mar 8, 2023
b725b52
docs, type checks in range ctor
isVoid Mar 8, 2023
cb5706a
Merge branch 'branch-23.04' into feature/polygon_distances
isVoid Mar 8, 2023
ab59e7d
use range based for loop in is_point_in_polygon
isVoid Mar 8, 2023
ddcd5d2
initial column API
isVoid Mar 9, 2023
b136c0b
Apply suggestions from code review
isVoid Mar 9, 2023
744f32f
add docs
isVoid Mar 9, 2023
bb6c637
style
isVoid Mar 9, 2023
756650b
Merge branch 'branch-23.04' of https://github.com/rapidsai/cuspatial …
isVoid Mar 9, 2023
ec23d6e
add column API tests, augment column_factories
isVoid Mar 10, 2023
8319e7c
fix bug in PiP tests
isVoid Mar 10, 2023
85fab66
Merge branch 'feature/polygon_distances' into feature/point_polygon_d…
isVoid Mar 10, 2023
43cc02a
add validation checks
isVoid Mar 10, 2023
386ad1f
Merge branch 'branch-23.04' of https://github.com/rapidsai/cuspatial …
isVoid Mar 21, 2023
402a8e3
remove unused code pieces and files
isVoid Mar 21, 2023
67c931b
Merge branch 'branch-23.04' into feature/point_polygon_distance_colum…
isVoid Mar 21, 2023
3f1da02
address docs review
isVoid Mar 21, 2023
4b33176
Merge branch 'feature/point_polygon_distance_column_api' of github.co…
isVoid Mar 21, 2023
961f6ce
Merge branch 'branch-23.04' into feature/point_polygon_distance_colum…
isVoid Mar 21, 2023
50b6c6b
style
isVoid Mar 21, 2023
375fae1
Merge branch 'feature/point_polygon_distance_column_api' of github.co…
isVoid Mar 21, 2023
16379c2
address reviews
isVoid Mar 21, 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 cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -133,6 +133,7 @@ add_library(cuspatial
src/spatial/linestring_intersection.cu
src/spatial/point_distance.cu
src/spatial/point_linestring_distance.cu
src/spatial/point_polygon_distance.cu
src/spatial/point_linestring_nearest_points.cu
src/spatial/sinusoidal_projection.cu
src/trajectory/derive_trajectories.cu
28 changes: 28 additions & 0 deletions cpp/include/cuspatial/detail/utility/validation.hpp
Original file line number Diff line number Diff line change
@@ -48,3 +48,31 @@
"Each polygon must have at least one (1) ring"); \
CUSPATIAL_EXPECTS(num_poly_points >= 4 * (num_poly_ring_offsets - 1), \
"Each ring must have at least four (4) vertices");

/**
* @brief Macro for validating the data array sizes for a multipolygon.
*
* Raises an exception if any of the following are false:
* - The number of multipolygon offsets is greater than zero.
* - The number of polygon offsets is greater than zero.
* - The number of ring offsets is greater than zero.
* - There is at least one ring offset per polygon offset.
* - There are at least four vertices per ring offset.
*
* MultiPolygons follow [GeoArrow data layout][1]. Offsets arrays (polygons and rings) have one more
* element than the number of items in the array. The last offset is always the sum of the previous
* offset and the size of that element. For example the last value in the ring offsets array is the
* last ring offset plus the number of rings in the last polygon. See
* [Arrow Variable-Size Binary layout](2). Note that an empty list still has one offset: {0}.
*
* Rings are assumed to be closed (closed means the first and last vertices of
* each ring are equal). Therefore rings must have at least 4 vertices.
*
* [1]: https://github.com/geoarrow/geoarrow/blob/main/format.md
* [2]: https://arrow.apache.org/docs/format/Columnar.html#variable-size-binary-layout
*/
#define CUSPATIAL_EXPECTS_VALID_MULTIPOLYGON_SIZES( \
num_poly_points, num_multipoly_offsets, num_poly_offsets, num_poly_ring_offsets) \
CUSPATIAL_EXPECTS(num_multipoly_offsets > 0, \
"Multipolygon offsets must contain at least one (1) value"); \
CUSPATIAL_EXPECTS_VALID_POLYGON_SIZES(num_poly_points, num_poly_offsets, num_poly_ring_offsets);
49 changes: 49 additions & 0 deletions cpp/include/cuspatial/distance/point_polygon_distance.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Copyright (c) 2023, NVIDIA CORPORATION.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#pragma once

#include <cuspatial/column/geometry_column_view.hpp>

#include <cudf/column/column_view.hpp>

#include <optional>

namespace cuspatial {

/**
* @ingroup distance
* @brief Compute pairwise (multi)point-to-(multi)polygon Cartesian distance
*
* @param multipoints Geometry column of multipoints
* @param multipolygons Geometry column of multipolygons
* @param mr Device memory resource used to allocate the returned column.
* @return Column of distances between each pair of input geometries, same type as input coordinate
* types.
*
* @throw cuspatial::logic_error if `multipoints` and `multipolygons` has different coordinate
* types.
* @throw cuspatial::logic_error if `multipoints` is not a point column and `multipolygons` is not a
* polygon column.
* @throw cuspatial::logic_error if input column sizes mismatch.
*/

std::unique_ptr<cudf::column> pairwise_point_polygon_distance(
geometry_column_view const& multipoints,
geometry_column_view const& multipolygons,
rmm::mr::device_memory_resource* mr = rmm::mr::get_current_device_resource());

} // namespace cuspatial
Original file line number Diff line number Diff line change
@@ -40,6 +40,13 @@ namespace detail {
* @return boolean to indicate if point is inside the polygon.
* `false` if point is on the edge of the polygon.
*
* @tparam T type of coordinate
* @tparam PolygonRef polygon_ref type
* @param test_point point to test for point in polygon
* @param polygon polygon to test for point in polygon
* @return boolean to indicate if point is inside the polygon.
* `false` if point is on the edge of the polygon.
*
* TODO: the ultimate goal of refactoring this as independent function is to remove
* src/utility/point_in_polygon.cuh and its usage in quadtree_point_in_polygon.cu. It isn't
* possible today without further work to refactor quadtree_point_in_polygon into header only
Original file line number Diff line number Diff line change
@@ -23,6 +23,7 @@

#include <cuspatial/cuda_utils.hpp>
#include <cuspatial/detail/iterator.hpp>
#include <cuspatial/detail/utility/validation.hpp>
#include <cuspatial/experimental/geometry/segment.cuh>
#include <cuspatial/experimental/geometry_collection/multipolygon_ref.cuh>
#include <cuspatial/traits.hpp>
@@ -32,7 +33,6 @@
#include <optional>

namespace cuspatial {

using namespace detail;

template <typename GeometryIterator,
@@ -104,7 +104,10 @@ multipolygon_range<GeometryIterator, PartIterator, RingIterator, VecIterator>::m
_point_end(point_end)
{
static_assert(is_vec_2d<iterator_value_type<VecIterator>>(),
"Coordinate range must be constructed with iterators to vec_2d.");
"Point iterator must be iterators to floating point vec_2d types.");

CUSPATIAL_EXPECTS_VALID_MULTIPOLYGON_SIZES(
num_points(), num_multipolygons() + 1, num_polygons() + 1, num_rings() + 1);
}

template <typename GeometryIterator,
Original file line number Diff line number Diff line change
@@ -245,7 +245,7 @@ auto make_multilinestring_range(GeometryColumnView const& linestrings_column)
"Must be Linestring geometry type.");
auto geometry_iter = thrust::make_counting_iterator(0);
auto const& part_offsets = linestrings_column.offsets();
auto const& points_xy = linestrings_column.child().child(1);
auto const& points_xy = linestrings_column.child().child(1); // Ignores x-y offset {0, 2, 4...}

auto points_it = make_vec_2d_iterator(points_xy.template begin<T>());

@@ -276,7 +276,7 @@ auto make_multilinestring_range(GeometryColumnView const& linestrings_column)
auto const& geometry_offsets = linestrings_column.offsets();
auto const& parts = linestrings_column.child();
auto const& part_offsets = parts.child(0);
auto const& points_xy = parts.child(1).child(1);
auto const& points_xy = parts.child(1).child(1); // Ignores x-y offset {0, 2, 4...}

auto points_it = make_vec_2d_iterator(points_xy.template begin<T>());

59 changes: 57 additions & 2 deletions cpp/include/cuspatial/experimental/ranges/multipoint_range.cuh
Original file line number Diff line number Diff line change
@@ -18,6 +18,7 @@

#include <cuspatial/cuda_utils.hpp>
#include <cuspatial/traits.hpp>
#include <cuspatial/types.hpp>

namespace cuspatial {

@@ -156,7 +157,7 @@ class multipoint_range {
};

/**
* @brief Create a multilinestring_range object of from size and start iterators
* @brief Create a multipoint_range object of from size and start iterators
* @ingroup ranges
*
* @tparam GeometryIteratorDiffType Index type of the size of the geometry array
@@ -192,7 +193,7 @@ multipoint_range<GeometryIterator, VecIterator> make_multipoint_range(
}

/**
* @brief Create multilinestring_range object from offset and point ranges
* @brief Create multipoint_range object from offset and point ranges
*
* @tparam IntegerRange Range to integers
* @tparam PointRange Range to points
@@ -208,6 +209,60 @@ auto make_multipoint_range(IntegerRange geometry_offsets, PointRange points)
geometry_offsets.begin(), geometry_offsets.end(), points.begin(), points.end());
}

/**
* @ingroup ranges
* @brief Create a range object of multipoints from cuspatial::geometry_column_view.
* Specialization for points column.
*
* @pre points_column must be a cuspatial::geometry_column_view
*/
template <collection_type_id Type,
typename T,
typename IndexType,
CUSPATIAL_ENABLE_IF(Type == collection_type_id::SINGLE),
typename GeometryColumnView>
auto make_multipoint_range(GeometryColumnView const& points_column)
{
CUSPATIAL_EXPECTS(points_column.geometry_type() == geometry_type_id::POINT,
"Must be POINT geometry type.");
auto geometry_iter = thrust::make_counting_iterator(0);
auto const& points_xy = points_column.child(); // Ignores x-y offset {0, 2, 4...}

auto points_it = make_vec_2d_iterator(points_xy.template begin<T>());

return multipoint_range(geometry_iter,
thrust::next(geometry_iter, points_column.size() + 1),
points_it,
points_it + points_xy.size() / 2);
}

/**
* @ingroup ranges
* @brief Create a range object of multipoints from cuspatial::geometry_column_view.
* Specialization for multipoints column.
*
* @pre multipoints_column must be a cuspatial::geometry_column_view
*/
template <collection_type_id Type,
typename T,
typename IndexType,
CUSPATIAL_ENABLE_IF(Type == collection_type_id::MULTI),
typename GeometryColumnView>
auto make_multipoint_range(GeometryColumnView const& points_column)
{
CUSPATIAL_EXPECTS(points_column.geometry_type() == geometry_type_id::POINT,
"Must be POINT geometry type.");
auto const& geometry_offsets = points_column.offsets();
auto const& points_xy = points_column.child().child(1); // Ignores x-y offset {0, 2, 4...}

auto points_it = make_vec_2d_iterator(points_xy.template begin<T>());

return multipoint_range(geometry_offsets.template begin<IndexType>(),
geometry_offsets.template end<IndexType>(),
points_it,
points_it + points_xy.size() / 2);
};

} // namespace cuspatial

#include <cuspatial/experimental/detail/ranges/multipoint_range.cuh>
Original file line number Diff line number Diff line change
@@ -152,6 +152,74 @@ class multipolygon_range {
CUSPATIAL_HOST_DEVICE bool is_valid_segment_id(IndexType1 segment_idx, IndexType2 ring_idx);
};

/**
* @ingroup ranges
* @brief Create a range object of multipolygon from cuspatial::geometry_column_view.
* Specialization for polygons column.
*
* @pre polygons_column must be a cuspatial::geometry_column_view
*/
template <collection_type_id Type,
typename T,
typename IndexType,
typename GeometryColumnView,
CUSPATIAL_ENABLE_IF(Type == collection_type_id::SINGLE)>
auto make_multipolygon_range(GeometryColumnView const& polygons_column)
{
CUSPATIAL_EXPECTS(polygons_column.geometry_type() == geometry_type_id::POLYGON,
"Must be polygon geometry type.");
auto geometry_iter = thrust::make_counting_iterator(0);
auto const& part_offsets = polygons_column.offsets();
auto const& ring_offsets = polygons_column.child().child(0);
auto const& points_xy =
polygons_column.child().child(1).child(1); // Ignores x-y offset {0, 2, 4...}

auto points_it = make_vec_2d_iterator(points_xy.template begin<T>());

return multipolygon_range(geometry_iter,
geometry_iter + part_offsets.size(),
part_offsets.template begin<IndexType>(),
part_offsets.template end<IndexType>(),
ring_offsets.template begin<IndexType>(),
ring_offsets.template end<IndexType>(),
points_it,
points_it + points_xy.size() / 2);
}

/**
* @ingroup ranges
* @brief Create a range object of multipolygon from cuspatial::geometry_column_view.
* Specialization for multipolygons column.
*
* @pre polygon_column must be a cuspatial::geometry_column_view
*/
template <collection_type_id Type,
typename T,
typename IndexType,
CUSPATIAL_ENABLE_IF(Type == collection_type_id::MULTI),
typename GeometryColumnView>
auto make_multipolygon_range(GeometryColumnView const& polygons_column)
{
CUSPATIAL_EXPECTS(polygons_column.geometry_type() == geometry_type_id::POLYGON,
"Must be polygon geometry type.");
auto const& geometry_offsets = polygons_column.offsets();
auto const& part_offsets = polygons_column.child().child(0);
auto const& ring_offsets = polygons_column.child().child(1).child(0);
auto const& points_xy =
polygons_column.child().child(1).child(1).child(1); // Ignores x-y offset {0, 2, 4...}

auto points_it = make_vec_2d_iterator(points_xy.template begin<T>());

return multipolygon_range(geometry_offsets.template begin<IndexType>(),
geometry_offsets.template end<IndexType>(),
part_offsets.template begin<IndexType>(),
part_offsets.template end<IndexType>(),
ring_offsets.template begin<IndexType>(),
ring_offsets.template end<IndexType>(),
points_it,
points_it + points_xy.size() / 2);
};

} // namespace cuspatial

#include <cuspatial/experimental/detail/ranges/multipolygon_range.cuh>
285 changes: 285 additions & 0 deletions cpp/include/cuspatial_test/column_factories.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,285 @@
/*
* Copyright (c) 2023, NVIDIA CORPORATION.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#include <cuspatial/types.hpp>

#include <cudf/column/column_factories.hpp>
#include <cudf/filling.hpp>
#include <cudf/scalar/scalar_factories.hpp>

#include <cudf_test/column_wrapper.hpp>

#include <initializer_list>
#include <memory>
#include <rmm/cuda_stream_view.hpp>

namespace cuspatial {
namespace test {

using namespace cudf;
using namespace cudf::test;

std::unique_ptr<column> coords_offsets(size_type num_points, rmm::cuda_stream_view stream)
{
auto zero = make_fixed_width_scalar<size_type>(0, stream);
auto two = make_fixed_width_scalar<size_type>(2, stream);

return sequence(num_points + 1, *zero, *two);
}

template <typename T>
std::unique_ptr<column> make_non_nullable_lists_column(std::unique_ptr<column> offset,
std::unique_ptr<column> child)
{
auto size = offset->size() - 1;
return make_lists_column(size, std::move(offset), std::move(child), 0, {});
}

template <typename T>
std::unique_ptr<column> make_non_nullable_lists_column(std::initializer_list<size_type> offset,
std::unique_ptr<column> child)
{
auto d_offset = fixed_width_column_wrapper<size_type>(offset).release();
return make_non_nullable_lists_column<T>(std::move(d_offset), std::move(child));
}

template <typename T>
std::unique_ptr<column> make_non_nullable_lists_column(std::unique_ptr<column> offset,
std::initializer_list<T> child)
{
auto d_child = fixed_width_column_wrapper<T>(child).release();
return make_non_nullable_lists_column<T>(std::move(offset), std::move(d_child));
}

/**
* @brief helper function to make a point column
*
* A point column has cudf type LIST<FLOAT | DOUBLE>
*
* Example:
* [POINT (0 0), POINT (1 1), POINT (2 2)]
* Offset 0 2 4 6
* Child 0 0 1 1 2 2
*
* @tparam T Coordinate value type
* @param point_coords interleaved x-y coordinates of the points
* @param stream The CUDA stream on which to perform computations
*
* @return Intersection Result
* @return A cudf LIST column with point data
*/
template <typename T>
std::pair<collection_type_id, std::unique_ptr<cudf::column>> make_point_column(
std::initializer_list<T>&& point_coords, rmm::cuda_stream_view stream)
{
auto num_points = point_coords.size() / 2;

return {collection_type_id::SINGLE,
make_non_nullable_lists_column<T>(coords_offsets(num_points, stream), point_coords)};
}

/**
* @brief helper function to make a multipoint column
*
* A multipoint column has cudf type LIST<LIST<FLOAT | DOUBLE>>
*
* Example:
* [MULTIPOINT (POINT (0 0), POINT (1 1)), MULTIPOINT (POINT (2 2), POINT (3 3))]
* Offset 0 2 5
* Offset 0 2 4 6 8
* Child 0 0 1 1 2 2 3 3
*
* @tparam T Coordinate value type
* @param multipoint_offsets Offset to the starting position for each multipoint
* @param point_coords Interleaved x-y coordinates of the points
* @param stream The CUDA stream on which to perform computations
*
* @return A cudf LIST column with multipoint data
*/
template <typename T>
std::pair<collection_type_id, std::unique_ptr<cudf::column>> make_point_column(
std::initializer_list<cudf::size_type>&& multipoint_offsets,
std::initializer_list<T> point_coords,
rmm::cuda_stream_view stream)
{
auto num_points = point_coords.size() / 2;

return {collection_type_id::MULTI,
make_non_nullable_lists_column<T>(
multipoint_offsets,
make_non_nullable_lists_column<T>(coords_offsets(num_points, stream), point_coords))};
}

/**
* @brief helper function to make a linestring column
*
* A linestring column has cudf type LIST<LIST<FLOAT | DOUBLE>>
*
* Example:
* [LINESTRING (0 0, 1 1, 2 2), LINESTRING (3 3, 4 4)]
* Offset 0 3 5
* Offset 0 2 4 6 8
* Child 0 0 1 1 2 2 3 3 4 4
*
* @tparam T Coordinate value type
* @param linestring_offsets Offset to the starting position for each linestring
* @param point_coords Interleaved x-y coordinates of the points
* @param stream The CUDA stream on which to perform computations
*
* @return A cudf LIST column with linestring data
*/
template <typename T>
std::pair<collection_type_id, std::unique_ptr<cudf::column>> make_linestring_column(
std::initializer_list<cudf::size_type>&& linestring_offsets,
std::initializer_list<T>&& linestring_coords,
rmm::cuda_stream_view stream)
{
auto num_points = linestring_coords.size() / 2;

return {
collection_type_id::SINGLE,
make_non_nullable_lists_column<T>(
linestring_offsets,
make_non_nullable_lists_column<T>(coords_offsets(num_points, stream), linestring_coords))};
}

/**
* @brief helper function to make a multilinestring column
*
* A multilinestring column has cudf type LIST<LIST<LIST<FLOAT | DOUBLE>>>
*
* Example:
* [
* MULTILINESTRING (LINESTRING (0 0, 1 1), LINESTRING (2 2, 3 3)),
* MULTILINESTRING (LINESTRING (4 4, 5 5))
* ]
* Offset 0 2 3
* Offset 0 2 4 6
* Offset 0 2 4 6 8 10
* Child 0 0 1 1 2 2 3 3 4 4 5 5
*
* @tparam T Coordinate value type
* @param multilinestring_offsets Offset to the starting position for each multilinestring
* @param linestring_offsets Offset to the starting position for each linestring
* @param point_coords Interleaved x-y coordinates of the points
* @param stream The CUDA stream on which to perform computations
*
* @return A cudf LIST column with multilinestring data
*/
template <typename T>
std::pair<collection_type_id, std::unique_ptr<cudf::column>> make_linestring_column(
std::initializer_list<cudf::size_type>&& multilinestring_offsets,
std::initializer_list<cudf::size_type>&& linestring_offsets,
std::initializer_list<T> linestring_coords,
rmm::cuda_stream_view stream)
{
auto num_points = linestring_coords.size() / 2;
return {
collection_type_id::MULTI,
make_non_nullable_lists_column<T>(
multilinestring_offsets,
make_non_nullable_lists_column<T>(
linestring_offsets,
make_non_nullable_lists_column<T>(coords_offsets(num_points, stream), linestring_coords)))};
}

/**
* @brief helper function to make a polygon column
*
* A polygon column has cudf type LIST<LIST<LIST<FLOAT | DOUBLE>>>
*
* Example:
* [
* POLYGON ((0 0, 1 1, 0 1, 0 0), (0 0, -1 0, -1 -1, 0 0)),
* POLYGON ((3 3, 4 4, 3 4, 3 3))
* ]
* Offset 0 2 3
* Offset 0 4 8 12
* Offset 0 2 4 6 8 10 12 14 16 18 20 22 24
* Child 0 0 1 1 0 1 0 0 0 0 -1 0 -1 -1 0 0 3 3 4 4 3 4 3 3
*
* @tparam T Coordinate value type
* @param polygon_offsets Offset to the starting position for each polygon
* @param ring_offsets Offset to the starting position for each ring
* @param point_coords Interleaved x-y coordinates of the points
* @param stream The CUDA stream on which to perform computations
*
* @return A cudf LIST column with polygon data
*/
template <typename T>
std::pair<collection_type_id, std::unique_ptr<cudf::column>> make_polygon_column(
std::initializer_list<cudf::size_type>&& polygon_offsets,
std::initializer_list<cudf::size_type>&& ring_offsets,
std::initializer_list<T> polygon_coords,
rmm::cuda_stream_view stream)
{
auto num_points = polygon_coords.size() / 2;
return {
collection_type_id::SINGLE,
make_non_nullable_lists_column<T>(
polygon_offsets,
make_non_nullable_lists_column<T>(
ring_offsets,
make_non_nullable_lists_column<T>(coords_offsets(num_points, stream), polygon_coords)))};
}

/**
* @brief helper function to make a multipolygon column
*
* A multipolygon column has cudf type LIST<LIST<LIST<LIST<FLOAT | DOUBLE>>>>
*
* Example:
* [
* MULTIPOLYGON (POLYGON (0 0, 1 1, 0 1, 0 0), POLYGON (0 0, -1 0, -1 -1, 0 0)),
* MULTIPOLYGON (POLYGON ((3 3, 4 4, 3 4, 3 3))
* ]
*
* Offset 0 1 2 3
* Offset 0 4 8 12
* Offset 0 2 4 6 8 10 12 14 16 18 20 22 24
* Child 0 0 1 1 0 1 0 0 0 0 -1 0 -1 -1 0 0 3 3 4 4 3 4 3 3
*
* @tparam T Coordinate value type
* @param multipolygon_offsets Offset to the starting position for each multipolygon
* @param polygon_offsets Offset to the starting position for each polygon
* @param ring_offsets Offset to the starting position for each ring
* @param point_coords Interleaved x-y coordinates of the points
* @param stream The CUDA stream on which to perform computations
*
* @return A cudf LIST column with multipolygon data
*/
template <typename T>
std::pair<collection_type_id, std::unique_ptr<cudf::column>> make_polygon_column(
std::initializer_list<cudf::size_type>&& multipolygon_offsets,
std::initializer_list<cudf::size_type>&& polygon_offsets,
std::initializer_list<cudf::size_type>&& ring_offsets,
std::initializer_list<T> polygon_coords,
rmm::cuda_stream_view stream)
{
auto num_points = polygon_coords.size() / 2;
return {
collection_type_id::MULTI,
make_non_nullable_lists_column<T>(
multipolygon_offsets,
make_non_nullable_lists_column<T>(
polygon_offsets,
make_non_nullable_lists_column<T>(
ring_offsets,
make_non_nullable_lists_column<T>(coords_offsets(num_points, stream), polygon_coords))))};
}

} // namespace test
} // namespace cuspatial
123 changes: 123 additions & 0 deletions cpp/src/spatial/point_polygon_distance.cu
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/*
* Copyright (c) 2023, NVIDIA CORPORATION.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#include "../utility/iterator.hpp"
#include "../utility/multi_geometry_dispatch.hpp"

#include <cudf/column/column.hpp>
#include <cudf/column/column_device_view.cuh>
#include <cudf/column/column_factories.hpp>
#include <cudf/column/column_view.hpp>
#include <cudf/copying.hpp>
#include <cudf/types.hpp>
#include <cudf/utilities/traits.hpp>
#include <cudf/utilities/type_dispatcher.hpp>

#include <rmm/cuda_stream_view.hpp>

#include <cuspatial/column/geometry_column_view.hpp>
#include <cuspatial/detail/iterator.hpp>
#include <cuspatial/error.hpp>
#include <cuspatial/experimental/iterator_factory.cuh>
#include <cuspatial/experimental/point_polygon_distance.cuh>
#include <cuspatial/experimental/ranges/multipoint_range.cuh>
#include <cuspatial/experimental/ranges/multipolygon_range.cuh>
#include <cuspatial/types.hpp>

#include <thrust/iterator/counting_iterator.h>

#include <memory>
#include <type_traits>

namespace cuspatial {

namespace detail {

namespace {

template <collection_type_id is_multi_point, collection_type_id is_multi_polygon>
struct pairwise_point_polygon_distance_impl {
using SizeType = cudf::device_span<cudf::size_type const>::size_type;

template <typename T, CUDF_ENABLE_IF(std::is_floating_point_v<T>)>
std::unique_ptr<cudf::column> operator()(geometry_column_view const& multipoints,
geometry_column_view const& multipolygons,
rmm::cuda_stream_view stream,
rmm::mr::device_memory_resource* mr)
{
auto multipoints_range = make_multipoint_range<is_multi_point, T, cudf::size_type>(multipoints);
auto multipolygons_range =
make_multipolygon_range<is_multi_polygon, T, cudf::size_type>(multipolygons);

auto output = cudf::make_numeric_column(
multipoints.coordinate_type(), multipoints.size(), cudf::mask_state::UNALLOCATED, stream, mr);

cuspatial::pairwise_point_polygon_distance(
multipoints_range, multipolygons_range, output->mutable_view().begin<T>(), stream);
return output;
}

template <typename T, CUDF_ENABLE_IF(!std::is_floating_point_v<T>), typename... Args>
std::unique_ptr<cudf::column> operator()(Args&&...)

{
CUSPATIAL_FAIL("Point-polygon distance API only supports floating point coordinates.");
}
};

} // namespace

template <collection_type_id is_multi_point, collection_type_id is_multi_polygon>
struct pairwise_point_polygon_distance {
std::unique_ptr<cudf::column> operator()(geometry_column_view const& multipoints,
geometry_column_view const& multipolygons,
rmm::cuda_stream_view stream,
rmm::mr::device_memory_resource* mr)
{
return cudf::type_dispatcher(
multipoints.coordinate_type(),
pairwise_point_polygon_distance_impl<is_multi_point, is_multi_polygon>{},
multipoints,
multipolygons,
stream,
mr);
}
};

} // namespace detail

std::unique_ptr<cudf::column> pairwise_point_polygon_distance(
geometry_column_view const& multipoints,
geometry_column_view const& multipolygons,
rmm::mr::device_memory_resource* mr)
{
CUSPATIAL_EXPECTS(multipoints.geometry_type() == geometry_type_id::POINT &&
multipolygons.geometry_type() == geometry_type_id::POLYGON,
"Unexpected input geometry types.");

CUSPATIAL_EXPECTS(multipoints.coordinate_type() == multipolygons.coordinate_type(),
"Input geometries must have the same coordinate data types.");

return multi_geometry_double_dispatch<detail::pairwise_point_polygon_distance>(
multipoints.collection_type(),
multipolygons.collection_type(),
multipoints,
multipolygons,
rmm::cuda_stream_default,
mr);
}

} // namespace cuspatial
3 changes: 3 additions & 0 deletions cpp/tests/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -86,6 +86,9 @@ ConfigureTest(POINT_LINESTRING_DISTANCE_TEST
ConfigureTest(LINESTRING_DISTANCE_TEST
spatial/linestring_distance_test.cpp)

ConfigureTest(POINT_POLYGON_DISTANCE_TEST
spatial/point_polygon_distance_test.cpp)

ConfigureTest(LINESTRING_INTERSECTION_TEST
spatial/linestring_intersection_test.cpp)

220 changes: 220 additions & 0 deletions cpp/tests/spatial/point_polygon_distance_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
/*
* Copyright (c) 2023, NVIDIA CORPORATION.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#include <cuspatial/column/geometry_column_view.hpp>
#include <cuspatial/distance/point_polygon_distance.hpp>
#include <cuspatial/error.hpp>
#include <cuspatial/types.hpp>
#include <cuspatial/vec_2d.hpp>

#include <cuspatial_test/column_factories.hpp>
#include <cuspatial_test/vector_equality.hpp>

#include <cudf_test/column_utilities.hpp>
#include <cudf_test/column_wrapper.hpp>

#include <rmm/cuda_stream_view.hpp>

#include <gmock/gmock.h>
#include <gtest/gtest.h>

#include <initializer_list>

using namespace cuspatial;
using namespace cuspatial::test;

using namespace cudf;
using namespace cudf::test;

template <typename T>
struct PairwisePointPolygonDistanceTest : public ::testing::Test {
rmm::cuda_stream_view stream() { return rmm::cuda_stream_default; }

void run_single(geometry_column_view points,
geometry_column_view polygons,
std::initializer_list<T> expected)
{
auto got = pairwise_point_polygon_distance(points, polygons);
CUDF_TEST_EXPECT_COLUMNS_EQUIVALENT(*got, fixed_width_column_wrapper<T>(expected));
}
};

struct PairwisePointPolygonDistanceTestUntyped : public ::testing::Test {
rmm::cuda_stream_view stream() { return rmm::cuda_stream_default; }
};

using TestTypes = ::testing::Types<float, double>;

TYPED_TEST_CASE(PairwisePointPolygonDistanceTest, TestTypes);

TYPED_TEST(PairwisePointPolygonDistanceTest, SingleToSingleEmpty)
{
using T = TypeParam;

auto [ptype, points] = make_point_column<T>(std::initializer_list<T>{}, this->stream());

auto [polytype, polygons] =
make_polygon_column<T>({0}, {0}, std::initializer_list<T>{}, this->stream());

CUSPATIAL_RUN_TEST(this->run_single,
geometry_column_view(points->view(), ptype, geometry_type_id::POINT),
geometry_column_view(polygons->view(), polytype, geometry_type_id::POLYGON),
{});
};

TYPED_TEST(PairwisePointPolygonDistanceTest, SingleToMultiEmpty)
{
using T = TypeParam;

auto [ptype, points] = make_point_column<T>(std::initializer_list<T>{}, this->stream());

auto [polytype, polygons] =
make_polygon_column<T>({0}, {0}, {0}, std::initializer_list<T>{}, this->stream());

CUSPATIAL_RUN_TEST(this->run_single,
geometry_column_view(points->view(), ptype, geometry_type_id::POINT),
geometry_column_view(polygons->view(), polytype, geometry_type_id::POLYGON),
{});
};

TYPED_TEST(PairwisePointPolygonDistanceTest, MultiToSingleEmpty)
{
using T = TypeParam;

auto [ptype, points] = make_point_column<T>({0}, std::initializer_list<T>{}, this->stream());

auto [polytype, polygons] =
make_polygon_column<T>({0}, {0}, std::initializer_list<T>{}, this->stream());

CUSPATIAL_RUN_TEST(this->run_single,
geometry_column_view(points->view(), ptype, geometry_type_id::POINT),
geometry_column_view(polygons->view(), polytype, geometry_type_id::POLYGON),
{});
};

TYPED_TEST(PairwisePointPolygonDistanceTest, MultiToMultiEmpty)
{
using T = TypeParam;

auto [ptype, points] = make_point_column<T>({0}, std::initializer_list<T>{}, this->stream());

auto [polytype, polygons] =
make_polygon_column<T>({0}, {0}, {0}, std::initializer_list<T>{}, this->stream());

CUSPATIAL_RUN_TEST(this->run_single,
geometry_column_view(points->view(), ptype, geometry_type_id::POINT),
geometry_column_view(polygons->view(), polytype, geometry_type_id::POLYGON),
{});
};

TYPED_TEST(PairwisePointPolygonDistanceTest, SingleToSingleOnePair)
{
using T = TypeParam;

auto [ptype, points] = make_point_column<T>({0.0, 0.0}, this->stream());

auto [polytype, polygons] =
make_polygon_column<T>({0, 1}, {0, 4}, {1, 1, 1, 2, 2, 2, 1, 1}, this->stream());

CUSPATIAL_RUN_TEST(this->run_single,
geometry_column_view(points->view(), ptype, geometry_type_id::POINT),
geometry_column_view(polygons->view(), polytype, geometry_type_id::POLYGON),
{1.4142135623730951});
};

TYPED_TEST(PairwisePointPolygonDistanceTest, SingleToMultiOnePair)
{
using T = TypeParam;

auto [ptype, points] = make_point_column<T>({0.0, 0.0}, this->stream());

auto [polytype, polygons] =
make_polygon_column<T>({0, 1}, {0, 1}, {0, 4}, {1, 1, 1, 2, 2, 2, 1, 1}, this->stream());

CUSPATIAL_RUN_TEST(this->run_single,
geometry_column_view(points->view(), ptype, geometry_type_id::POINT),
geometry_column_view(polygons->view(), polytype, geometry_type_id::POLYGON),
{1.4142135623730951});
};

TYPED_TEST(PairwisePointPolygonDistanceTest, MultiToSingleOnePair)
{
using T = TypeParam;

auto [ptype, points] = make_point_column<T>({0, 1}, {0.0, 0.0}, this->stream());

auto [polytype, polygons] =
make_polygon_column<T>({0, 1}, {0, 4}, {1, 1, 1, 2, 2, 2, 1, 1}, this->stream());

CUSPATIAL_RUN_TEST(this->run_single,
geometry_column_view(points->view(), ptype, geometry_type_id::POINT),
geometry_column_view(polygons->view(), polytype, geometry_type_id::POLYGON),
{1.4142135623730951});
};

TYPED_TEST(PairwisePointPolygonDistanceTest, MultiToMultiOnePair)
{
using T = TypeParam;

auto [ptype, points] = make_point_column<T>({0, 1}, {0.0, 0.0}, this->stream());

auto [polytype, polygons] =
make_polygon_column<T>({0, 1}, {0, 1}, {0, 4}, {1, 1, 1, 2, 2, 2, 1, 1}, this->stream());

CUSPATIAL_RUN_TEST(this->run_single,
geometry_column_view(points->view(), ptype, geometry_type_id::POINT),
geometry_column_view(polygons->view(), polytype, geometry_type_id::POLYGON),
{1.4142135623730951});
};

TEST_F(PairwisePointPolygonDistanceTestUntyped, SizeMismatch)
{
auto [ptype, points] = make_point_column<float>({0, 1, 2}, {0.0, 0.0, 1.0, 1.0}, this->stream());

auto [polytype, polygons] =
make_polygon_column<float>({0, 1}, {0, 1}, {0, 4}, {1, 1, 1, 2, 2, 2, 1, 1}, this->stream());

auto points_view = geometry_column_view(points->view(), ptype, geometry_type_id::POINT);
auto polygons_view = geometry_column_view(polygons->view(), polytype, geometry_type_id::POLYGON);

EXPECT_THROW(pairwise_point_polygon_distance(points_view, polygons_view), cuspatial::logic_error);
};

TEST_F(PairwisePointPolygonDistanceTestUntyped, TypeMismatch)
{
auto [ptype, points] = make_point_column<double>({0, 1}, {0.0, 0.0}, this->stream());

auto [polytype, polygons] =
make_polygon_column<float>({0, 1}, {0, 1}, {0, 4}, {1, 1, 1, 2, 2, 2, 1, 1}, this->stream());

auto points_view = geometry_column_view(points->view(), ptype, geometry_type_id::POINT);
auto polygons_view = geometry_column_view(polygons->view(), polytype, geometry_type_id::POLYGON);

EXPECT_THROW(pairwise_point_polygon_distance(points_view, polygons_view), cuspatial::logic_error);
};

TEST_F(PairwisePointPolygonDistanceTestUntyped, WrongGeometryType)
{
auto [ltype, lines] = make_linestring_column<double>({0, 1}, {0, 1}, {0.0, 0.0}, this->stream());

auto [polytype, polygons] =
make_polygon_column<float>({0, 1}, {0, 1}, {0, 4}, {1, 1, 1, 2, 2, 2, 1, 1}, this->stream());

auto lines_view = geometry_column_view(lines->view(), ltype, geometry_type_id::LINESTRING);
auto polygons_view = geometry_column_view(polygons->view(), polytype, geometry_type_id::POLYGON);

EXPECT_THROW(pairwise_point_polygon_distance(lines_view, polygons_view), cuspatial::logic_error);
};