diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index 7aab5bc33..05ee9991d 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -126,6 +126,7 @@ add_library(cuspatial src/spatial/polygon_bounding_box.cu src/spatial/linestring_bounding_box.cu src/spatial/point_in_polygon.cu + src/spatial/pairwise_point_in_polygon.cu src/spatial_window/spatial_window.cu src/spatial/haversine.cu src/spatial/hausdorff.cu diff --git a/cpp/include/cuspatial/experimental/detail/is_point_in_polygon.cuh b/cpp/include/cuspatial/experimental/detail/is_point_in_polygon.cuh new file mode 100644 index 000000000..b9c7d5ac6 --- /dev/null +++ b/cpp/include/cuspatial/experimental/detail/is_point_in_polygon.cuh @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2022, 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 + +#include + +namespace cuspatial { +namespace detail { + +/** + * @brief Kernel to test if a point is inside a polygon. + * + * Implemented based on Eric Haines's crossings-multiply algorithm: + * See "Crossings test" section of http://erich.realtimerendering.com/ptinpoly/ + * The improvement in addenda is also addopted to remove divisions in this kernel. + * + * 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 + * API. + */ +template ::difference_type, + class Cart2dItDiffType = typename std::iterator_traits::difference_type> +__device__ inline bool is_point_in_polygon(Cart2d const& test_point, + OffsetType poly_begin, + OffsetType poly_end, + OffsetIterator ring_offsets_first, + OffsetItDiffType const& num_rings, + Cart2dIt poly_points_first, + Cart2dItDiffType const& num_poly_points) +{ + using T = iterator_vec_base_type; + + bool point_is_within = false; + bool is_colinear = false; + // for each ring + for (auto ring_idx = poly_begin; ring_idx < poly_end; ring_idx++) { + int32_t ring_idx_next = ring_idx + 1; + int32_t ring_begin = ring_offsets_first[ring_idx]; + int32_t ring_end = + (ring_idx_next < num_rings) ? ring_offsets_first[ring_idx_next] : num_poly_points; + + Cart2d b = poly_points_first[ring_end - 1]; + bool y0_flag = b.y > test_point.y; + bool y1_flag; + // for each line segment, including the segment between the last and first vertex + for (auto point_idx = ring_begin; point_idx < ring_end; point_idx++) { + Cart2d const a = poly_points_first[point_idx]; + T run = b.x - a.x; + T rise = b.y - a.y; + + // Points on the line segment are the same, so intersection is impossible. + // This is possible because we allow closed or unclosed polygons. + T constexpr zero = 0.0; + if (float_equal(run, zero) && float_equal(rise, zero)) continue; + + T rise_to_point = test_point.y - a.y; + + // colinearity test + T run_to_point = test_point.x - a.x; + is_colinear = float_equal(run * rise_to_point, run_to_point * rise); + if (is_colinear) { break; } + + y1_flag = a.y > test_point.y; + if (y1_flag != y0_flag) { + // Transform the following inequality to avoid division + // test_point.x < (run / rise) * rise_to_point + a.x + auto lhs = (test_point.x - a.x) * rise; + auto rhs = run * rise_to_point; + if (lhs < rhs != y1_flag) { point_is_within = not point_is_within; } + } + b = a; + y0_flag = y1_flag; + } + if (is_colinear) { + point_is_within = false; + break; + } + } + + return point_is_within; +} +} // namespace detail +} // namespace cuspatial diff --git a/cpp/include/cuspatial/experimental/detail/pairwise_point_in_polygon.cuh b/cpp/include/cuspatial/experimental/detail/pairwise_point_in_polygon.cuh new file mode 100644 index 000000000..8753367f6 --- /dev/null +++ b/cpp/include/cuspatial/experimental/detail/pairwise_point_in_polygon.cuh @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2022, 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 +#include +#include +#include +#include + +#include + +#include + +#include +#include + +namespace cuspatial { +namespace detail { + +template ::difference_type, + class Cart2dItBDiffType = typename std::iterator_traits::difference_type, + class OffsetItADiffType = typename std::iterator_traits::difference_type, + class OffsetItBDiffType = typename std::iterator_traits::difference_type> +__global__ void pairwise_point_in_polygon_kernel(Cart2dItA test_points_first, + Cart2dItADiffType const num_test_points, + OffsetIteratorA poly_offsets_first, + OffsetItADiffType const num_polys, + OffsetIteratorB ring_offsets_first, + OffsetItBDiffType const num_rings, + Cart2dItB poly_points_first, + Cart2dItBDiffType const num_poly_points, + OutputIt result) +{ + using Cart2d = iterator_value_type; + using OffsetType = iterator_value_type; + for (auto idx = threadIdx.x + blockIdx.x * blockDim.x; idx < num_test_points; + idx += gridDim.x * blockDim.x) { + Cart2d const test_point = test_points_first[idx]; + // for the matching polygon + OffsetType poly_begin = poly_offsets_first[idx]; + OffsetType poly_end = (idx + 1 < num_polys) ? poly_offsets_first[idx + 1] : num_rings; + bool const point_is_within = is_point_in_polygon(test_point, + poly_begin, + poly_end, + ring_offsets_first, + num_rings, + poly_points_first, + num_poly_points); + result[idx] = point_is_within; + } +} + +} // namespace detail + +template +OutputIt pairwise_point_in_polygon(Cart2dItA test_points_first, + Cart2dItA test_points_last, + OffsetIteratorA polygon_offsets_first, + OffsetIteratorA polygon_offsets_last, + OffsetIteratorB poly_ring_offsets_first, + OffsetIteratorB poly_ring_offsets_last, + Cart2dItB polygon_points_first, + Cart2dItB polygon_points_last, + OutputIt output, + rmm::cuda_stream_view stream) +{ + using T = iterator_vec_base_type; + + auto const num_test_points = std::distance(test_points_first, test_points_last); + auto const num_polys = std::distance(polygon_offsets_first, polygon_offsets_last); + auto const num_rings = std::distance(poly_ring_offsets_first, poly_ring_offsets_last); + auto const num_poly_points = std::distance(polygon_points_first, polygon_points_last); + + static_assert(is_same_floating_point>(), + "Underlying type of Cart2dItA and Cart2dItB must be the same floating point type"); + static_assert( + is_same, iterator_value_type, iterator_value_type>(), + "Inputs must be cuspatial::vec_2d"); + + static_assert(cuspatial::is_integral, + iterator_value_type>(), + "OffsetIterators must point to integral type."); + + static_assert(std::is_same_v, int32_t>, + "OutputIt must point to 32 bit integer type."); + + CUSPATIAL_EXPECTS(num_rings >= num_polys, "Each polygon must have at least one ring"); + CUSPATIAL_EXPECTS(num_poly_points >= num_polys * 4, "Each ring must have at least four vertices"); + + CUSPATIAL_EXPECTS(num_test_points == num_polys, + "Must pass in an equal number of points and polygons"); + + // TODO: introduce a validation function that checks the rings of the polygon are + // actually closed. (i.e. the first and last vertices are the same) + + auto [threads_per_block, num_blocks] = grid_1d(num_test_points); + detail::pairwise_point_in_polygon_kernel<<>>( + test_points_first, + num_test_points, + polygon_offsets_first, + num_polys, + poly_ring_offsets_first, + num_rings, + polygon_points_first, + num_poly_points, + output); + CUSPATIAL_CUDA_TRY(cudaGetLastError()); + + return output + num_test_points; +} + +} // namespace cuspatial diff --git a/cpp/include/cuspatial/experimental/detail/point_in_polygon.cuh b/cpp/include/cuspatial/experimental/detail/point_in_polygon.cuh index e9e3412f8..5232cae1f 100644 --- a/cpp/include/cuspatial/experimental/detail/point_in_polygon.cuh +++ b/cpp/include/cuspatial/experimental/detail/point_in_polygon.cuh @@ -17,6 +17,7 @@ #pragma once #include +#include #include #include @@ -30,69 +31,6 @@ namespace cuspatial { namespace detail { -/** - * @brief Kernel to test if a point is inside a polygon. - * - * Implemented based on Eric Haines's crossings-multiply algorithm: - * See "Crossings test" section of http://erich.realtimerendering.com/ptinpoly/ - * The improvement in addenda is also addopted to remove divisions in this kernel. - * - * 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 - * API. - */ -template ::difference_type, - class Cart2dItDiffType = typename std::iterator_traits::difference_type> -__device__ inline bool is_point_in_polygon(Cart2d const& test_point, - OffsetType poly_begin, - OffsetType poly_end, - OffsetIterator ring_offsets_first, - OffsetItDiffType const& num_rings, - Cart2dIt poly_points_first, - Cart2dItDiffType const& num_poly_points) -{ - using T = iterator_vec_base_type; - - bool point_is_within = false; - // for each ring - for (auto ring_idx = poly_begin; ring_idx < poly_end; ring_idx++) { - int32_t ring_idx_next = ring_idx + 1; - int32_t ring_begin = ring_offsets_first[ring_idx]; - int32_t ring_end = - (ring_idx_next < num_rings) ? ring_offsets_first[ring_idx_next] : num_poly_points; - - Cart2d b = poly_points_first[ring_end - 1]; - bool y0_flag = b.y > test_point.y; - bool y1_flag; - // for each line segment, including the segment between the last and first vertex - for (auto point_idx = ring_begin; point_idx < ring_end; point_idx++) { - Cart2d const a = poly_points_first[point_idx]; - y1_flag = a.y > test_point.y; - if (y1_flag != y0_flag) { - T run = b.x - a.x; - T rise = b.y - a.y; - T rise_to_point = test_point.y - a.y; - - // Transform the following inequality to avoid division - // test_point.x < (run / rise) * rise_to_point + a.x - auto lhs = (test_point.x - a.x) * rise; - auto rhs = run * rise_to_point; - if ((rise > 0 && lhs < rhs) || (rise < 0 && lhs > rhs)) - point_is_within = not point_is_within; - } - b = a; - y0_flag = y1_flag; - } - } - - return point_is_within; -} - template + +namespace cuspatial { + +/** + * @ingroup spatial_relationship + * + * @brief Given (point, polygon) pairs, tests whether the point of each pair is inside the polygon + * of the pair. + * + * Tests whether each point is inside a corresponding polygon. Points on the edges of the + * polygon are not considered to be inside. + * Polygons are a collection of one or more rings. Rings are a collection of three or more vertices. + * + * Each input point will map to one `int32_t` element in the output. + * + * + * @tparam Cart2dItA iterator type for point array. Must meet + * the requirements of [LegacyRandomAccessIterator][LinkLRAI] and be device-accessible. + * @tparam Cart2dItB iterator type for point array. Must meet + * the requirements of [LegacyRandomAccessIterator][LinkLRAI] and be device-accessible. + * @tparam OffsetIteratorA iterator type for offset array. Must meet + * the requirements of [LegacyRandomAccessIterator][LinkLRAI] and be device-accessible. + * @tparam OffsetIteratorB iterator type for offset array. Must meet + * the requirements of [LegacyRandomAccessIterator][LinkLRAI] and be device-accessible. + * @tparam OutputIt iterator type for output array. Must meet + * the requirements of [LegacyRandomAccessIterator][LinkLRAI], be device-accessible, mutable and + * iterate on `int32_t` type. + * + * @param test_points_first begin of range of test points + * @param test_points_last end of range of test points + * @param polygon_offsets_first begin of range of indices to the first ring in each polygon + * @param polygon_offsets_last end of range of indices to the first ring in each polygon + * @param ring_offsets_first begin of range of indices to the first point in each ring + * @param ring_offsets_last end of range of indices to the first point in each ring + * @param polygon_points_first begin of range of polygon points + * @param polygon_points_last end of range of polygon points + * @param output begin iterator to the output buffer + * @param stream The CUDA stream to use for kernel launches. + * @return iterator to one past the last element in the output buffer + * + * @note Direction of rings does not matter. + * @note This algorithm supports the ESRI shapefile format, but assumes all polygons are "clean" (as + * defined by the format), and does _not_ verify whether the input adheres to the shapefile format. + * @note The points of the rings must be explicitly closed. + * @note Overlapping rings negate each other. This behavior is not limited to a single negation, + * allowing for "islands" within the same polygon. + * @note `poly_ring_offsets` must contain only the rings that make up the polygons indexed by + * `poly_offsets`. If there are rings in `poly_ring_offsets` that are not part of the polygons in + * `poly_offsets`, results are likely to be incorrect and behavior is undefined. + * + * ``` + * poly w/two rings poly w/four rings + * +-----------+ +------------------------+ + * :███████████: :████████████████████████: + * :███████████: :██+------------------+██: + * :██████+----:------+ :██: +----+ +----+ :██: + * :██████: :██████: :██: :████: :████: :██: + * +------;----+██████: :██: :----: :----: :██: + * :███████████: :██+------------------+██: + * :███████████: :████████████████████████: + * +-----------+ +------------------------+ + * ``` + * + * @pre All point iterators must have the same `vec_2d` value type, with the same underlying + * floating-point coordinate type (e.g. `cuspatial::vec_2d`). + * @pre All offset iterators must have the same integral value type. + * @pre Output iterator must be mutable and iterate on int32_t type. + * + * @throw cuspatial::logic_error polygon has less than 1 ring. + * @throw cuspatial::logic_error polygon has less than 4 vertices. + * + * [LinkLRAI]: https://en.cppreference.com/w/cpp/named_req/RandomAccessIterator + * "LegacyRandomAccessIterator" + */ +template +OutputIt pairwise_point_in_polygon(Cart2dItA test_points_first, + Cart2dItA test_points_last, + OffsetIteratorA polygon_offsets_first, + OffsetIteratorA polygon_offsets_last, + OffsetIteratorB poly_ring_offsets_first, + OffsetIteratorB poly_ring_offsets_last, + Cart2dItB polygon_points_first, + Cart2dItB polygon_points_last, + OutputIt output, + rmm::cuda_stream_view stream = rmm::cuda_stream_default); + +} // namespace cuspatial + +#include diff --git a/cpp/include/cuspatial/pairwise_point_in_polygon.hpp b/cpp/include/cuspatial/pairwise_point_in_polygon.hpp new file mode 100644 index 000000000..76563d2fc --- /dev/null +++ b/cpp/include/cuspatial/pairwise_point_in_polygon.hpp @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2020-2022, 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 +#include +#include + +#include + +#include + +namespace cuspatial { + +/** + * @addtogroup spatial_relationship + * @{ + */ + +/** + * @brief Given (point, polygon pairs), tests whether the point of each pair is inside the polygon + * of the pair. + * + * Tests that each point is or is not inside of the polygon in the corresponding index. + * Polygons are a collection of one or more * rings. Rings are a collection of three or more + * vertices. + * + * @param[in] test_points_x: x-coordinates of points to test + * @param[in] test_points_y: y-coordinates of points to test + * @param[in] poly_offsets: beginning index of the first ring in each polygon + * @param[in] poly_ring_offsets: beginning index of the first point in each ring + * @param[in] poly_points_x: x-coordinates of polygon points + * @param[in] poly_points_y: y-coordinates of polygon points + * + * @returns A column of booleans for each point/polygon pair. + * + * @note Direction of rings does not matter. + * @note Supports open or closed polygon formats. + * @note This algorithm supports the ESRI shapefile format, but assumes all polygons are "clean" (as + * defined by the format), and does _not_ verify whether the input adheres to the shapefile format. + * @note Overlapping rings negate each other. This behavior is not limited to a single negation, + * allowing for "islands" within the same polygon. + * @note `poly_ring_offsets` must contain only the rings that make up the polygons indexed by + * `poly_offsets`. If there are rings in `poly_ring_offsets` that are not part of the polygons in + * `poly_offsets`, results are likely to be incorrect and behavior is undefined. + * + * ``` + * poly w/two rings poly w/four rings + * +-----------+ +------------------------+ + * :███████████: :████████████████████████: + * :███████████: :██+------------------+██: + * :██████+----:------+ :██: +----+ +----+ :██: + * :██████: :██████: :██: :████: :████: :██: + * +------;----+██████: :██: :----: :----: :██: + * :███████████: :██+------------------+██: + * :███████████: :████████████████████████: + * +-----------+ +------------------------+ + * ``` + */ +std::unique_ptr pairwise_point_in_polygon( + cudf::column_view const& test_points_x, + cudf::column_view const& test_points_y, + cudf::column_view const& poly_offsets, + cudf::column_view const& poly_ring_offsets, + cudf::column_view const& poly_points_x, + cudf::column_view const& poly_points_y, + rmm::mr::device_memory_resource* mr = rmm::mr::get_current_device_resource()); + +/** + * @} // end of doxygen group + */ + +} // namespace cuspatial diff --git a/cpp/src/spatial/pairwise_point_in_polygon.cu b/cpp/src/spatial/pairwise_point_in_polygon.cu new file mode 100644 index 000000000..2e5bf5e4b --- /dev/null +++ b/cpp/src/spatial/pairwise_point_in_polygon.cu @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2020, 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 +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include +#include + +namespace { + +struct pairwise_point_in_polygon_functor { + template + static constexpr bool is_supported() + { + return std::is_floating_point::value; + } + + template ()>* = nullptr, typename... Args> + std::unique_ptr operator()(Args&&...) + { + CUSPATIAL_FAIL("Non-floating point operation is not supported"); + } + + template ()>* = nullptr> + std::unique_ptr operator()(cudf::column_view const& test_points_x, + cudf::column_view const& test_points_y, + cudf::column_view const& poly_offsets, + cudf::column_view const& poly_ring_offsets, + cudf::column_view const& poly_points_x, + cudf::column_view const& poly_points_y, + rmm::cuda_stream_view stream, + rmm::mr::device_memory_resource* mr) + { + auto size = test_points_x.size(); + auto tid = cudf::type_to_id(); + auto type = cudf::data_type{tid}; + auto results = + cudf::make_fixed_width_column(type, size, cudf::mask_state::UNALLOCATED, stream, mr); + + if (results->size() == 0) { return results; } + + auto points_begin = + cuspatial::make_vec_2d_iterator(test_points_x.begin(), test_points_y.begin()); + auto polygon_offsets_begin = poly_offsets.begin(); + auto ring_offsets_begin = poly_ring_offsets.begin(); + auto polygon_points_begin = + cuspatial::make_vec_2d_iterator(poly_points_x.begin(), poly_points_y.begin()); + auto results_begin = results->mutable_view().begin(); + + cuspatial::pairwise_point_in_polygon(points_begin, + points_begin + test_points_x.size(), + polygon_offsets_begin, + polygon_offsets_begin + poly_offsets.size(), + ring_offsets_begin, + ring_offsets_begin + poly_ring_offsets.size(), + polygon_points_begin, + polygon_points_begin + poly_points_x.size(), + results_begin, + stream); + + return results; + } +}; +} // anonymous namespace + +namespace cuspatial { + +namespace detail { + +std::unique_ptr pairwise_point_in_polygon(cudf::column_view const& test_points_x, + cudf::column_view const& test_points_y, + cudf::column_view const& poly_offsets, + cudf::column_view const& poly_ring_offsets, + cudf::column_view const& poly_points_x, + cudf::column_view const& poly_points_y, + rmm::cuda_stream_view stream, + rmm::mr::device_memory_resource* mr) +{ + return cudf::type_dispatcher(test_points_x.type(), + pairwise_point_in_polygon_functor(), + test_points_x, + test_points_y, + poly_offsets, + poly_ring_offsets, + poly_points_x, + poly_points_y, + stream, + mr); +} + +} // namespace detail + +std::unique_ptr pairwise_point_in_polygon(cudf::column_view const& test_points_x, + cudf::column_view const& test_points_y, + cudf::column_view const& poly_offsets, + cudf::column_view const& poly_ring_offsets, + cudf::column_view const& poly_points_x, + cudf::column_view const& poly_points_y, + rmm::mr::device_memory_resource* mr) +{ + CUSPATIAL_EXPECTS( + test_points_x.size() == test_points_y.size() and poly_points_x.size() == poly_points_y.size(), + "All points must have both x and y values"); + + CUSPATIAL_EXPECTS(test_points_x.type() == test_points_y.type() and + test_points_x.type() == poly_points_x.type() and + test_points_x.type() == poly_points_y.type(), + "All points much have the same type for both x and y"); + + CUSPATIAL_EXPECTS(not test_points_x.has_nulls() && not test_points_y.has_nulls(), + "Test points must not contain nulls"); + + CUSPATIAL_EXPECTS(not poly_points_x.has_nulls() && not poly_points_y.has_nulls(), + "Polygon points must not contain nulls"); + + CUSPATIAL_EXPECTS(poly_ring_offsets.size() >= poly_offsets.size(), + "Each polygon must have at least one ring"); + + CUSPATIAL_EXPECTS(poly_points_x.size() >= poly_offsets.size() * 4, + "Each ring must have at least four vertices"); + + CUSPATIAL_EXPECTS(test_points_x.size() == poly_offsets.size(), + "Must pass in the same number of points as polygons."); + + return cuspatial::detail::pairwise_point_in_polygon(test_points_x, + test_points_y, + poly_offsets, + poly_ring_offsets, + poly_points_x, + poly_points_y, + rmm::cuda_stream_default, + mr); +} + +} // namespace cuspatial diff --git a/cpp/tests/CMakeLists.txt b/cpp/tests/CMakeLists.txt index 084dcdb54..db6f9d1f3 100644 --- a/cpp/tests/CMakeLists.txt +++ b/cpp/tests/CMakeLists.txt @@ -68,6 +68,9 @@ ConfigureTest(JOIN_POINT_TO_LINESTRING_SMALL_TEST ConfigureTest(POINT_IN_POLYGON_TEST spatial/point_in_polygon_test.cpp) +ConfigureTest(PAIRWISE_POINT_IN_POLYGON_TEST + spatial/pairwise_point_in_polygon_test.cpp) + ConfigureTest(POINT_QUADTREE_TEST indexing/point_quadtree_test.cu) @@ -146,6 +149,9 @@ ConfigureTest(POINTS_IN_RANGE_TEST_EXP ConfigureTest(POINT_IN_POLYGON_TEST_EXP experimental/spatial/point_in_polygon_test.cu) +ConfigureTest(PAIRWISE_POINT_IN_POLYGON_TEST_EXP + experimental/spatial/pairwise_point_in_polygon_test.cu) + ConfigureTest(DERIVE_TRAJECTORIES_TEST_EXP experimental/trajectory/derive_trajectories_test.cu) diff --git a/cpp/tests/experimental/spatial/pairwise_point_in_polygon_test.cu b/cpp/tests/experimental/spatial/pairwise_point_in_polygon_test.cu new file mode 100644 index 000000000..5a5fe62e3 --- /dev/null +++ b/cpp/tests/experimental/spatial/pairwise_point_in_polygon_test.cu @@ -0,0 +1,385 @@ +/* + * Copyright (c) 2022, 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 +#include +#include +#include + +#include + +#include +#include +#include + +#include + +using namespace cuspatial; + +template +struct PairwisePointInPolygonTest : public ::testing::Test { + public: + rmm::device_vector> make_device_points(std::initializer_list> pts) + { + return rmm::device_vector>(pts.begin(), pts.end()); + } + + rmm::device_vector make_device_offsets(std::initializer_list pts) + { + return rmm::device_vector(pts.begin(), pts.end()); + } +}; + +// float and double are logically the same but would require separate tests due to precision. +using TestTypes = ::testing::Types; +TYPED_TEST_CASE(PairwisePointInPolygonTest, TestTypes); + +TYPED_TEST(PairwisePointInPolygonTest, OnePolygonOneRing) +{ + using T = TypeParam; + auto point_list = std::vector>{{-2.0, 0.0}, + {2.0, 0.0}, + {0.0, -2.0}, + {0.0, 2.0}, + {-0.5, 0.0}, + {0.5, 0.0}, + {0.0, -0.5}, + {0.0, 0.5}}; + auto poly_offsets = this->make_device_offsets({0}); + auto poly_ring_offsets = this->make_device_offsets({0}); + auto poly_point = + this->make_device_points({{-1.0, -1.0}, {1.0, -1.0}, {1.0, 1.0}, {-1.0, 1.0}, {-1.0, -1.0}}); + + auto got = rmm::device_vector(1); + auto expected = std::vector{false, false, false, false, true, true, true, true}; + + for (size_t i = 0; i < point_list.size(); ++i) { + auto point = this->make_device_points({{point_list[i][0], point_list[i][1]}}); + auto ret = pairwise_point_in_polygon(point.begin(), + point.end(), + poly_offsets.begin(), + poly_offsets.end(), + poly_ring_offsets.begin(), + poly_ring_offsets.end(), + poly_point.begin(), + poly_point.end(), + got.begin()); + EXPECT_EQ(got, std::vector({expected[i]})); + EXPECT_EQ(ret, got.end()); + } +} + +TYPED_TEST(PairwisePointInPolygonTest, TwoPolygonsOneRingEach) +{ + using T = TypeParam; + auto point_list = std::vector>{{-2.0, 0.0}, + {2.0, 0.0}, + {0.0, -2.0}, + {0.0, 2.0}, + {-0.5, 0.0}, + {0.5, 0.0}, + {0.0, -0.5}, + {0.0, 0.5}}; + + auto poly_offsets = this->make_device_offsets({0, 1}); + auto poly_ring_offsets = this->make_device_offsets({0, 5}); + auto poly_point = this->make_device_points({{-1.0, -1.0}, + {-1.0, 1.0}, + {1.0, 1.0}, + {1.0, -1.0}, + {-1.0, -1.0}, + {0.0, 1.0}, + {1.0, 0.0}, + {0.0, -1.0}, + {-1.0, 0.0}, + {0.0, 1.0}}); + + auto got = rmm::device_vector(2); + auto expected = std::vector({false, false, false, false, true, true, true, true}); + + for (size_t i = 0; i < point_list.size() / 2; i = i + 2) { + auto points = this->make_device_points( + {{point_list[i][0], point_list[i][1]}, {point_list[i + 1][0], point_list[i + 1][1]}}); + auto ret = pairwise_point_in_polygon(points.begin(), + points.end(), + poly_offsets.begin(), + poly_offsets.end(), + poly_ring_offsets.begin(), + poly_ring_offsets.end(), + poly_point.begin(), + poly_point.end(), + got.begin()); + + EXPECT_EQ(got, std::vector({expected[i], expected[i + 1]})); + EXPECT_EQ(ret, got.end()); + } +} + +TYPED_TEST(PairwisePointInPolygonTest, OnePolygonTwoRings) +{ + using T = TypeParam; + auto point_list = + std::vector>{{0.0, 0.0}, {-0.4, 0.0}, {-0.6, 0.0}, {0.0, 0.4}, {0.0, -0.6}}; + auto poly_offsets = this->make_device_offsets({0}); + auto poly_ring_offsets = this->make_device_offsets({0, 5}); + auto poly_point = this->make_device_points({{-1.0, -1.0}, + {1.0, -1.0}, + {1.0, 1.0}, + {-1.0, 1.0}, + {-1.0, -1.0}, + {-0.5, -0.5}, + {-0.5, 0.5}, + {0.5, 0.5}, + {0.5, -0.5}, + {-0.5, -0.5}}); + + auto got = rmm::device_vector(1); + auto expected = std::vector{0b0, 0b0, 0b1, 0b0, 0b1}; + + for (size_t i = 0; i < point_list.size(); ++i) { + auto point = this->make_device_points({{point_list[i][0], point_list[i][1]}}); + auto ret = pairwise_point_in_polygon(point.begin(), + point.end(), + poly_offsets.begin(), + poly_offsets.end(), + poly_ring_offsets.begin(), + poly_ring_offsets.end(), + poly_point.begin(), + poly_point.end(), + got.begin()); + + EXPECT_EQ(got, std::vector{expected[i]}); + EXPECT_EQ(ret, got.end()); + } +} + +TYPED_TEST(PairwisePointInPolygonTest, EdgesOfSquare) +{ + auto test_point = this->make_device_points({{0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}}); + auto poly_offsets = this->make_device_offsets({0, 1, 2, 3}); + auto poly_ring_offsets = this->make_device_offsets({0, 5, 10, 15}); + + // 0: rect on min x side + // 1: rect on max x side + // 2: rect on min y side + // 3: rect on max y side + auto poly_point = this->make_device_points( + {{-1.0, -1.0}, {0.0, -1.0}, {0.0, 1.0}, {-1.0, 1.0}, {-1.0, -1.0}, {0.0, -1.0}, {1.0, -1.0}, + {1.0, 1.0}, {0.0, 1.0}, {0.0, -1.0}, {-1.0, -1.0}, {-1.0, 0.0}, {1.0, 0.0}, {1.0, -1.0}, + {-1.0, 1.0}, {-1.0, 0.0}, {-1.0, 1.0}, {1.0, 1.0}, {1.0, 0.0}, {-1.0, 0.0}}); + + auto expected = std::vector{0b0, 0b0, 0b0, 0b0}; + auto got = rmm::device_vector(test_point.size()); + + auto ret = pairwise_point_in_polygon(test_point.begin(), + test_point.end(), + poly_offsets.begin(), + poly_offsets.end(), + poly_ring_offsets.begin(), + poly_ring_offsets.end(), + poly_point.begin(), + poly_point.end(), + got.begin()); + + EXPECT_EQ(got, expected); + EXPECT_EQ(ret, got.end()); +} + +TYPED_TEST(PairwisePointInPolygonTest, CornersOfSquare) +{ + auto test_point = this->make_device_points({{0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}}); + auto poly_offsets = this->make_device_offsets({0, 1, 2, 3}); + auto poly_ring_offsets = this->make_device_offsets({0, 5, 10, 15}); + + // 0: min x min y corner + // 1: min x max y corner + // 2: max x min y corner + // 3: max x max y corner + auto poly_point = this->make_device_points( + {{-1.0, -1.0}, {-1.0, 0.0}, {0.0, 0.0}, {0.0, -1.0}, {-1.0, -1.0}, {-1.0, 0.0}, {-1.0, 1.0}, + {0.0, 1.0}, {-1.0, 0.0}, {-1.0, 0.0}, {0.0, -1.0}, {0.0, 0.0}, {1.0, 0.0}, {1.0, -1.0}, + {0.0, -1.0}, {0.0, 0.0}, {0.0, 1.0}, {1.0, 1.0}, {1.0, 0.0}, {0.0, 0.0}}); + + auto expected = std::vector{0b0, 0b0, 0b0, 0b0}; + auto got = rmm::device_vector(test_point.size()); + + auto ret = pairwise_point_in_polygon(test_point.begin(), + test_point.end(), + poly_offsets.begin(), + poly_offsets.end(), + poly_ring_offsets.begin(), + poly_ring_offsets.end(), + poly_point.begin(), + poly_point.end(), + got.begin()); + + EXPECT_EQ(got, expected); + EXPECT_EQ(ret, got.end()); +} + +struct OffsetIteratorFunctor { + std::size_t __device__ operator()(std::size_t idx) { return idx * 5; } +}; + +template +struct PolyPointIteratorFunctorA { + T __device__ operator()(std::size_t idx) + { + switch (idx % 5) { + case 0: + case 1: return -1.0; + case 2: + case 3: return 1.0; + case 4: + default: return -1.0; + } + } +}; + +template +struct PolyPointIteratorFunctorB { + T __device__ operator()(std::size_t idx) + { + switch (idx % 5) { + case 0: return -1.0; + case 1: + case 2: return 1.0; + case 3: + case 4: + default: return -1.0; + } + } +}; + +TYPED_TEST(PairwisePointInPolygonTest, 32PolygonSupport) +{ + using T = TypeParam; + + auto constexpr num_polys = 32; + auto constexpr num_poly_points = num_polys * 5; + + auto test_point = this->make_device_points( + {{0.0, 0.0}, {2.0, 0.0}, {0.0, 0.0}, {2.0, 0.0}, {0.0, 0.0}, {2.0, 0.0}, {0.0, 0.0}, + {2.0, 0.0}, {0.0, 0.0}, {2.0, 0.0}, {0.0, 0.0}, {2.0, 0.0}, {0.0, 0.0}, {2.0, 0.0}, + {0.0, 0.0}, {2.0, 0.0}, {0.0, 0.0}, {2.0, 0.0}, {0.0, 0.0}, {2.0, 0.0}, {0.0, 0.0}, + {2.0, 0.0}, {0.0, 0.0}, {2.0, 0.0}, {0.0, 0.0}, {2.0, 0.0}, {0.0, 0.0}, {2.0, 0.0}, + {0.0, 0.0}, {2.0, 0.0}, {0.0, 0.0}, {2.0, 0.0}}); + auto offsets_iter = thrust::make_counting_iterator(0); + auto poly_ring_offsets_iter = + thrust::make_transform_iterator(offsets_iter, OffsetIteratorFunctor{}); + auto poly_point_xs_iter = + thrust::make_transform_iterator(offsets_iter, PolyPointIteratorFunctorA{}); + auto poly_point_ys_iter = + thrust::make_transform_iterator(offsets_iter, PolyPointIteratorFunctorB{}); + auto poly_point_iter = make_vec_2d_iterator(poly_point_xs_iter, poly_point_ys_iter); + + auto expected = std::vector({1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0}); + auto got = rmm::device_vector(test_point.size()); + + auto ret = pairwise_point_in_polygon(test_point.begin(), + test_point.end(), + offsets_iter, + offsets_iter + num_polys, + poly_ring_offsets_iter, + poly_ring_offsets_iter + num_polys, + poly_point_iter, + poly_point_iter + num_poly_points, + got.begin()); + + EXPECT_EQ(got, expected); + EXPECT_EQ(ret, got.end()); +} + +struct PairwisePointInPolygonErrorTest : public PairwisePointInPolygonTest { +}; + +TEST_F(PairwisePointInPolygonErrorTest, MismatchPolyPointXYLength) +{ + using T = double; + + auto test_point = this->make_device_points({{0.0, 0.0}, {0.0, 0.0}}); + auto poly_offsets = this->make_device_offsets({0}); + auto poly_ring_offsets = this->make_device_offsets({0}); + auto poly_point = this->make_device_points({{0.0, 1.0}, {1.0, 0.0}, {0.0, -1.0}}); + auto got = rmm::device_vector(test_point.size()); + + EXPECT_THROW(pairwise_point_in_polygon(test_point.begin(), + test_point.end(), + poly_offsets.begin(), + poly_offsets.end(), + poly_ring_offsets.begin(), + poly_ring_offsets.end(), + poly_point.begin(), + poly_point.end(), + got.begin()), + cuspatial::logic_error); +} + +TYPED_TEST(PairwisePointInPolygonTest, SelfClosingLoopLeftEdgeMissing) +{ + using T = TypeParam; + auto point_list = std::vector>{{-2.0, 0.0}, {0.0, 0.0}, {2.0, 0.0}}; + auto poly_offsets = this->make_device_offsets({0}); + auto poly_ring_offsets = this->make_device_offsets({0}); + // "left" edge missing + auto poly_point = this->make_device_points({{-1, 1}, {1, 1}, {1, -1}, {-1, -1}}); + auto expected = std::vector{0b0, 0b1, 0b0}; + auto got = rmm::device_vector(1); + + for (size_t i = 0; i < point_list.size(); ++i) { + auto point = this->make_device_points({{point_list[i][0], point_list[i][1]}}); + auto ret = pairwise_point_in_polygon(point.begin(), + point.end(), + poly_offsets.begin(), + poly_offsets.end(), + poly_ring_offsets.begin(), + poly_ring_offsets.end(), + poly_point.begin(), + poly_point.end(), + got.begin()); + + EXPECT_EQ(std::vector{expected[i]}, got); + EXPECT_EQ(got.end(), ret); + } +} + +TYPED_TEST(PairwisePointInPolygonTest, SelfClosingLoopRightEdgeMissing) +{ + using T = TypeParam; + auto point_list = std::vector>{{-2.0, 0.0}, {0.0, 0.0}, {2.0, 0.0}}; + auto poly_offsets = this->make_device_offsets({0}); + auto poly_ring_offsets = this->make_device_offsets({0}); + // "right" edge missing + auto poly_point = this->make_device_points({{1, -1}, {-1, -1}, {-1, 1}, {1, 1}}); + auto expected = std::vector{0b0, 0b1, 0b0}; + auto got = rmm::device_vector(1); + for (size_t i = 0; i < point_list.size(); ++i) { + auto point = this->make_device_points({{point_list[i][0], point_list[i][1]}}); + auto ret = pairwise_point_in_polygon(point.begin(), + point.end(), + poly_offsets.begin(), + poly_offsets.end(), + poly_ring_offsets.begin(), + poly_ring_offsets.end(), + poly_point.begin(), + poly_point.end(), + got.begin()); + + EXPECT_EQ(std::vector{expected[i]}, got); + EXPECT_EQ(got.end(), ret); + } +} diff --git a/cpp/tests/experimental/spatial/point_in_polygon_test.cu b/cpp/tests/experimental/spatial/point_in_polygon_test.cu index 1a769c24a..cf9971b82 100644 --- a/cpp/tests/experimental/spatial/point_in_polygon_test.cu +++ b/cpp/tests/experimental/spatial/point_in_polygon_test.cu @@ -169,9 +169,7 @@ TYPED_TEST(PointInPolygonTest, EdgesOfSquare) {1.0, 1.0}, {0.0, 1.0}, {0.0, -1.0}, {-1.0, -1.0}, {-1.0, 0.0}, {1.0, 0.0}, {1.0, -1.0}, {-1.0, 1.0}, {-1.0, 0.0}, {-1.0, 1.0}, {1.0, 1.0}, {1.0, 0.0}, {-1.0, 0.0}}); - // point is included in rects on min x and y sides, but not on max x or y sides. - // this behavior is inconsistent, and not necessarily intentional. - auto expected = std::vector{0b1010}; + auto expected = std::vector{0b0000}; auto got = rmm::device_vector(test_point.size()); auto ret = point_in_polygon(test_point.begin(), @@ -203,9 +201,7 @@ TYPED_TEST(PointInPolygonTest, CornersOfSquare) {0.0, 1.0}, {-1.0, 0.0}, {-1.0, 0.0}, {0.0, -1.0}, {0.0, 0.0}, {1.0, 0.0}, {1.0, -1.0}, {0.0, -1.0}, {0.0, 0.0}, {0.0, 1.0}, {1.0, 1.0}, {1.0, 0.0}, {0.0, 0.0}}); - // point is only included on the max x max y corner. - // this behavior is inconsistent, and not necessarily intentional. - auto expected = std::vector{0b1000}; + auto expected = std::vector{0b0000}; auto got = rmm::device_vector(test_point.size()); auto ret = point_in_polygon(test_point.begin(), diff --git a/cpp/tests/spatial/pairwise_point_in_polygon_test.cpp b/cpp/tests/spatial/pairwise_point_in_polygon_test.cpp new file mode 100644 index 000000000..8e27e1042 --- /dev/null +++ b/cpp/tests/spatial/pairwise_point_in_polygon_test.cpp @@ -0,0 +1,224 @@ +/* + * Copyright (c) 2019-2020, 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 +#include + +#include +#include +#include +#include +#include + +#include + +using namespace cudf::test; + +template +using wrapper = fixed_width_column_wrapper; + +template +struct PairwisePointInPolygonTest : public BaseFixture { +}; + +// float and double are logically the same but would require separate tests due to precision. +using TestTypes = FloatingPointTypes; +TYPED_TEST_CASE(PairwisePointInPolygonTest, TestTypes); + +constexpr cudf::test::debug_output_level verbosity{cudf::test::debug_output_level::ALL_ERRORS}; + +TYPED_TEST(PairwisePointInPolygonTest, Empty) +{ + using T = TypeParam; + + auto test_point_xs = wrapper({}); + auto test_point_ys = wrapper({}); + auto poly_offsets = wrapper({}); + auto poly_ring_offsets = wrapper({}); + auto poly_point_xs = wrapper({}); + auto poly_point_ys = wrapper({}); + + auto expected = wrapper({}); + + auto actual = cuspatial::pairwise_point_in_polygon( + test_point_xs, test_point_ys, poly_offsets, poly_ring_offsets, poly_point_xs, poly_point_ys); + + expect_columns_equal(expected, actual->view(), verbosity); +} + +template +struct PairwisePointInPolygonUnsupportedTypesTest : public BaseFixture { +}; + +using UnsupportedTestTypes = RemoveIf, NumericTypes>; +TYPED_TEST_CASE(PairwisePointInPolygonUnsupportedTypesTest, UnsupportedTestTypes); + +TYPED_TEST(PairwisePointInPolygonUnsupportedTypesTest, UnsupportedPointType) +{ + using T = TypeParam; + + auto test_point_xs = wrapper({0.0}); + auto test_point_ys = wrapper({0.0}); + auto poly_offsets = wrapper({0}); + auto poly_ring_offsets = wrapper({0}); + auto poly_point_xs = wrapper({0.0, 1.0, 0.0, -1.0}); + auto poly_point_ys = wrapper({1.0, 0.0, -1.0, 0.0}); + + EXPECT_THROW( + cuspatial::pairwise_point_in_polygon( + test_point_xs, test_point_ys, poly_offsets, poly_ring_offsets, poly_point_xs, poly_point_ys), + cuspatial::logic_error); +} + +template +struct PairwisePointInPolygonUnsupportedChronoTypesTest : public BaseFixture { +}; + +TYPED_TEST_CASE(PairwisePointInPolygonUnsupportedChronoTypesTest, ChronoTypes); + +TYPED_TEST(PairwisePointInPolygonUnsupportedChronoTypesTest, UnsupportedPointChronoType) +{ + using T = TypeParam; + using R = typename T::rep; + + auto test_point_xs = wrapper({R{0}, R{0}}); + auto test_point_ys = wrapper({R{0}}); + auto poly_offsets = wrapper({0}); + auto poly_ring_offsets = wrapper({0}); + auto poly_point_xs = wrapper({R{0}, R{1}, R{0}, R{-1}}); + auto poly_point_ys = wrapper({R{1}, R{0}, R{-1}, R{0}}); + + EXPECT_THROW( + cuspatial::pairwise_point_in_polygon( + test_point_xs, test_point_ys, poly_offsets, poly_ring_offsets, poly_point_xs, poly_point_ys), + cuspatial::logic_error); +} + +struct PairwisePointInPolygonErrorTest : public BaseFixture { +}; + +TEST_F(PairwisePointInPolygonErrorTest, MismatchTestPointXYLength) +{ + using T = double; + + auto test_point_xs = wrapper({0.0, 0.0}); + auto test_point_ys = wrapper({0.0}); + auto poly_offsets = wrapper({0}); + auto poly_ring_offsets = wrapper({0}); + auto poly_point_xs = wrapper({0.0, 1.0, 0.0, -1.0}); + auto poly_point_ys = wrapper({1.0, 0.0, -1.0, 0.0}); + + EXPECT_THROW( + cuspatial::pairwise_point_in_polygon( + test_point_xs, test_point_ys, poly_offsets, poly_ring_offsets, poly_point_xs, poly_point_ys), + cuspatial::logic_error); +} + +TEST_F(PairwisePointInPolygonErrorTest, MismatchTestPointType) +{ + using T = double; + + auto test_point_xs = wrapper({0.0}); + auto test_point_ys = wrapper({0.0}); + auto poly_offsets = wrapper({0}); + auto poly_ring_offsets = wrapper({0}); + auto poly_point_xs = wrapper({0.0, 1.0, 0.0}); + auto poly_point_ys = wrapper({1.0, 0.0, -1.0, 0.0}); + + EXPECT_THROW( + cuspatial::pairwise_point_in_polygon( + test_point_xs, test_point_ys, poly_offsets, poly_ring_offsets, poly_point_xs, poly_point_ys), + cuspatial::logic_error); +} + +TEST_F(PairwisePointInPolygonErrorTest, MismatchPolyPointXYLength) +{ + using T = double; + + auto test_point_xs = wrapper({0.0}); + auto test_point_ys = wrapper({0.0}); + auto poly_offsets = wrapper({0}); + auto poly_ring_offsets = wrapper({0}); + auto poly_point_xs = wrapper({0.0, 1.0, 0.0}); + auto poly_point_ys = wrapper({1.0, 0.0, -1.0, 0.0}); + + EXPECT_THROW( + cuspatial::pairwise_point_in_polygon( + test_point_xs, test_point_ys, poly_offsets, poly_ring_offsets, poly_point_xs, poly_point_ys), + cuspatial::logic_error); +} + +TEST_F(PairwisePointInPolygonErrorTest, MismatchPolyPointType) +{ + using T = double; + + auto test_point_xs = wrapper({0.0}); + auto test_point_ys = wrapper({0.0}); + auto poly_offsets = wrapper({0}); + auto poly_ring_offsets = wrapper({0}); + auto poly_point_xs = wrapper({0.0, 1.0, 0.0}); + auto poly_point_ys = wrapper({1.0, 0.0, -1.0, 0.0}); + + EXPECT_THROW( + cuspatial::pairwise_point_in_polygon( + test_point_xs, test_point_ys, poly_offsets, poly_ring_offsets, poly_point_xs, poly_point_ys), + cuspatial::logic_error); +} + +TEST_F(PairwisePointInPolygonErrorTest, MismatchPointTypes) +{ + auto test_point_xs = wrapper({0.0}); + auto test_point_ys = wrapper({0.0}); + auto poly_offsets = wrapper({0}); + auto poly_ring_offsets = wrapper({0}); + auto poly_point_xs = wrapper({0.0, 1.0, 0.0, -1.0}); + auto poly_point_ys = wrapper({1.0, 0.0, -1.0, 0.0}); + + EXPECT_THROW( + cuspatial::pairwise_point_in_polygon( + test_point_xs, test_point_ys, poly_offsets, poly_ring_offsets, poly_point_xs, poly_point_ys), + cuspatial::logic_error); +} + +TEST_F(PairwisePointInPolygonErrorTest, MorePointsThanPolygons) +{ + auto test_point_xs = wrapper({0.0, 0.0}); + auto test_point_ys = wrapper({0.0, 0.0}); + auto poly_offsets = wrapper({0}); + auto poly_ring_offsets = wrapper({0}); + auto poly_point_xs = wrapper({0.0, 1.0, 0.0, -1.0}); + auto poly_point_ys = wrapper({1.0, 0.0, -1.0, 0.0}); + + EXPECT_THROW( + cuspatial::pairwise_point_in_polygon( + test_point_xs, test_point_ys, poly_offsets, poly_ring_offsets, poly_point_xs, poly_point_ys), + cuspatial::logic_error); +} + +TEST_F(PairwisePointInPolygonErrorTest, MorePolygonsThanPoints) +{ + auto test_point_xs = wrapper({0.0}); + auto test_point_ys = wrapper({0.0}); + auto poly_offsets = wrapper({0, 4}); + auto poly_ring_offsets = wrapper({0, 4}); + auto poly_point_xs = wrapper({0.0, 1.0, 0.0, -1.0, 0.0, 1.0, 0.0, -1.0}); + auto poly_point_ys = wrapper({1.0, 0.0, -1.0, 0.0, 1.0, 0.0, -1.0, 0.0}); + + EXPECT_THROW( + cuspatial::pairwise_point_in_polygon( + test_point_xs, test_point_ys, poly_offsets, poly_ring_offsets, poly_point_xs, poly_point_ys), + cuspatial::logic_error); +} diff --git a/python/cuspatial/cuspatial/tests/spatial/join/test_point_in_polygon.py b/python/cuspatial/cuspatial/tests/spatial/join/test_point_in_polygon.py index 7c7010728..c01f37e05 100644 --- a/python/cuspatial/cuspatial/tests/spatial/join/test_point_in_polygon.py +++ b/python/cuspatial/cuspatial/tests/spatial/join/test_point_in_polygon.py @@ -236,7 +236,7 @@ def test_three_points_two_features(): ) expected = cudf.DataFrame() expected[0] = [True, True, False] - expected[1] = [True, False, True] + expected[1] = [False, False, True] cudf.testing.assert_frame_equal(expected, result)