diff --git a/cpp/benchmarks/points_in_range.cu b/cpp/benchmarks/points_in_range.cu index 442fc18f4..1ebe6349f 100644 --- a/cpp/benchmarks/points_in_range.cu +++ b/cpp/benchmarks/points_in_range.cu @@ -36,7 +36,7 @@ #include -using namespace cuspatial; +using cuspatial::vec_2d; /** * @brief Helper to generate random points within a range diff --git a/cpp/include/cuspatial/detail/utility/validation.hpp b/cpp/include/cuspatial/detail/utility/validation.hpp index 3eb0e7060..7fe5742ea 100644 --- a/cpp/include/cuspatial/detail/utility/validation.hpp +++ b/cpp/include/cuspatial/detail/utility/validation.hpp @@ -18,6 +18,51 @@ #include +/** + * @brief Macro for validating the data array sizes for linestrings. + * + * Raises an exception if any of the following are false: + * - The number of linestring offsets is greater than zero. + * - There are at least two vertices per linestring offset. + * + * Linestrings follow [GeoArrow data layout][1]. Offsets arrays 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 linestring offsets array is the + * last linestring offset plus one. See [Arrow Variable-Size Binary layout](2). Note that an + * empty list still has one offset: {0}. + * + * [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_LINESTRING_SIZES(num_linestring_points, num_linestring_offsets) \ + CUSPATIAL_EXPECTS(num_linestring_offsets > 0, \ + "Polygon offsets must contain at least one (1) value"); \ + CUSPATIAL_EXPECTS(num_linestring_points >= 2 * (num_linestring_offsets - 1), \ + "Each linestring must have at least two vertices"); + +/** + * @brief Macro for validating the data array sizes for multilinestrings. + * + * Raises an exception if any of the following are false: + * - The number of multilinestring offsets is greater than zero. + * - The number of linestring offsets is greater than zero. + * - There are at least two vertices per linestring offset. + * + * Multilinestrings follow [GeoArrow data layout][1]. Offsets arrays 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 linestring offsets array is the + * last linestring offset plus one. See [Arrow Variable-Size Binary layout](2). Note that an + * empty list still has one offset: {0}. + * + * [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_MULTILINESTRING_SIZES( \ + num_linestring_points, num_multilinestring_offsets, num_linestring_offsets) \ + CUSPATIAL_EXPECTS(num_multilinestring_offsets > 0, \ + "Multilinestring offsets must contain at least one (1) value"); \ + CUSPATIAL_EXPECTS_VALID_LINESTRING_SIZES(num_linestring_points, num_linestring_offsets); + /** * @brief Macro for validating the data array sizes for a polygon. * diff --git a/cpp/include/cuspatial/experimental/detail/algorithm/is_point_in_polygon.cuh b/cpp/include/cuspatial/experimental/detail/algorithm/is_point_in_polygon.cuh index 73c3ebd29..40c5cce33 100644 --- a/cpp/include/cuspatial/experimental/detail/algorithm/is_point_in_polygon.cuh +++ b/cpp/include/cuspatial/experimental/detail/algorithm/is_point_in_polygon.cuh @@ -27,7 +27,7 @@ namespace cuspatial { namespace detail { /** - * @brief Kernel to test if a point is inside a polygon. + * @brief 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/ diff --git a/cpp/include/cuspatial/experimental/detail/algorithm/point_linestring_distance.cuh b/cpp/include/cuspatial/experimental/detail/algorithm/point_linestring_distance.cuh new file mode 100644 index 000000000..4b8cf4a6c --- /dev/null +++ b/cpp/include/cuspatial/experimental/detail/algorithm/point_linestring_distance.cuh @@ -0,0 +1,54 @@ +/* + * 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 +#include +#include + +namespace cuspatial { +namespace detail { + +template +__device__ T proj2(segment const& s, vec_2d const& v) +{ + return dot(v - s.v1, s.v2 - s.v1); +} + +template +inline __device__ T point_linestring_distance(vec_2d const& point, + LinestringRef const& linestring) +{ + T distance_squared = std::numeric_limits::max(); + + for (auto const& s : linestring) { + auto v1p = point - s.v1; + auto v2p = point - s.v2; + auto d0 = dot(v1p, v1p); + auto d1 = dot(v2p, v2p); + auto d2 = s.length2(); + auto d3 = proj2(s, point); + auto const r = d3 * d3 / d2; + auto const d = (d3 <= 0 || r >= d2) ? min(d0, d1) : d0 - r; + distance_squared = min(distance_squared, d); + } + + return sqrt(distance_squared); +} + +} // namespace detail +} // namespace cuspatial diff --git a/cpp/include/cuspatial/experimental/detail/linestring_bounding_boxes.cuh b/cpp/include/cuspatial/experimental/detail/linestring_bounding_boxes.cuh index 5c3979431..ea86f17ef 100644 --- a/cpp/include/cuspatial/experimental/detail/linestring_bounding_boxes.cuh +++ b/cpp/include/cuspatial/experimental/detail/linestring_bounding_boxes.cuh @@ -43,9 +43,12 @@ BoundingBoxIterator linestring_bounding_boxes(LinestringOffsetIterator linestrin T expansion_radius, rmm::cuda_stream_view stream) { + static_assert(is_same>(), + "expansion_radius type must match vertex floating-point type"); + static_assert(is_floating_point(), "Only floating point polygon vertices supported"); - static_assert(is_same, iterator_value_type>(), + static_assert(is_vec_2d>, "Input vertices must be cuspatial::vec_2d"); static_assert(cuspatial::is_integral>(), diff --git a/cpp/include/cuspatial/experimental/detail/polygon_bounding_boxes.cuh b/cpp/include/cuspatial/experimental/detail/polygon_bounding_boxes.cuh index a03bec811..618fb05cf 100644 --- a/cpp/include/cuspatial/experimental/detail/polygon_bounding_boxes.cuh +++ b/cpp/include/cuspatial/experimental/detail/polygon_bounding_boxes.cuh @@ -47,9 +47,12 @@ BoundingBoxIterator polygon_bounding_boxes(PolygonOffsetIterator polygon_offsets T expansion_radius, rmm::cuda_stream_view stream) { + static_assert(is_same>(), + "expansion_radius type must match vertex floating-point type"); + static_assert(is_floating_point(), "Only floating point polygon vertices supported"); - static_assert(is_same, iterator_value_type>(), + static_assert(is_vec_2d>, "Input vertices must be cuspatial::vec_2d"); static_assert(cuspatial::is_integral, diff --git a/cpp/include/cuspatial/experimental/detail/quadtree_point_to_nearest_linestring.cuh b/cpp/include/cuspatial/experimental/detail/quadtree_point_to_nearest_linestring.cuh new file mode 100644 index 000000000..112942edf --- /dev/null +++ b/cpp/include/cuspatial/experimental/detail/quadtree_point_to_nearest_linestring.cuh @@ -0,0 +1,308 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include + +namespace cuspatial { +namespace detail { + +template +inline __device__ std::pair get_local_linestring_index_and_count( + uint32_t const linestring_index, QuadOffsetsIter quad_offsets, QuadOffsetsIter quad_offsets_end) +{ + auto const lhs_end = quad_offsets; + auto const rhs_end = quad_offsets_end; + auto const quad_offset = quad_offsets[linestring_index]; + auto const lhs = + thrust::lower_bound(thrust::seq, lhs_end, quad_offsets + linestring_index, quad_offset); + auto const rhs = + thrust::upper_bound(thrust::seq, quad_offsets + linestring_index, rhs_end, quad_offset); + + return std::make_pair( + // local_linestring_index + static_cast(thrust::distance(lhs, quad_offsets + linestring_index)), + // num_linestrings_in_quad + static_cast(thrust::distance(lhs, rhs))); +} + +template +inline __device__ std::pair get_transposed_point_and_pair_index( + uint32_t const global_index, + uint32_t const* point_offsets, + uint32_t const* point_offsets_end, + QuadOffsetsIter quad_offsets, + QuadOffsetsIter quad_offsets_end, + QuadLengthsIter quad_lengths) +{ + auto const [quad_linestring_index, local_point_index] = + get_quad_and_local_point_indices(global_index, point_offsets, point_offsets_end); + + auto const [local_linestring_index, num_linestrings_in_quad] = + get_local_linestring_index_and_count(quad_linestring_index, quad_offsets, quad_offsets_end); + + auto const quad_point_offset = quad_offsets[quad_linestring_index]; + auto const num_points_in_quad = quad_lengths[quad_linestring_index]; + auto const quad_linestring_offset = quad_linestring_index - local_linestring_index; + auto const quad_linestring_point_start = local_linestring_index * num_points_in_quad; + auto const transposed_point_start = quad_linestring_point_start + local_point_index; + + return std::make_pair( + // transposed point index + (transposed_point_start / num_linestrings_in_quad) + quad_point_offset, + // transposed linestring index + (transposed_point_start % num_linestrings_in_quad) + quad_linestring_offset); +} + +template > +struct compute_point_linestring_indices_and_distances { + PointIter points; + PointOffsetsIter point_offsets; + PointOffsetsIter point_offsets_end; + QuadOffsetsIter quad_offsets; + QuadOffsetsIter quad_offsets_end; + QuadLengthsIter quad_lengths; + LinestringIndexIterator linestring_indices; + MultiLinestringRange linestrings; + + compute_point_linestring_indices_and_distances(PointIter points, + PointOffsetsIter point_offsets, + PointOffsetsIter point_offsets_end, + QuadOffsetsIter quad_offsets, + QuadOffsetsIter quad_offsets_end, + QuadLengthsIter quad_lengths, + LinestringIndexIterator linestring_indices, + MultiLinestringRange linestrings) + : points(points), + point_offsets(point_offsets), + point_offsets_end(point_offsets_end), + quad_offsets(quad_offsets), + quad_offsets_end(quad_offsets_end), + quad_lengths(quad_lengths), + linestring_indices(linestring_indices), + linestrings(linestrings) + { + } + + inline __device__ thrust::tuple operator()(uint32_t const global_index) + { + auto const [point_id, linestring_id] = get_transposed_point_and_pair_index( + global_index, point_offsets, point_offsets_end, quad_offsets, quad_offsets_end, quad_lengths); + + // We currently support only single-linestring multilinestrings, so use the zero index + auto linestring = linestrings[linestring_indices[linestring_id]][0]; + auto const distance = + point_linestring_distance(thrust::raw_reference_cast(points[point_id]), linestring); + + return thrust::make_tuple(point_id, linestring_indices[linestring_id], distance); + } +}; + +} // namespace detail + +template +std::tuple, rmm::device_uvector, rmm::device_uvector> +quadtree_point_to_nearest_linestring(LinestringIndexIterator linestring_indices_first, + LinestringIndexIterator linestring_indices_last, + QuadIndexIterator quad_indices_first, + point_quadtree_ref quadtree, + PointIndexIterator point_indices_first, + PointIndexIterator point_indices_last, + PointIterator points_first, + MultiLinestringRange linestrings, + rmm::cuda_stream_view stream, + rmm::mr::device_memory_resource* mr) +{ + CUSPATIAL_EXPECTS(linestrings.num_multilinestrings() == linestrings.num_linestrings(), + "Only one linestring per multilinestring currently supported."); + + auto num_linestring_quad_pairs = std::distance(linestring_indices_first, linestring_indices_last); + + auto quad_lengths_iter = + thrust::make_permutation_iterator(quadtree.length_begin(), quad_indices_first); + + auto quad_offsets_iter = + thrust::make_permutation_iterator(quadtree.offset_begin(), quad_indices_first); + + // Compute a "local" set of zero-based point offsets from number of points in each quadrant + // Use `num_poly_quad_pairs + 1` as the length so that the last element produced by + // `inclusive_scan` is the total number of points to be tested against any polygon. + rmm::device_uvector local_point_offsets(num_linestring_quad_pairs + 1, stream); + + // inclusive scan of quad_lengths_iter + thrust::inclusive_scan(rmm::exec_policy(stream), + quad_lengths_iter, + quad_lengths_iter + num_linestring_quad_pairs, + local_point_offsets.begin() + 1); + + // Ensure local point offsets starts at 0 + IndexType init{0}; + local_point_offsets.set_element_async(0, init, stream); + + // The last element is the total number of points to test against any polygon. + auto num_point_linestring_pairs = local_point_offsets.back_element(stream); + + // Enumerate the point X/Ys using the sorted `point_indices` (from quadtree construction) + auto point_xys_iter = thrust::make_permutation_iterator(points_first, point_indices_first); + + // + // Compute the combination of point and linestring index pairs. For each linestring / quadrant + // pair, enumerate pairs of (point_index, linestring_index) for each point in each quadrant, + // and calculate the minimum distance between each point / linestring pair. + // + // In Python pseudocode: + // ``` + // pl_pairs_and_dist = [] + // for linestring, quadrant in lq_pairs: + // for point in quadrant: + // pl_pairs_and_dist.append((point, linestring, min_distance(point, linestring))) + // ``` + // + // However, the above pseudocode produces values in an order such that the distance + // from a point to each linestring cannot be reduced with `thrust::reduce_by_key`: + // ``` + // point | linestring | distance + // 0 | 0 | 10.0 + // 1 | 0 | 30.0 + // 2 | 0 | 20.0 + // 0 | 1 | 30.0 + // 1 | 1 | 20.0 + // 2 | 1 | 10.0 + // ``` + // + // In order to use `thrust::reduce_by_key` to compute the minimum distance from a point to + // the linestrings in its quadrant, the above table needs to be sorted by `point` instead of + // `linestring`: + // ``` + // point | linestring | distance + // 0 | 0 | 10.0 + // 0 | 1 | 30.0 + // 1 | 0 | 30.0 + // 1 | 1 | 20.0 + // 2 | 0 | 20.0 + // 2 | 1 | 10.0 + // ``` + // + // A naive approach would be to allocate memory for the above three columns, sort the + // columns by `point`, then use `thrust::reduce_by_key` to compute the min distances. + // + // The sizes of the intermediate buffers required can easily grow beyond available + // device memory, so a better approach is to use a Thrust iterator to yield values + // in the sorted order as we do here. + // + auto all_point_linestring_indices_and_distances = detail::make_counting_transform_iterator( + 0u, + compute_point_linestring_indices_and_distances{point_xys_iter, + local_point_offsets.begin(), + local_point_offsets.end(), + quad_offsets_iter, + quad_offsets_iter + num_linestring_quad_pairs, + quad_lengths_iter, + linestring_indices_first, + linestrings}); + + auto all_point_indices = + thrust::make_transform_iterator(all_point_linestring_indices_and_distances, + [] __device__(auto const& x) { return thrust::get<0>(x); }); + + // Allocate vectors for the distances min reduction + auto num_points = std::distance(point_indices_first, point_indices_last); + rmm::device_uvector point_idxs(num_points, stream); // temporary, used to scatter + + rmm::device_uvector output_linestring_idxs(num_points, stream, mr); + rmm::device_uvector output_distances(num_points, stream, mr); + rmm::device_uvector output_point_idxs(num_points, stream, mr); + + // Fill distances with 0 + zero_data_async(output_distances.begin(), output_distances.end(), stream); + // Reduce the intermediate point/linestring indices to lists of point/linestring index pairs + // and distances, selecting the linestring index closest to each point. + auto point_idxs_end = thrust::reduce_by_key( + rmm::exec_policy(stream), + all_point_indices, // point indices in + all_point_indices + num_point_linestring_pairs, + all_point_linestring_indices_and_distances, + point_idxs.begin(), // point indices out + // point/linestring indices and distances out + thrust::make_zip_iterator( + thrust::make_discard_iterator(), output_linestring_idxs.begin(), output_distances.begin()), + thrust::equal_to(), // comparator + // binop to select the point/linestring pair with the smallest distance + [] __device__(auto const& lhs, auto const& rhs) { + T const& d_lhs = thrust::get<2>(lhs); + T const& d_rhs = thrust::get<2>(rhs); + // If lhs distance is 0, choose rhs + if (d_lhs == T{0}) { return rhs; } + // if rhs distance is 0, choose lhs + if (d_rhs == T{0}) { return lhs; } + // If distances to lhs/rhs are the same, choose linestring with smallest id + if (d_lhs == d_rhs) { + auto const& i_lhs = thrust::get<1>(lhs); + auto const& i_rhs = thrust::get<1>(rhs); + return i_lhs < i_rhs ? lhs : rhs; + } + // Otherwise choose linestring with smallest distance + return d_lhs < d_rhs ? lhs : rhs; + }); + + auto const num_distances = thrust::distance(point_idxs.begin(), point_idxs_end.first); + + auto point_linestring_idxs_and_distances = thrust::make_zip_iterator( + point_idxs.begin(), output_linestring_idxs.begin(), output_distances.begin()); + + // scatter the values from their positions after reduction into their output positions + thrust::scatter( + rmm::exec_policy(stream), + point_linestring_idxs_and_distances, + point_linestring_idxs_and_distances + num_distances, + point_idxs.begin(), + thrust::make_zip_iterator( + output_point_idxs.begin(), output_linestring_idxs.begin(), output_distances.begin())); + + return std::tuple{ + std::move(output_point_idxs), std::move(output_linestring_idxs), std::move(output_distances)}; +} + +} // namespace cuspatial diff --git a/cpp/include/cuspatial/experimental/detail/ranges/multilinestring_range.cuh b/cpp/include/cuspatial/experimental/detail/ranges/multilinestring_range.cuh index 5add3a769..332cacaa8 100644 --- a/cpp/include/cuspatial/experimental/detail/ranges/multilinestring_range.cuh +++ b/cpp/include/cuspatial/experimental/detail/ranges/multilinestring_range.cuh @@ -23,6 +23,7 @@ #include #include +#include #include #include #include @@ -81,6 +82,11 @@ multilinestring_range::multilinestr _point_begin(point_begin), _point_end(point_end) { + static_assert(is_vec_2d>, + "point_begin and point_end must be iterators to floating point vec_2d types."); + + CUSPATIAL_EXPECTS_VALID_MULTILINESTRING_SIZES( + num_points(), num_multilinestrings() + 1, num_linestrings() + 1); } template diff --git a/cpp/include/cuspatial/experimental/detail/ranges/multipoint_range.cuh b/cpp/include/cuspatial/experimental/detail/ranges/multipoint_range.cuh index fa7d4002b..13814708e 100644 --- a/cpp/include/cuspatial/experimental/detail/ranges/multipoint_range.cuh +++ b/cpp/include/cuspatial/experimental/detail/ranges/multipoint_range.cuh @@ -63,7 +63,7 @@ multipoint_range::multipoint_range(GeometryIterat _points_begin(points_begin), _points_end(points_end) { - static_assert(is_vec_2d>(), + static_assert(is_vec_2d>, "Coordinate range must be constructed with iterators to vec_2d."); } diff --git a/cpp/include/cuspatial/experimental/detail/ranges/multipolygon_range.cuh b/cpp/include/cuspatial/experimental/detail/ranges/multipolygon_range.cuh index f62b68044..b9e281af8 100644 --- a/cpp/include/cuspatial/experimental/detail/ranges/multipolygon_range.cuh +++ b/cpp/include/cuspatial/experimental/detail/ranges/multipolygon_range.cuh @@ -103,7 +103,7 @@ multipolygon_range::m _point_begin(point_begin), _point_end(point_end) { - static_assert(is_vec_2d>(), + static_assert(is_vec_2d>, "point_begin and point_end must be iterators to floating point vec_2d types."); CUSPATIAL_EXPECTS_VALID_MULTIPOLYGON_SIZES( diff --git a/cpp/include/cuspatial/experimental/geometry/segment.cuh b/cpp/include/cuspatial/experimental/geometry/segment.cuh index 214819694..91c9ac5a4 100644 --- a/cpp/include/cuspatial/experimental/geometry/segment.cuh +++ b/cpp/include/cuspatial/experimental/geometry/segment.cuh @@ -52,6 +52,9 @@ class alignas(sizeof(Vertex)) segment { /// Return the geometric center of segment. Vertex CUSPATIAL_HOST_DEVICE center() const { return midpoint(v1, v2); } + /// Return the length squared of segment. + T CUSPATIAL_HOST_DEVICE length2() const { return dot(v2 - v1, v2 - v1); } + private: friend std::ostream& operator<<(std::ostream& os, segment const& seg) { diff --git a/cpp/include/cuspatial/experimental/ranges/multilinestring_range.cuh b/cpp/include/cuspatial/experimental/ranges/multilinestring_range.cuh index c99b5ca92..1f45db50f 100644 --- a/cpp/include/cuspatial/experimental/ranges/multilinestring_range.cuh +++ b/cpp/include/cuspatial/experimental/ranges/multilinestring_range.cuh @@ -89,6 +89,18 @@ class multilinestring_range { /// Return the iterator to the one past the last multilinestring in the range. CUSPATIAL_HOST_DEVICE auto end() { return multilinestring_end(); } + /// Return the iterator to the first point in the range. + CUSPATIAL_HOST_DEVICE auto point_begin() { return _point_begin; } + + /// Return the iterator to the one past the last point in the range. + CUSPATIAL_HOST_DEVICE auto point_end() { return _point_end; } + + /// Return the iterator to the first part offset in the range. + CUSPATIAL_HOST_DEVICE auto part_offset_begin() { return _part_begin; } + + /// Return the iterator to the one past the last part offset in the range. + CUSPATIAL_HOST_DEVICE auto part_offset_end() { return _part_end; } + /// Given the index of a point, return the part (linestring) index where the point locates. template CUSPATIAL_HOST_DEVICE auto part_idx_from_point_idx(IndexType point_idx); diff --git a/cpp/include/cuspatial/experimental/spatial_join.cuh b/cpp/include/cuspatial/experimental/spatial_join.cuh index 741dba1a8..a90d56b7c 100644 --- a/cpp/include/cuspatial/experimental/spatial_join.cuh +++ b/cpp/include/cuspatial/experimental/spatial_join.cuh @@ -20,6 +20,7 @@ #include #include #include +#include #include @@ -129,7 +130,65 @@ std::pair, rmm::device_uvector> quadtr rmm::cuda_stream_view stream = rmm::cuda_stream_default, rmm::mr::device_memory_resource* mr = rmm::mr::get_current_device_resource()); +/** + * @brief Finds the nearest linestring to each point in a quadrant, and computes the distances + * between each point and linestring. + * + * Uses the (linestring, quadrant) pairs returned by `cuspatial::join_quadtree_and_bounding_boxes` + * to ensure distances are computed only for the points in the same quadrant as each linestring. + * + * @param linestring_quad_pairs cudf table of (linestring, quadrant) index pairs returned by + * `cuspatial::join_quadtree_and_bounding_boxes` + * @param quadtree cudf table representing a quadtree (key, level, is_internal_node, length, + * offset). + * @param point_indices Sorted point indices returned by `cuspatial::quadtree_on_points` + * @param point_x x-coordinates of points to test + * @param point_y y-coordinates of points to test + * @param linestring_offsets Begin indices of the first point in each linestring (i.e. prefix-sum) + * @param linestring_points_x Linestring point x-coordinates + * @param linestring_points_y Linestring point y-coordinates + * @param mr The optional resource to use for output device memory allocations. + * + * @throw cuspatial::logic_error If the linestring_quad_pairs table is malformed. + * @throw cuspatial::logic_error If the quadtree table is malformed. + * @throw cuspatial::logic_error If the number of point indices doesn't match the number of points. + * @throw cuspatial::logic_error If any linestring has fewer than two vertices. + * @throw cuspatial::logic_error If the types of point and linestring vertices are different. + * + * @return A cudf table with three columns, where each row represents a point/linestring pair and + * the distance between the two: + * + * point_offset - UINT32 column of point indices + * linestring_offset - UINT32 column of linestring indices + * distance - FLOAT or DOUBLE column (based on input point data type) of distances + * between each point and linestring + * + * @note The returned point and linestring indices are offsets into the `point_indices` and + * `linestring_quad_pairs` inputs, respectively. + * + **/ +template , + typename T = iterator_vec_base_type> +std::tuple, rmm::device_uvector, rmm::device_uvector> +quadtree_point_to_nearest_linestring( + LinestringIndexIterator linestring_indices_first, + LinestringIndexIterator linestring_indices_last, + QuadIndexIterator quad_indices_first, + point_quadtree_ref quadtree, + PointIndexIterator point_indices_first, + PointIndexIterator point_indices_last, + PointIterator points_first, + MultiLinestringRange linestrings, + rmm::cuda_stream_view stream = rmm::cuda_stream_default, + rmm::mr::device_memory_resource* mr = rmm::mr::get_current_device_resource()); + } // namespace cuspatial #include #include +#include diff --git a/cpp/include/cuspatial/traits.hpp b/cpp/include/cuspatial/traits.hpp index 178b74d4b..d16a1e269 100644 --- a/cpp/include/cuspatial/traits.hpp +++ b/cpp/include/cuspatial/traits.hpp @@ -79,15 +79,16 @@ constexpr bool is_integral() return std::conjunction_v...>; } +template +constexpr bool is_vec_2d_impl = false; +template +constexpr bool is_vec_2d_impl> = true; /** * @internal - * @brief returns true if `T` is `vec_2d` or `vec_2d` + * @brief Evaluates to true if T is a cuspatial::vec_2d */ template -constexpr bool is_vec_2d() -{ - return std::is_same_v> or std::is_same_v>; -} +constexpr bool is_vec_2d = is_vec_2d_impl>>; /** * @internal @@ -104,6 +105,10 @@ template constexpr bool is_optional_impl = false; template constexpr bool is_optional_impl> = true; +/** + * @internal + * @brief Evaluates to true if T is a std::optional + */ template constexpr bool is_optional = is_optional_impl>>; diff --git a/cpp/include/cuspatial_test/vector_equality.hpp b/cpp/include/cuspatial_test/vector_equality.hpp index 88b2f193f..204a1589d 100644 --- a/cpp/include/cuspatial_test/vector_equality.hpp +++ b/cpp/include/cuspatial_test/vector_equality.hpp @@ -145,14 +145,14 @@ inline void expect_vector_equivalent(Vector1 const& lhs, Vector2 const& rhs) using T = typename Vector1::value_type; static_assert(std::is_same_v, "Value type mismatch."); - if constexpr (cuspatial::is_vec_2d()) { + if constexpr (cuspatial::is_vec_2d) { EXPECT_THAT(to_host(lhs), ::testing::Pointwise(vec_2d_matcher(), to_host(rhs))); } else if constexpr (std::is_floating_point_v) { EXPECT_THAT(to_host(lhs), ::testing::Pointwise(float_matcher(), to_host(rhs))); } else if constexpr (std::is_integral_v) { EXPECT_THAT(to_host(lhs), ::testing::Pointwise(::testing::Eq(), to_host(rhs))); } else if constexpr (cuspatial::is_optional) { - if constexpr (cuspatial::is_vec_2d()) { + if constexpr (cuspatial::is_vec_2d) { EXPECT_THAT(to_host(lhs), ::testing::Pointwise(optional_matcher(vec_2d_matcher()), to_host(rhs))); } else if constexpr (std::is_floating_point_v) { @@ -176,14 +176,14 @@ inline void expect_vector_equivalent(Vector1 const& lhs, Vector2 const& rhs, U a static_assert(std::is_same_v, "Value type mismatch."); static_assert(!std::is_integral_v, "Integral types cannot be compared with an error."); - if constexpr (cuspatial::is_vec_2d()) { + if constexpr (cuspatial::is_vec_2d) { EXPECT_THAT(to_host(lhs), ::testing::Pointwise(vec_2d_near_matcher(abs_error), to_host(rhs))); } else if constexpr (std::is_floating_point_v) { EXPECT_THAT(to_host(lhs), ::testing::Pointwise(float_near_matcher(abs_error), to_host(rhs))); } else if constexpr (cuspatial::is_optional) { - if constexpr (cuspatial::is_vec_2d()) { + if constexpr (cuspatial::is_vec_2d) { EXPECT_THAT(to_host(lhs), ::testing::Pointwise(optional_matcher(vec_2d_matcher()), to_host(rhs))); } else if constexpr (std::is_floating_point_v) { diff --git a/cpp/src/join/quadtree_point_to_nearest_linestring.cu b/cpp/src/join/quadtree_point_to_nearest_linestring.cu index 1e0fa7100..a1ea45cbc 100644 --- a/cpp/src/join/quadtree_point_to_nearest_linestring.cu +++ b/cpp/src/join/quadtree_point_to_nearest_linestring.cu @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020-2022, NVIDIA CORPORATION. + * Copyright (c) 2020-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. @@ -14,16 +14,12 @@ * limitations under the License. */ -#include -#include - -#include -#include - +#include #include -#include +#include +#include +#include -#include #include #include #include @@ -33,101 +29,11 @@ #include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - #include #include namespace cuspatial { namespace detail { -namespace { - -template -inline __device__ std::pair get_local_linestring_index_and_count( - uint32_t const linestring_index, QuadOffsetsIter quad_offsets, QuadOffsetsIter quad_offsets_end) -{ - auto const lhs_end = quad_offsets; - auto const rhs_end = quad_offsets_end; - auto const quad_offset = quad_offsets[linestring_index]; - auto const lhs = - thrust::lower_bound(thrust::seq, lhs_end, quad_offsets + linestring_index, quad_offset); - auto const rhs = - thrust::upper_bound(thrust::seq, quad_offsets + linestring_index, rhs_end, quad_offset); - - return std::make_pair( - // local_linestring_index - static_cast(thrust::distance(lhs, quad_offsets + linestring_index)), - // num_linestrings_in_quad - static_cast(thrust::distance(lhs, rhs))); -} - -template -inline __device__ std::pair get_transposed_point_and_pair_index( - uint32_t const global_index, - uint32_t const* point_offsets, - uint32_t const* point_offsets_end, - QuadOffsetsIter quad_offsets, - QuadOffsetsIter quad_offsets_end, - QuadLengthsIter quad_lengths) -{ - auto const [quad_linestring_index, local_point_index] = - get_quad_and_local_point_indices(global_index, point_offsets, point_offsets_end); - - auto const [local_linestring_index, num_linestrings_in_quad] = - get_local_linestring_index_and_count(quad_linestring_index, quad_offsets, quad_offsets_end); - - auto const quad_point_offset = quad_offsets[quad_linestring_index]; - auto const num_points_in_quad = quad_lengths[quad_linestring_index]; - auto const quad_linestring_offset = quad_linestring_index - local_linestring_index; - auto const quad_linestring_point_start = local_linestring_index * num_points_in_quad; - auto const transposed_point_start = quad_linestring_point_start + local_point_index; - - return std::make_pair( - // transposed point index - (transposed_point_start / num_linestrings_in_quad) + quad_point_offset, - // transposed linestring index - (transposed_point_start % num_linestrings_in_quad) + quad_linestring_offset); -} - -template -struct compute_point_linestring_indices_and_distances { - PointIter points; - uint32_t const* point_offsets; - uint32_t const* point_offsets_end; - QuadOffsetsIter quad_offsets; - QuadOffsetsIter quad_offsets_end; - QuadLengthsIter quad_lengths; - uint32_t const* linestring_indices; - cudf::column_device_view const linestring_offsets; - cudf::column_device_view const linestring_points_x; - cudf::column_device_view const linestring_points_y; - inline __device__ thrust::tuple operator()(uint32_t const global_index) - { - auto const [point_id, linestring_id] = get_transposed_point_and_pair_index( - global_index, point_offsets, point_offsets_end, quad_offsets, quad_offsets_end, quad_lengths); - - T x{}, y{}; - thrust::tie(x, y) = points[point_id]; - auto const linestring_idx = linestring_indices[linestring_id]; - auto const distance = point_to_linestring_distance( - x, y, linestring_idx, linestring_offsets, linestring_points_x, linestring_points_y); - - return thrust::make_tuple(point_id, linestring_idx, distance); - } -}; struct compute_quadtree_point_to_nearest_linestring { template @@ -150,181 +56,54 @@ struct compute_quadtree_point_to_nearest_linestring { rmm::cuda_stream_view stream, rmm::mr::device_memory_resource* mr) { - // Wrapped in an IIFE so `local_point_offsets` is freed on return - auto const [point_idxs, linestring_idxs, distances, num_distances] = [&]() { - auto num_linestring_quad_pairs = linestring_quad_pairs.num_rows(); - auto linestring_indices = linestring_quad_pairs.column(0).begin(); - auto quad_lengths = thrust::make_permutation_iterator( - quadtree.column(3).begin(), linestring_quad_pairs.column(1).begin()); - auto quad_offsets = thrust::make_permutation_iterator( - quadtree.column(4).begin(), linestring_quad_pairs.column(1).begin()); - - // Compute a "local" set of zero-based point offsets from number of points in each quadrant - // Use `num_linestring_quad_pairs + 1` as the length so that the last element produced by - // `inclusive_scan` is the total number of points to be tested against any linestring. - rmm::device_uvector local_point_offsets(num_linestring_quad_pairs + 1, stream); - - thrust::inclusive_scan(rmm::exec_policy(stream), - quad_lengths, - quad_lengths + num_linestring_quad_pairs, - local_point_offsets.begin() + 1); - - // Ensure local point offsets starts at 0 - uint32_t init{0}; - local_point_offsets.set_element_async(0, init, stream); - - // The last element is the total number of points to test against any linestring. - auto num_point_linestring_pairs = local_point_offsets.back_element(stream); - - // Enumerate the point X/Ys using the sorted `point_indices` (from quadtree construction) - auto point_xys_iter = thrust::make_permutation_iterator( - thrust::make_zip_iterator(point_x.begin(), point_y.begin()), - point_indices.begin()); - - // - // Compute the combination of point and linestring index pairs. For each linestring / quadrant - // pair, enumerate pairs of (point_index, linestring_index) for each point in each quadrant, - // and calculate the minimum distance between each point / linestring pair. - // - // In Python pseudocode: - // ``` - // pl_pairs_and_dist = [] - // for linestring, quadrant in lq_pairs: - // for point in quadrant: - // pl_pairs_and_dist.append((point, linestring, min_distance(point, linestring))) - // ``` - // - // However, the above pseudocode produces values in an order such that the distance - // from a point to each linestring cannot be reduced with `thrust::reduce_by_key`: - // ``` - // point | linestring | distance - // 0 | 0 | 10.0 - // 1 | 0 | 30.0 - // 2 | 0 | 20.0 - // 0 | 1 | 30.0 - // 1 | 1 | 20.0 - // 2 | 1 | 10.0 - // ``` - // - // In order to use `thrust::reduce_by_key` to compute the minimum distance from a point to - // the linestrings in its quadrant, the above table needs to be sorted by `point` instead of - // `linestring`: - // ``` - // point | linestring | distance - // 0 | 0 | 10.0 - // 0 | 1 | 30.0 - // 1 | 0 | 30.0 - // 1 | 1 | 20.0 - // 2 | 0 | 20.0 - // 2 | 1 | 10.0 - // ``` - // - // A naive approach would be to allocate memory for the above three columns, sort the - // columns by `point`, then use `thrust::reduce_by_key` to compute the min distances. - // - // The sizes of the intermediate buffers required can easily grow beyond available - // device memory, so a better approach is to use a Thrust iterator to yield values - // in the sorted order as we do here. - // - - auto all_point_linestring_indices_and_distances = thrust::make_transform_iterator( - thrust::make_counting_iterator(0u), - compute_point_linestring_indices_and_distances{ - point_xys_iter, - local_point_offsets.begin(), - local_point_offsets.end(), - quad_offsets, - quad_offsets + num_linestring_quad_pairs, - quad_lengths, - linestring_indices, - *cudf::column_device_view::create(linestring_offsets, stream), - *cudf::column_device_view::create(linestring_points_x, stream), - *cudf::column_device_view::create(linestring_points_y, stream)}); - - auto all_point_indices = - thrust::make_transform_iterator(all_point_linestring_indices_and_distances, - [] __device__(auto const& x) { return thrust::get<0>(x); }); - - // Allocate vectors for the distances min reduction - rmm::device_uvector point_idxs(point_x.size(), stream); - rmm::device_uvector linestring_idxs(point_x.size(), stream); - rmm::device_uvector distances(point_x.size(), stream); - - // Fill distances with 0 - CUSPATIAL_CUDA_TRY( - cudaMemsetAsync(distances.data(), 0, distances.size() * sizeof(T), stream.value())); - - // Reduce the intermediate point/linestring indices to lists of point/linestring index pairs - // and distances, selecting the linestring index closest to each point. - auto point_idxs_end = thrust::reduce_by_key( - rmm::exec_policy(stream), - all_point_indices, // point indices in - all_point_indices + num_point_linestring_pairs, - all_point_linestring_indices_and_distances, - point_idxs.begin(), // point indices out - // point/linestring indices and distances out - thrust::make_zip_iterator( - thrust::make_discard_iterator(), linestring_idxs.begin(), distances.begin()), - thrust::equal_to(), // comparator - // binop to select the point/linestring pair with the smallest distance - [] __device__(auto const& lhs, auto const& rhs) { - T const& d_lhs = thrust::get<2>(lhs); - T const& d_rhs = thrust::get<2>(rhs); - // If lhs distance is 0, choose rhs - if (d_lhs == T{0}) { return rhs; } - // if rhs distance is 0, choose lhs - if (d_rhs == T{0}) { return lhs; } - // If distances to lhs/rhs are the same, choose linestring with smallest id - if (d_lhs == d_rhs) { - auto const& i_lhs = thrust::get<1>(lhs); - auto const& i_rhs = thrust::get<1>(rhs); - return i_lhs < i_rhs ? lhs : rhs; - } - // Otherwise choose linestring with smallest distance - return d_lhs < d_rhs ? lhs : rhs; - }); - - auto const num_distances = thrust::distance(point_idxs.begin(), point_idxs_end.first); - - return std::make_tuple( - std::move(point_idxs), std::move(linestring_idxs), std::move(distances), num_distances); - }(); - - // Allocate output columns for the point and linestring index pairs and their distances - auto point_index_col = make_fixed_width_column(point_x.size(), stream, mr); - auto linestring_index_col = make_fixed_width_column(point_x.size(), stream, mr); - auto distance_col = make_fixed_width_column(point_x.size(), stream, mr); - - // Note: no need to resize `point_idxs`, `linestring_idxs`, or `distances` if we set the end - // iterator to `point_linestring_idxs_and_distances + num_distances`. - - auto point_linestring_idxs_and_distances = - thrust::make_zip_iterator(point_idxs.begin(), linestring_idxs.begin(), distances.begin()); - - // scatter the values from their positions after reduction into their output positions - thrust::scatter( - rmm::exec_policy(stream), - point_linestring_idxs_and_distances, - point_linestring_idxs_and_distances + num_distances, - point_idxs.begin(), - thrust::make_zip_iterator(point_index_col->mutable_view().begin(), - linestring_index_col->mutable_view().begin(), - distance_col->mutable_view().template begin())); + auto linestring_indices = linestring_quad_pairs.column(0); + auto quad_indices = linestring_quad_pairs.column(1); + + auto quadtree_ref = point_quadtree_ref(quadtree.column(0).begin(), // keys + quadtree.column(0).end(), + quadtree.column(1).begin(), // levels + quadtree.column(2).begin(), // is_internal_node + quadtree.column(3).begin(), // lengths + quadtree.column(4).begin()); // offsets + + auto linestrings = multilinestring_range( + thrust::make_counting_iterator(0), + thrust::make_counting_iterator(linestring_offsets.size()), + linestring_offsets.begin(), + linestring_offsets.end(), + make_vec_2d_iterator(linestring_points_x.begin(), linestring_points_y.begin()), + make_vec_2d_iterator(linestring_points_x.end(), linestring_points_y.end())); + + auto [point_idxs, linestring_idxs, distances] = cuspatial::quadtree_point_to_nearest_linestring( + linestring_indices.begin(), + linestring_indices.end(), + quad_indices.begin(), + quadtree_ref, + point_indices.begin(), + point_indices.end(), + make_vec_2d_iterator(point_x.begin(), point_y.begin()), + linestrings, + stream, + mr); + + auto num_distances = distances.size(); + + auto point_idx_col = std::make_unique( + cudf::data_type{cudf::type_id::UINT32}, num_distances, point_idxs.release()); + auto linestring_idx_col = std::make_unique( + cudf::data_type{cudf::type_id::UINT32}, num_distances, linestring_idxs.release()); + auto distance_col = + std::make_unique(point_x.type(), num_distances, distances.release()); std::vector> cols{}; cols.reserve(3); - cols.push_back(std::move(point_index_col)); - cols.push_back(std::move(linestring_index_col)); - cols.push_back(std::move(distance_col)); + cols.emplace_back(std::move(point_idx_col)); + cols.emplace_back(std::move(linestring_idx_col)); + cols.emplace_back(std::move(distance_col)); return std::make_unique(std::move(cols)); } }; -} // namespace - std::unique_ptr quadtree_point_to_nearest_linestring( cudf::table_view const& linestring_quad_pairs, cudf::table_view const& quadtree, @@ -367,12 +146,11 @@ std::unique_ptr quadtree_point_to_nearest_linestring( CUSPATIAL_EXPECTS(linestring_quad_pairs.num_columns() == 2, "a quadrant-linestring table must have 2 columns"); CUSPATIAL_EXPECTS(quadtree.num_columns() == 5, "a quadtree table must have 5 columns"); + CUSPATIAL_EXPECTS(point_indices.size() == point_x.size() && point_x.size() == point_y.size(), "number of points must be the same for both x and y columns"); CUSPATIAL_EXPECTS(linestring_points_x.size() == linestring_points_y.size(), "numbers of vertices must be the same for both x and y columns"); - CUSPATIAL_EXPECTS(linestring_points_x.size() >= 2 * linestring_offsets.size(), - "all linestrings must have at least two vertices"); CUSPATIAL_EXPECTS(linestring_points_x.type() == linestring_points_y.type(), "linestring columns must have the same data type"); CUSPATIAL_EXPECTS(point_x.type() == point_y.type(), "point columns must have the same data type"); diff --git a/cpp/src/utility/point_to_nearest_linestring.cuh b/cpp/src/utility/point_to_nearest_linestring.cuh deleted file mode 100644 index 7803a799e..000000000 --- a/cpp/src/utility/point_to_nearest_linestring.cuh +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (c) 2021-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 { - -template -inline __device__ T -point_to_linestring_distance(T const px, - T const py, - cudf::size_type const ring_idx, - cudf::column_device_view const& ring_offsets, - cudf::column_device_view const& linestring_points_x, - cudf::column_device_view const& linestring_points_y) -{ - T distance_squared = std::numeric_limits::max(); - auto ring_begin = ring_offsets.element(ring_idx); - auto ring_end = ring_idx < ring_offsets.size() - 1 ? ring_offsets.element(ring_idx + 1) - : linestring_points_x.size(); - auto ring_len = ring_end - ring_begin; - for (auto point_idx = 0u; point_idx < ring_len; ++point_idx) { - auto const i0 = ring_begin + ((point_idx + 0) % ring_len); - auto const i1 = ring_begin + ((point_idx + 1) % ring_len); - auto const x0 = linestring_points_x.element(i0); - auto const y0 = linestring_points_y.element(i0); - auto const x1 = linestring_points_x.element(i1); - auto const y1 = linestring_points_y.element(i1); - auto const dx0 = px - x0; - auto const dy0 = py - y0; - auto const dx1 = px - x1; - auto const dy1 = py - y1; - auto const dx2 = x1 - x0; - auto const dy2 = y1 - y0; - auto const d0 = dx0 * dx0 + dy0 * dy0; - auto const d1 = dx1 * dx1 + dy1 * dy1; - auto const d2 = dx2 * dx2 + dy2 * dy2; - auto const d3 = dx2 * dx0 + dy2 * dy0; - auto const r = d3 * d3 / d2; - auto const d = d3 <= 0 || r >= d2 ? min(d0, d1) : d0 - r; - distance_squared = min(distance_squared, d); - } - - return sqrt(distance_squared); -} - -} // namespace detail -} // namespace cuspatial diff --git a/cpp/tests/CMakeLists.txt b/cpp/tests/CMakeLists.txt index 10e4d269b..821de8bff 100644 --- a/cpp/tests/CMakeLists.txt +++ b/cpp/tests/CMakeLists.txt @@ -57,8 +57,8 @@ ConfigureTest(HAVERSINE_TEST ConfigureTest(HAUSDORFF_TEST spatial/hausdorff_test.cpp) -ConfigureTest(JOIN_POINT_TO_LINESTRING_SMALL_TEST - join/point_to_nearest_linestring_test_small.cpp) +ConfigureTest(JOIN_POINT_TO_NEAREST_LINESTRING_TEST + join/quadtree_point_to_nearest_linestring_test.cpp) ConfigureTest(JOIN_POINT_IN_POLYGON_TEST join/quadtree_point_in_polygon_test.cpp) @@ -192,3 +192,6 @@ ConfigureTest(JOIN_POINT_IN_POLYGON_SMALL_TEST_EXP ConfigureTest(JOIN_POINT_IN_POLYGON_LARGE_TEST_EXP experimental/join/quadtree_point_in_polygon_test_large.cu) + +ConfigureTest(JOIN_POINT_TO_LINESTRING_SMALL_TEST_EXP + experimental/join/quadtree_point_to_nearest_linestring_test_small.cu) diff --git a/cpp/tests/experimental/join/quadtree_point_in_polygon_test_small.cu b/cpp/tests/experimental/join/quadtree_point_in_polygon_test_small.cu index a4dfc1607..8576c3f23 100644 --- a/cpp/tests/experimental/join/quadtree_point_in_polygon_test_small.cu +++ b/cpp/tests/experimental/join/quadtree_point_in_polygon_test_small.cu @@ -31,7 +31,7 @@ * A small test that it is suitable for manually visualizing point-polygon pairing results in a GIS * environment. GPU results are compared with expected values embedded in code. However, the number * of points in each quadrant is less than 32, the two kernels for point-in-polygon test are not - * fully tested. This is left for pip_refine_test_large. + * fully tested. */ template struct PIPRefineTestSmall : public cuspatial::test::BaseFixture { diff --git a/cpp/tests/experimental/join/quadtree_point_to_nearest_linestring_test_small.cu b/cpp/tests/experimental/join/quadtree_point_to_nearest_linestring_test_small.cu new file mode 100644 index 000000000..13cd8c4fe --- /dev/null +++ b/cpp/tests/experimental/join/quadtree_point_to_nearest_linestring_test_small.cu @@ -0,0 +1,198 @@ +/* + * 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 +#include +#include + +#include +#include +#include +#include +#include + +#include + +template +struct QuadtreePointToLinestringTestSmall : public cuspatial::test::BaseFixture { +}; + +using TestTypes = ::testing::Types; + +TYPED_TEST_CASE(QuadtreePointToLinestringTestSmall, TestTypes); + +TYPED_TEST(QuadtreePointToLinestringTestSmall, TestSmall) +{ + using T = TypeParam; + using cuspatial::vec_2d; + using cuspatial::test::make_device_vector; + + vec_2d v_min{0.0, 0.0}; + vec_2d v_max{8.0, 8.0}; + T const scale{1.0}; + uint8_t const max_depth{3}; + uint32_t const max_size{12}; + + auto points = make_device_vector>( + {{1.9804558865545805, 1.3472225743317712}, {0.1895259128530169, 0.5431061133894604}, + {1.2591725716781235, 0.1448705855995005}, {0.8178039499335275, 0.8138440641113271}, + {0.48171647380517046, 1.9022922214961997}, {1.3890664414691907, 1.5177694304735412}, + {0.2536015260915061, 1.8762161698642947}, {3.1907684812039956, 0.2621847215928189}, + {3.028362149164369, 0.027638405909631958}, {3.918090468102582, 0.3338651960183463}, + {3.710910700915217, 0.9937713340192049}, {3.0706987088385853, 0.9376313558467103}, + {3.572744183805594, 0.33184908855075124}, {3.7080407833612004, 0.09804238103130436}, + {3.70669993057843, 0.7485845679979923}, {3.3588457228653024, 0.2346381514128677}, + {2.0697434332621234, 1.1809465376402173}, {2.5322042870739683, 1.419555755682142}, + {2.175448214220591, 1.2372448404986038}, {2.113652420701984, 1.2774712415624014}, + {2.520755151373394, 1.902015274420646}, {2.9909779614491687, 1.2420487904041893}, + {2.4613232527836137, 1.0484414482621331}, {4.975578758530645, 0.9606291981013242}, + {4.07037627210835, 1.9486902798139454}, {4.300706849071861, 0.021365525588281198}, + {4.5584381091040616, 1.8996548860019926}, {4.822583857757069, 0.3234041700489503}, + {4.849847745942472, 1.9531893897409585}, {4.75489831780737, 0.7800065259479418}, + {4.529792124514895, 1.942673409259531}, {4.732546857961497, 0.5659923375279095}, + {3.7622247877537456, 2.8709552313924487}, {3.2648444465931474, 2.693039435509084}, + {3.01954722322135, 2.57810040095543}, {3.7164018490892348, 2.4612194182614333}, + {3.7002781846945347, 2.3345952955903906}, {2.493975723955388, 3.3999020934055837}, + {2.1807636574967466, 3.2296461832828114}, {2.566986568683904, 3.6607732238530897}, + {2.2006520196663066, 3.7672478678985257}, {2.5104987015171574, 3.0668114607133137}, + {2.8222482218882474, 3.8159308233351266}, {2.241538022180476, 3.8812819070357545}, + {2.3007438625108882, 3.6045900851589048}, {6.0821276168848994, 2.5470532680258002}, + {6.291790729917634, 2.983311357415729}, {6.109985464455084, 2.2235950639628523}, + {6.101327777646798, 2.5239201807166616}, {6.325158445513714, 2.8765450351723674}, + {6.6793884701899, 2.5605928243991434}, {6.4274219368674315, 2.9754616970668213}, + {6.444584786789386, 2.174562817047202}, {7.897735998643542, 3.380784914178574}, + {7.079453687660189, 3.063690547962938}, {7.430677191305505, 3.380489849365283}, + {7.5085184104988, 3.623862886287816}, {7.886010001346151, 3.538128217886674}, + {7.250745898479374, 3.4154469467473447}, {7.769497359206111, 3.253257011908445}, + {1.8703303641352362, 4.209727933188015}, {1.7015273093278767, 7.478882372510933}, + {2.7456295127617385, 7.474216636277054}, {2.2065031771469, 6.896038613284851}, + {3.86008672302403, 7.513564222799629}, {1.9143371250907073, 6.885401350515916}, + {3.7176098065039747, 6.194330707468438}, {0.059011873032214, 5.823535317960799}, + {3.1162712022943757, 6.789029097334483}, {2.4264509160270813, 5.188939408363776}, + {3.154282922203257, 5.788316610960881}}); + + // build a quadtree on the points + auto [point_indices, quadtree] = cuspatial::quadtree_on_points( + points.begin(), points.end(), v_min, v_max, scale, max_depth, max_size, this->stream()); + + T const expansion_radius{2.0}; + + auto multilinestring_array = + cuspatial::test::make_multilinestring_array({0, 1, 2, 3, 4}, + {0, 4, 10, 14, 19}, + {// ring 1 + {2.488450, 5.856625}, + {1.333584, 5.008840}, + {3.460720, 4.586599}, + {2.488450, 5.856625}, + // ring 2 + {5.039823, 4.229242}, + {5.561707, 1.825073}, + {7.103516, 1.503906}, + {7.190674, 4.025879}, + {5.998939, 5.653384}, + {5.039823, 4.229242}, + // ring 3 + {5.998939, 1.235638}, + {5.573720, 0.197808}, + {6.703534, 0.086693}, + {5.998939, 1.235638}, + // ring 4 + {2.088115, 4.541529}, + {1.034892, 3.530299}, + {2.415080, 2.896937}, + {3.208660, 3.745936}, + {2.088115, 4.541529}}); + + auto multilinestrings = multilinestring_array.range(); + + auto bboxes = + rmm::device_uvector>(multilinestrings.num_linestrings(), this->stream()); + + cuspatial::linestring_bounding_boxes(multilinestrings.part_offset_begin(), + multilinestrings.part_offset_end(), + multilinestrings.point_begin(), + multilinestrings.point_end(), + bboxes.begin(), + expansion_radius, + this->stream()); + + auto [linestring_indices, quad_indices] = cuspatial::join_quadtree_and_bounding_boxes( + quadtree, bboxes.begin(), bboxes.end(), v_min, scale, max_depth, this->stream(), this->mr()); + + auto [point_idx, linestring_idx, distance] = + cuspatial::quadtree_point_to_nearest_linestring(linestring_indices.begin(), + linestring_indices.end(), + quad_indices.begin(), + quadtree, + point_indices.begin(), + point_indices.end(), + points.begin(), + multilinestrings, + this->stream()); + + auto expected_distances = []() { + if constexpr (std::is_same_v) { + return make_device_vector( + {3.06755614, 2.55945015, 2.98496079, 1.71036518, 1.82931805, 1.60950696, + 1.68141198, 2.38382101, 2.55103993, 1.66121042, 2.02551198, 2.06608653, + 2.0054605, 1.86834478, 1.94656599, 2.2151804, 1.75039434, 1.48201656, + 1.67690217, 1.6472789, 1.00051796, 1.75223088, 1.84907377, 1.00189602, + 0.760027468, 0.65931344, 1.24821293, 1.32290053, 0.285818338, 0.204662085, + 0.41061914, 0.566183507, 0.0462928228, 0.166630849, 0.449532568, 0.566757083, + 0.842694938, 1.2851826, 0.761564255, 0.978420198, 0.917963803, 1.43116546, + 0.964613676, 0.668479323, 0.983481824, 0.661732435, 0.862337708, 0.50195682, + 0.675588429, 0.825302362, 0.460371286, 0.726516545, 0.5221892, 0.728920817, + 0.0779202655, 0.262149751, 0.331539005, 0.711767673, 0.0811179057, 0.605163872, + 0.0885084718, 1.51270044, 0.389437437, 0.487170845, 1.17812812, 1.8030436, + 1.07697463, 1.1812768, 1.12407148, 1.63790822, 2.15100765}); + } else { + return make_device_vector( + {3.0675562686570932, 2.5594501016565698, 2.9849608928964071, 1.7103652150920774, + 1.8293181280383963, 1.6095070428899729, 1.681412227243898, 2.3838209461314879, + 2.5510398428020409, 1.6612106150272572, 2.0255119347250292, 2.0660867596957564, + 2.005460353737949, 1.8683447535522375, 1.9465658908648766, 2.215180472008103, + 1.7503944159063249, 1.4820166799617225, 1.6769023397521503, 1.6472789467219351, + 1.0005181046076022, 1.7522309916961678, 1.8490738879835735, 1.0018961233717569, + 0.76002760100291122, 0.65931355999132091, 1.2482129257770731, 1.3229005055827028, + 0.28581819228716798, 0.20466187296772376, 0.41061901127492934, 0.56618357460517321, + 0.046292709584059538, 0.16663093663041179, 0.44953247369220306, 0.56675685520587671, + 0.8426949387264755, 1.2851826443010033, 0.7615641155638555, 0.97842040913621187, + 0.91796378078050755, 1.4311654461101424, 0.96461369875795078, 0.66847988653443491, + 0.98348202146010699, 0.66173276971965733, 0.86233789031448094, 0.50195678903916696, + 0.6755886291567379, 0.82530249944765133, 0.46037120394920633, 0.72651648874084795, + 0.52218906793095576, 0.72892093000338909, 0.077921089704128393, 0.26215098141130333, + 0.33153993710577778, 0.71176747526132511, 0.081119666144327182, 0.60516346789266895, + 0.088508309264124049, 1.5127004224070386, 0.38943741327066272, 0.48717099143018805, + 1.1781283344854494, 1.8030436222567465, 1.0769747770485747, 1.181276832710481, + 1.1240715558969043, 1.6379084234284416, 2.1510078772519496}); + } + }(); + + auto expected_point_indices = make_device_vector( + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, + 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70}); + + auto expected_linestring_indices = make_device_vector( + {3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 3, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}); + + CUSPATIAL_EXPECT_VECTORS_EQUIVALENT(expected_point_indices, point_idx); + CUSPATIAL_EXPECT_VECTORS_EQUIVALENT(expected_linestring_indices, linestring_idx); + CUSPATIAL_EXPECT_VECTORS_EQUIVALENT(expected_distances, distance); +} diff --git a/cpp/tests/experimental/spatial/linestring_intersection_intermediates_remove_if_test.cu b/cpp/tests/experimental/spatial/linestring_intersection_intermediates_remove_if_test.cu index 5329b4e28..227040cfa 100644 --- a/cpp/tests/experimental/spatial/linestring_intersection_intermediates_remove_if_test.cu +++ b/cpp/tests/experimental/spatial/linestring_intersection_intermediates_remove_if_test.cu @@ -78,7 +78,7 @@ struct LinestringIntersectionIntermediatesRemoveIfTest : public ::testing::Test intermediates.remove_if(range(d_flags.begin(), d_flags.end()), this->stream()); CUSPATIAL_EXPECT_VECTORS_EQUIVALENT(*intermediates.offsets, *expected.offsets); - if constexpr (cuspatial::is_vec_2d()) + if constexpr (cuspatial::is_vec_2d) CUSPATIAL_EXPECT_VECTORS_EQUIVALENT(*intermediates.geoms, *expected.geoms); else CUSPATIAL_EXPECT_VEC2D_PAIRS_EQUIVALENT(*intermediates.geoms, *expected.geoms); diff --git a/cpp/tests/join/point_to_nearest_linestring_test_small.cpp b/cpp/tests/join/point_to_nearest_linestring_test_small.cpp deleted file mode 100644 index 2c0972165..000000000 --- a/cpp/tests/join/point_to_nearest_linestring_test_small.cpp +++ /dev/null @@ -1,229 +0,0 @@ -/* - * 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. - */ - -#include -#include -#include -#include - -#include -#include -#include - -#include -#include -#include -#include -#include - -#include - -/* - * A small test that it is suitable for manually visualizing point-polygon pairing results in a GIS - * environment GPU results are compared with expected values embedded in code However, the number of - * points in each quadrant is less than 32, the two kernels for point-in-polygon test are not fully - * tested. This is left for pip_refine_test_large. - */ -template -struct PIPRefineTestSmall : public cudf::test::BaseFixture { -}; - -TYPED_TEST_CASE(PIPRefineTestSmall, cudf::test::FloatingPointTypes); - -TYPED_TEST(PIPRefineTestSmall, TestSmall) -{ - using T = TypeParam; - using namespace cudf::test; - - double const x_min{0.0}; - double const x_max{8.0}; - double const y_min{0.0}; - double const y_max{8.0}; - double const scale{1.0}; - uint32_t const max_depth{3}; - uint32_t const min_size{12}; - - fixed_width_column_wrapper x( - {1.9804558865545805, 0.1895259128530169, 1.2591725716781235, 0.8178039499335275, - 0.48171647380517046, 1.3890664414691907, 0.2536015260915061, 3.1907684812039956, - 3.028362149164369, 3.918090468102582, 3.710910700915217, 3.0706987088385853, - 3.572744183805594, 3.7080407833612004, 3.70669993057843, 3.3588457228653024, - 2.0697434332621234, 2.5322042870739683, 2.175448214220591, 2.113652420701984, - 2.520755151373394, 2.9909779614491687, 2.4613232527836137, 4.975578758530645, - 4.07037627210835, 4.300706849071861, 4.5584381091040616, 4.822583857757069, - 4.849847745942472, 4.75489831780737, 4.529792124514895, 4.732546857961497, - 3.7622247877537456, 3.2648444465931474, 3.01954722322135, 3.7164018490892348, - 3.7002781846945347, 2.493975723955388, 2.1807636574967466, 2.566986568683904, - 2.2006520196663066, 2.5104987015171574, 2.8222482218882474, 2.241538022180476, - 2.3007438625108882, 6.0821276168848994, 6.291790729917634, 6.109985464455084, - 6.101327777646798, 6.325158445513714, 6.6793884701899, 6.4274219368674315, - 6.444584786789386, 7.897735998643542, 7.079453687660189, 7.430677191305505, - 7.5085184104988, 7.886010001346151, 7.250745898479374, 7.769497359206111, - 1.8703303641352362, 1.7015273093278767, 2.7456295127617385, 2.2065031771469, - 3.86008672302403, 1.9143371250907073, 3.7176098065039747, 0.059011873032214, - 3.1162712022943757, 2.4264509160270813, 3.154282922203257}); - - fixed_width_column_wrapper y( - {1.3472225743317712, 0.5431061133894604, 0.1448705855995005, 0.8138440641113271, - 1.9022922214961997, 1.5177694304735412, 1.8762161698642947, 0.2621847215928189, - 0.027638405909631958, 0.3338651960183463, 0.9937713340192049, 0.9376313558467103, - 0.33184908855075124, 0.09804238103130436, 0.7485845679979923, 0.2346381514128677, - 1.1809465376402173, 1.419555755682142, 1.2372448404986038, 1.2774712415624014, - 1.902015274420646, 1.2420487904041893, 1.0484414482621331, 0.9606291981013242, - 1.9486902798139454, 0.021365525588281198, 1.8996548860019926, 0.3234041700489503, - 1.9531893897409585, 0.7800065259479418, 1.942673409259531, 0.5659923375279095, - 2.8709552313924487, 2.693039435509084, 2.57810040095543, 2.4612194182614333, - 2.3345952955903906, 3.3999020934055837, 3.2296461832828114, 3.6607732238530897, - 3.7672478678985257, 3.0668114607133137, 3.8159308233351266, 3.8812819070357545, - 3.6045900851589048, 2.5470532680258002, 2.983311357415729, 2.2235950639628523, - 2.5239201807166616, 2.8765450351723674, 2.5605928243991434, 2.9754616970668213, - 2.174562817047202, 3.380784914178574, 3.063690547962938, 3.380489849365283, - 3.623862886287816, 3.538128217886674, 3.4154469467473447, 3.253257011908445, - 4.209727933188015, 7.478882372510933, 7.474216636277054, 6.896038613284851, - 7.513564222799629, 6.885401350515916, 6.194330707468438, 5.823535317960799, - 6.789029097334483, 5.188939408363776, 5.788316610960881}); - - auto quadtree_pair = cuspatial::quadtree_on_points( - x, y, x_min, x_max, y_min, y_max, scale, max_depth, min_size, this->mr()); - - auto& quadtree = std::get<1>(quadtree_pair); - auto& point_indices = std::get<0>(quadtree_pair); - - double const expansion_radius{2.0}; - fixed_width_column_wrapper linestring_offsets({0, 4, 10, 14, 19}); - fixed_width_column_wrapper linestring_x({// ring 1 - 2.488450, - 1.333584, - 3.460720, - 2.488450, - // ring 2 - 5.039823, - 5.561707, - 7.103516, - 7.190674, - 5.998939, - 5.039823, - // ring 3 - 5.998939, - 5.573720, - 6.703534, - 5.998939, - // ring 4 - 2.088115, - 1.034892, - 2.415080, - 3.208660, - 2.088115}); - fixed_width_column_wrapper linestring_y({// ring 1 - 5.856625, - 5.008840, - 4.586599, - 5.856625, - // ring 2 - 4.229242, - 1.825073, - 1.503906, - 4.025879, - 5.653384, - 4.229242, - // ring 3 - 1.235638, - 0.197808, - 0.086693, - 1.235638, - // ring 4 - 4.541529, - 3.530299, - 2.896937, - 3.745936, - 4.541529}); - - auto linestring_bboxes = cuspatial::linestring_bounding_boxes( - linestring_offsets, linestring_x, linestring_y, expansion_radius, this->mr()); - - auto polygon_quadrant_pairs = cuspatial::join_quadtree_and_bounding_boxes( - *quadtree, *linestring_bboxes, x_min, x_max, y_min, y_max, scale, max_depth, this->mr()); - - auto point_to_linestring_distances = - cuspatial::quadtree_point_to_nearest_linestring(*polygon_quadrant_pairs, - *quadtree, - *point_indices, - x, - y, - linestring_offsets, - linestring_x, - linestring_y, - this->mr()); - - CUSPATIAL_EXPECTS(point_to_linestring_distances->num_columns() == 3, - "a point-to-linestring distance table must have 3 columns"); - - CUSPATIAL_EXPECTS( - point_to_linestring_distances->num_rows() == point_indices->size(), - "number of point-to-linestring distance pairs should be the same as number of points"); - - auto expected_distances_column = []() { - if (std::is_same()) { - return fixed_width_column_wrapper( - {3.06755614, 2.55945015, 2.98496079, 1.71036518, 1.82931805, 1.60950696, - 1.68141198, 2.38382101, 2.55103993, 1.66121042, 2.02551198, 2.06608653, - 2.0054605, 1.86834478, 1.94656599, 2.2151804, 1.75039434, 1.48201656, - 1.67690217, 1.6472789, 1.00051796, 1.75223088, 1.84907377, 1.00189602, - 0.760027468, 0.65931344, 1.24821293, 1.32290053, 0.285818338, 0.204662085, - 0.41061914, 0.566183507, 0.0462928228, 0.166630849, 0.449532568, 0.566757083, - 0.842694938, 1.2851826, 0.761564255, 0.978420198, 0.917963803, 1.43116546, - 0.964613676, 0.668479323, 0.983481824, 0.661732435, 0.862337708, 0.50195682, - 0.675588429, 0.825302362, 0.460371286, 0.726516545, 0.5221892, 0.728920817, - 0.0779202655, 0.262149751, 0.331539005, 0.711767673, 0.0811179057, 0.605163872, - 0.0885084718, 1.51270044, 0.389437437, 0.487170845, 1.17812812, 1.8030436, - 1.07697463, 1.1812768, 1.12407148, 1.63790822, 2.15100765}); - } - return fixed_width_column_wrapper( - {3.0675562686570932, 2.5594501016565698, 2.9849608928964071, 1.7103652150920774, - 1.8293181280383963, 1.6095070428899729, 1.681412227243898, 2.3838209461314879, - 2.5510398428020409, 1.6612106150272572, 2.0255119347250292, 2.0660867596957564, - 2.005460353737949, 1.8683447535522375, 1.9465658908648766, 2.215180472008103, - 1.7503944159063249, 1.4820166799617225, 1.6769023397521503, 1.6472789467219351, - 1.0005181046076022, 1.7522309916961678, 1.8490738879835735, 1.0018961233717569, - 0.76002760100291122, 0.65931355999132091, 1.2482129257770731, 1.3229005055827028, - 0.28581819228716798, 0.20466187296772376, 0.41061901127492934, 0.56618357460517321, - 0.046292709584059538, 0.16663093663041179, 0.44953247369220306, 0.56675685520587671, - 0.8426949387264755, 1.2851826443010033, 0.7615641155638555, 0.97842040913621187, - 0.91796378078050755, 1.4311654461101424, 0.96461369875795078, 0.66847988653443491, - 0.98348202146010699, 0.66173276971965733, 0.86233789031448094, 0.50195678903916696, - 0.6755886291567379, 0.82530249944765133, 0.46037120394920633, 0.72651648874084795, - 0.52218906793095576, 0.72892093000338909, 0.077921089704128393, 0.26215098141130333, - 0.33153993710577778, 0.71176747526132511, 0.081119666144327182, 0.60516346789266895, - 0.088508309264124049, 1.5127004224070386, 0.38943741327066272, 0.48717099143018805, - 1.1781283344854494, 1.8030436222567465, 1.0769747770485747, 1.181276832710481, - 1.1240715558969043, 1.6379084234284416, 2.1510078772519496}); - }(); - - fixed_width_column_wrapper expected1( - {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, - 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, - 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70}); - - fixed_width_column_wrapper expected2( - {3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 3, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, - 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}); - - auto expected = cudf::table_view{ - {cudf::column_view(expected1), cudf::column_view(expected2), expected_distances_column}}; - - CUDF_TEST_EXPECT_TABLES_EQUIVALENT(expected, *point_to_linestring_distances); -} diff --git a/cpp/tests/join/quadtree_point_to_nearest_linestring_test.cpp b/cpp/tests/join/quadtree_point_to_nearest_linestring_test.cpp new file mode 100644 index 000000000..fbb3b0c19 --- /dev/null +++ b/cpp/tests/join/quadtree_point_to_nearest_linestring_test.cpp @@ -0,0 +1,313 @@ +/* + * 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 +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +using T = float; + +template +using wrapper = cudf::test::fixed_width_column_wrapper; + +struct QuadtreePointToNearestLinestringErrorTest : public ::testing::Test { + auto prepare_test(cudf::column_view const& x, + cudf::column_view const& y, + cudf::column_view const& linestring_offsets, + cudf::column_view const& polygon_x, + cudf::column_view const& polygon_y, + cuspatial::vec_2d v_min, + cuspatial::vec_2d v_max, + T scale, + uint32_t max_depth, + uint32_t min_size, + T expansion_radius) + { + using namespace cudf::test; + + auto quadtree_pair = cuspatial::quadtree_on_points( + x, y, v_min.x, v_max.x, v_min.y, v_max.y, scale, max_depth, min_size); + + auto& quadtree = std::get<1>(quadtree_pair); + auto& point_indices = std::get<0>(quadtree_pair); + + auto linestring_bboxes = cuspatial::linestring_bounding_boxes( + linestring_offsets, polygon_x, polygon_y, expansion_radius); + + auto linestring_quadrant_pairs = cuspatial::join_quadtree_and_bounding_boxes( + *quadtree, *linestring_bboxes, v_min.x, v_max.x, v_min.y, v_max.y, scale, max_depth); + + return std::make_tuple(std::move(quadtree), + std::move(point_indices), + std::move(linestring_bboxes), + std::move(linestring_quadrant_pairs)); + } + + void SetUp() override + { + using namespace cudf::test; + + cuspatial::vec_2d v_min{0.0, 0.0}; + cuspatial::vec_2d v_max{8.0, 8.0}; + double const scale{1.0}; + uint32_t const max_depth{3}; + uint32_t const min_size{12}; + double const expansion_radius{0.0}; + + auto x_col = + wrapper({1.9804558865545805, 0.1895259128530169, 1.2591725716781235, 0.8178039499335275}); + auto y_col = + wrapper({1.3472225743317712, 0.5431061133894604, 0.1448705855995005, 0.8138440641113271}); + + auto linestring_offsets_col = wrapper({0, 4, 10}); + auto linestring_x_col = wrapper({// ring 1 + 2.488450, + 1.333584, + 3.460720, + 2.488450, + // ring 2 + 5.039823, + 5.561707, + 7.103516, + 7.190674, + 5.998939, + 5.039823}); + auto linestring_y_col = wrapper({// ring 1 + 2.488450, + 1.333584, + 3.460720, + 2.488450, + // ring 2 + 5.039823, + 5.561707, + 7.103516, + 7.190674, + 5.998939, + 5.039823}); + + std::tie(quadtree, point_indices, linestring_bboxes, linestring_quadrant_pairs) = + prepare_test(x_col, + y_col, + linestring_offsets_col, + linestring_x_col, + linestring_y_col, + v_min, + v_max, + scale, + max_depth, + min_size, + expansion_radius); + + x = x_col.release(); + y = y_col.release(); + linestring_offsets = linestring_offsets_col.release(); + linestring_x = linestring_x_col.release(); + linestring_y = linestring_y_col.release(); + } + + void TearDown() override {} + + std::unique_ptr x; + std::unique_ptr y; + std::unique_ptr linestring_offsets; + std::unique_ptr linestring_x; + std::unique_ptr linestring_y; + std::unique_ptr point_indices; + std::unique_ptr quadtree; + std::unique_ptr linestring_bboxes; + std::unique_ptr linestring_quadrant_pairs; +}; + +// test cudf::quadtree_point_in_polygon with empty inputs +TEST_F(QuadtreePointToNearestLinestringErrorTest, test_empty) +{ + // empty point data + { + auto empty_point_indices = wrapper({}); + auto empty_x = wrapper({}); + auto empty_y = wrapper({}); + + auto results = cuspatial::quadtree_point_to_nearest_linestring(*linestring_quadrant_pairs, + *quadtree, + empty_point_indices, + empty_x, + empty_y, + *linestring_offsets, + *linestring_x, + *linestring_y); + + auto expected_linestring_offset = wrapper({}); + auto expected_point_offset = wrapper({}); + auto expected_distance = wrapper({}); + + auto expected = + cudf::table_view{{expected_linestring_offset, expected_point_offset, expected_distance}}; + + CUDF_TEST_EXPECT_TABLES_EQUIVALENT(expected, *results); + } + + // empty linestring data + { + auto empty_linestring_offsets = wrapper({}); + auto empty_linestring_x = wrapper({}); + auto empty_linestring_y = wrapper({}); + + auto results = cuspatial::quadtree_point_to_nearest_linestring(*linestring_quadrant_pairs, + *quadtree, + *point_indices, + *x, + *y, + empty_linestring_offsets, + empty_linestring_x, + empty_linestring_y); + + auto expected_linestring_offset = wrapper({}); + auto expected_point_offset = wrapper({}); + auto expected_distance = wrapper({}); + + auto expected = + cudf::table_view{{expected_linestring_offset, expected_point_offset, expected_distance}}; + + CUDF_TEST_EXPECT_TABLES_EQUIVALENT(expected, *results); + } +} + +TEST_F(QuadtreePointToNearestLinestringErrorTest, type_mismatch) +{ + // x/y type mismatch + { + auto x_col = wrapper({1, 2, 3, 4}); + auto y_col = wrapper({1, 2, 3, 4}); + + EXPECT_THROW(cuspatial::quadtree_point_to_nearest_linestring(*linestring_quadrant_pairs, + *quadtree, + *point_indices, + x_col, + y_col, + *linestring_offsets, + *linestring_x, + *linestring_y), + cuspatial::logic_error); + } + + // linestring_x/linestring_y type mismatch + { + auto linestring_x_col = wrapper({1, 2, 3, 4}); + auto linestring_y_col = wrapper({1, 2, 3, 4}); + + EXPECT_THROW(cuspatial::quadtree_point_to_nearest_linestring(*linestring_quadrant_pairs, + *quadtree, + *point_indices, + *x, + *y, + *linestring_offsets, + linestring_x_col, + linestring_y_col), + cuspatial::logic_error); + } + + // x / linestring_x type mismatch + { + auto x_col = wrapper({1, 2, 3, 4}); + auto linestring_x_col = wrapper({1, 2, 3, 4}); + + EXPECT_THROW(cuspatial::quadtree_point_to_nearest_linestring(*linestring_quadrant_pairs, + *quadtree, + *point_indices, + x_col, + *y, + *linestring_offsets, + linestring_x_col, + *linestring_y), + cuspatial::logic_error); + } +} + +TEST_F(QuadtreePointToNearestLinestringErrorTest, size_mismatch) +{ + { + auto linestring_offsets_col = wrapper({0, 4, 10}); + auto linestring_x = wrapper({1, 2, 3, 4, 5, 6, 7, 8, 9, 10}); + auto linestring_y = wrapper({1, 2, 3, 4, 5, 6}); + + EXPECT_THROW(cuspatial::quadtree_point_to_nearest_linestring(*linestring_quadrant_pairs, + *quadtree, + *point_indices, + *x, + *y, + linestring_offsets_col, + linestring_x, + linestring_y), + cuspatial::logic_error); + } + { + auto point_indices = wrapper({0, 1, 2, 3, 4}); + auto x = wrapper({1, 2, 3, 4, 5, 6, 7, 8, 9, 10}); + auto y = wrapper({1, 2, 3, 4, 5, 6, 7, 8, 9, 10}); + + EXPECT_THROW(cuspatial::quadtree_point_to_nearest_linestring(*linestring_quadrant_pairs, + *quadtree, + point_indices, + x, + y, + *linestring_offsets, + *linestring_x, + *linestring_y), + cuspatial::logic_error); + } + { + auto point_indices = wrapper({0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}); + auto x = wrapper({1, 2, 3, 4, 5}); + auto y = wrapper({1, 2, 3, 4, 5, 6, 7, 8, 9, 10}); + + EXPECT_THROW(cuspatial::quadtree_point_to_nearest_linestring(*linestring_quadrant_pairs, + *quadtree, + point_indices, + x, + y, + *linestring_offsets, + *linestring_x, + *linestring_y), + cuspatial::logic_error); + } + { + auto point_indices = wrapper({0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}); + auto x = wrapper({1, 2, 3, 4, 5, 6, 7, 8, 9, 10}); + auto y = wrapper({1, 2, 3, 4, 5}); + + EXPECT_THROW(cuspatial::quadtree_point_to_nearest_linestring(*linestring_quadrant_pairs, + *quadtree, + point_indices, + x, + y, + *linestring_offsets, + *linestring_x, + *linestring_y), + cuspatial::logic_error); + } +} diff --git a/python/cuspatial/cuspatial/tests/spatial/join/test_spatial_join.py b/python/cuspatial/cuspatial/tests/spatial/join/test_spatial_join.py index a25a567a5..938ffac95 100644 --- a/python/cuspatial/cuspatial/tests/spatial/join/test_spatial_join.py +++ b/python/cuspatial/cuspatial/tests/spatial/join/test_spatial_join.py @@ -12,18 +12,20 @@ bbox_2 = (0, 2, 0, 2) small_poly_offsets = cudf.Series([0, 1, 2, 3, 4], dtype=np.uint32) -small_ring_offsets = cudf.Series([0, 3, 8, 12, 17], dtype=np.uint32) +small_ring_offsets = cudf.Series([0, 4, 10, 14, 19], dtype=np.uint32) small_poly_xs = cudf.Series( [ 2.488450, 1.333584, 3.460720, + 2.488450, 5.039823, 5.561707, 7.103516, 7.190674, 5.998939, + 5.039823, 5.998939, 5.573720, 6.703534, @@ -41,11 +43,13 @@ 5.856625, 5.008840, 4.586599, + 5.856625, 4.229242, 1.825073, 1.503906, 4.025879, 5.653384, + 4.229242, 1.235638, 0.197808, 0.086693,