Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add C++ API for point_linestring_nearest_points #658

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
75 commits
Select commit Hold shift + click to select a range
de78310
Initial to atomics refactoring
isVoid Jun 14, 2022
6011900
Initial refactoring geometry utilities
isVoid Jun 14, 2022
9761eab
Add header only api
isVoid Jun 14, 2022
2ed60eb
Add cudf column API
isVoid Jun 14, 2022
e3ec12d
cmake updates
isVoid Jun 14, 2022
a72ac10
inline atomics to avoid compiling RDC
isVoid Jun 14, 2022
31b1e78
Rename to proper naming convention
isVoid Jun 14, 2022
f55af2f
make all atomics inline
isVoid Jun 14, 2022
e874f3b
Merge branch 'improvement/device_atomics_refactor' into feature/point…
isVoid Jun 14, 2022
e9b2ca8
Merge branch 'improvement/geometry_utilities' into feature/point_line…
isVoid Jun 14, 2022
f2277f0
Changes header only API to accept only single offset iterator
isVoid Jun 15, 2022
7bc0914
Add header only API tests
isVoid Jun 15, 2022
a8ec5ff
Update cudf column API calls
isVoid Jun 15, 2022
1fdfd59
Initial add utility method
isVoid Jun 15, 2022
0274875
Add counting transform iterators
isVoid Jun 15, 2022
df286a0
Revert "Update cudf column API calls"
isVoid Jun 15, 2022
688fbb5
Revert "Add cudf column API"
isVoid Jun 15, 2022
d0c0c4f
Merge branch 'branch-22.08' of https://github.com/rapidsai/cuspatial …
isVoid Aug 1, 2022
1c4ad61
Update refactored helper locations
isVoid Aug 1, 2022
e4038a5
Add cudf column API
isVoid Aug 1, 2022
5004bbe
Add equality test utility and fix header only tests
isVoid Aug 1, 2022
986dbb2
minor docstring updates
isVoid Aug 1, 2022
2feab4b
Add cudf column API documentation
isVoid Aug 1, 2022
5e07194
Include point-linestring distance files in docs.
isVoid Aug 1, 2022
4834aa2
Apply suggestions from code review
isVoid Aug 1, 2022
de0feb7
Address review comments
isVoid Aug 11, 2022
c897974
Merge branch 'feature/header_only_point_linestring_distance' of githu…
isVoid Aug 11, 2022
6dc34e0
Merge branch 'branch-22.10' of https://github.com/rapidsai/cuspatial …
isVoid Aug 16, 2022
624a97f
Fix broken builds
isVoid Aug 16, 2022
7d8b4b6
Initial scaffolding for nearest point
isVoid Aug 17, 2022
488ab7a
refactor primitives
isVoid Aug 17, 2022
26a6c6a
Initial implementation of kernel
isVoid Aug 17, 2022
8165c68
Check CUDA Error after launch
isVoid Aug 17, 2022
37de419
Assert multi-point, multi-linestring format
isVoid Aug 19, 2022
924ef59
Add `interleaved_iterator_to_vec_2d_iterator`
isVoid Aug 19, 2022
999d5d7
Multi-geom implementation
isVoid Aug 19, 2022
164fd68
Flesh out cudf column API
isVoid Aug 20, 2022
faeff0e
rename header only API iterators
isVoid Sep 6, 2022
4729e4d
Merge branch 'branch-22.10' of https://github.com/rapidsai/cuspatial …
isVoid Sep 6, 2022
d34ee82
Fixes tuple construction in helper
isVoid Sep 8, 2022
59a2207
Variable renames, adds static asserts
isVoid Sep 8, 2022
aa39cdc
Adds vec2d to interleaved transformer
isVoid Sep 8, 2022
529d7b2
Variable renames, use interleaved iterator transformer
isVoid Sep 8, 2022
7e1dffe
Adds simple test, vector of vec2d equivalence test utility
isVoid Sep 8, 2022
50bfd25
fix end of segment access, writes intra-index not global index to res…
isVoid Sep 9, 2022
fde05b8
Merge branch 'branch-22.10' of https://github.com/rapidsai/cuspatial …
isVoid Sep 13, 2022
afae694
docstring for column API
isVoid Sep 13, 2022
55411a4
Update to also return multipoint index when needed
isVoid Sep 13, 2022
35a661b
Add column API impl to cmake
isVoid Sep 13, 2022
ff8be26
Update header only API tests to accomodate the new return type
isVoid Sep 13, 2022
50a2aea
use `constexpr` instead
isVoid Sep 13, 2022
a2240ed
Make column API device span const access
isVoid Sep 13, 2022
b0c3665
Add column API tests
isVoid Sep 13, 2022
93d6c5f
Add invalid input tests
isVoid Sep 13, 2022
281b972
Bug: min distance squared should be initialized for all points of a m…
isVoid Sep 13, 2022
b4e1c47
Bug: return coordinate array should be npairs*2; geometry array lengt…
isVoid Sep 13, 2022
9f260f7
MInor test fixes
isVoid Sep 13, 2022
7863a3e
Rename launch functor
isVoid Sep 13, 2022
633cdaa
Write throws in docstring
isVoid Sep 13, 2022
979611b
Update header only API docs
isVoid Sep 13, 2022
f3f52dc
Add docstring for kernel
isVoid Sep 13, 2022
6701d64
Typo
isVoid Sep 13, 2022
6e3d785
Rename the function as `nearest_points`
isVoid Sep 14, 2022
15af211
Merge branch 'branch-22.10' of https://github.com/rapidsai/cuspatial …
isVoid Sep 15, 2022
6c13809
use latest vector<vec_2d> matcher
isVoid Sep 15, 2022
946bd10
Merge branch 'branch-22.10' of https://github.com/rapidsai/cuspatial …
isVoid Sep 19, 2022
cb80449
replace tuple with custom struct
isVoid Sep 19, 2022
1c8ea50
rename column API file and add API to doc page
isVoid Sep 19, 2022
fceafaf
fix tests after rename
isVoid Sep 19, 2022
ce1d8b7
address review comments in bulk
isVoid Sep 20, 2022
9dd1a1e
revert the use of thrust::tuple
isVoid Sep 21, 2022
f72ef3d
Add two tests for nearest end points
isVoid Sep 21, 2022
3fba1c4
add more corner case tests
isVoid Sep 21, 2022
ed1bf28
Apply suggestions from code review
isVoid Sep 21, 2022
8f5d7c1
style
isVoid Sep 22, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ add_library(cuspatial
src/spatial/linestring_distance.cu
src/spatial/point_distance.cu
src/spatial/point_linestring_distance.cu
src/spatial/point_linestring_nearest_points.cu
src/spatial/lonlat_to_cartesian.cu
src/trajectory/derive_trajectories.cu
src/trajectory/trajectory_bounding_boxes.cu
Expand Down
36 changes: 29 additions & 7 deletions cpp/include/cuspatial/detail/utility/linestring.cuh
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

#pragma once

#include <thrust/tuple.h>

#include <cuspatial/vec_2d.hpp>

namespace cuspatial {
Expand All @@ -40,24 +42,44 @@ endpoint_index_of_linestring(SizeType const& linestring_idx,

/**
* @internal
* @brief Computes shortest distance between @p c and segment ab
* @brief Computes shortest distance and nearest point between @p c and segment
* ab
*/
template <typename T>
__forceinline__ T __device__ point_to_segment_distance_squared(vec_2d<T> const& c,
vec_2d<T> const& a,
vec_2d<T> const& b)
__forceinline__ thrust::tuple<T, vec_2d<T>> __device__
point_to_segment_distance_squared_nearest_point(vec_2d<T> const& c,
vec_2d<T> const& a,
vec_2d<T> const& b)
{
auto ab = b - a;
auto ac = c - a;
auto l_squared = dot(ab, ab);
if (l_squared == 0) { return dot(ac, ac); }
if (l_squared == 0) { return thrust::make_tuple(dot(ac, ac), a); }
auto r = dot(ac, ab);
auto bc = c - b;
// If the projection of `c` is outside of segment `ab`, compute point-point distance.
if (r <= 0 or r >= l_squared) { return std::min(dot(ac, ac), dot(bc, bc)); }
if (r <= 0 or r >= l_squared) {
auto dac = dot(ac, ac);
auto dbc = dot(bc, bc);
return dac < dbc ? thrust::make_tuple(dac, a) : thrust::make_tuple(dbc, b);
}
auto p = a + (r / l_squared) * ab;
auto pc = c - p;
return dot(pc, pc);
return thrust::make_tuple(dot(pc, pc), p);
}

/**
* @internal
* @brief Computes shortest distance between @p c and segment ab
*/
template <typename T>
__forceinline__ T __device__ point_to_segment_distance_squared(vec_2d<T> const& c,
vec_2d<T> const& a,
vec_2d<T> const& b)
{
[[maybe_unused]] auto [distance_squared, _] =
point_to_segment_distance_squared_nearest_point(c, a, b);
return distance_squared;
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
/*
* 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 <cuspatial/detail/utility/device_atomics.cuh>
#include <cuspatial/detail/utility/linestring.cuh>
#include <cuspatial/error.hpp>
#include <cuspatial/traits.hpp>
#include <cuspatial/vec_2d.hpp>

#include <rmm/cuda_stream_view.hpp>
#include <rmm/exec_policy.hpp>

#include <thrust/binary_search.h>

#include <iterator>
#include <limits>
#include <memory>
#include <type_traits>

namespace cuspatial {
namespace detail {

/**
* @internal
* @brief Kernel to compute the nearest point between a multipoint and multilinestring
*
* See header only API for input parameter definitions.
*
* Each thread computes the nearest point between a pair of multipoint and multilinestring.
* The minimum distance between the geometries are stored in `min_distance_squared` and updated
* when smaller is encountered. `linestring.cuh::point_to_segment_distance_squared_nearest_point`
* is used to compute the nearest point on the segment and its distance to the test point.
*/
template <class Vec2dItA,
class Vec2dItB,
class OffsetIteratorA,
class OffsetIteratorB,
class OffsetIteratorC,
class OutputIt>
void __global__
pairwise_point_linestring_nearest_points_kernel(OffsetIteratorA points_geometry_offsets_first,
OffsetIteratorA points_geometry_offsets_last,
Vec2dItA points_first,
Vec2dItA points_last,
OffsetIteratorB linestring_geometry_offsets_first,
OffsetIteratorB linestring_geometry_offsets_last,
OffsetIteratorC linestring_part_offsets_first,
OffsetIteratorC linestring_part_offsets_last,
Vec2dItB linestring_points_first,
Vec2dItB linestring_points_last,
OutputIt output_first)
{
using T = iterator_vec_base_type<Vec2dItA>;
using IndexType = iterator_value_type<OffsetIteratorA>;

auto num_pairs =
thrust::distance(points_geometry_offsets_first, points_geometry_offsets_last) - 1;
auto num_linestring_points = thrust::distance(linestring_points_first, linestring_points_last);

for (auto idx = threadIdx.x + blockIdx.x * blockDim.x; idx < num_pairs;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seeing this quadruple nested loop, I see now how important it will be to add automatic geometry iterators that know how to do the nested indexing. You want to be able to just iterate over the pairs, and for each pair, just have a for_each over the points and a nested for_each over the points of the multilinestring.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, abstracting different level of iteration is direly needed. Perhaps we can continue the discussion in #677 ?

idx += gridDim.x * blockDim.x) {
IndexType nearest_point_idx;
IndexType nearest_part_idx;
IndexType nearest_segment_idx;
vec_2d<T> nearest_point;

T min_distance_squared = std::numeric_limits<T>::max();
IndexType point_start = points_geometry_offsets_first[idx];
IndexType point_end = points_geometry_offsets_first[idx + 1];
for (auto point_idx = point_start; point_idx < point_end; point_idx++) {
IndexType linestring_parts_start = linestring_geometry_offsets_first[idx];
IndexType linestring_parts_end = linestring_geometry_offsets_first[idx + 1];

for (auto part_idx = linestring_parts_start; part_idx < linestring_parts_end; part_idx++) {
IndexType segment_start = linestring_part_offsets_first[part_idx];
// The last point of the linestring does not form a segment
IndexType segment_end = linestring_part_offsets_first[part_idx + 1] - 1;

for (auto segment_idx = segment_start; segment_idx < segment_end; segment_idx++) {
vec_2d<T> c = points_first[point_idx];
vec_2d<T> a = linestring_points_first[segment_idx];
vec_2d<T> b = linestring_points_first[segment_idx + 1];

auto distance_nearest_point_pair =
point_to_segment_distance_squared_nearest_point(c, a, b);
auto distance_squared = thrust::get<0>(distance_nearest_point_pair);
if (distance_squared < min_distance_squared) {
min_distance_squared = distance_squared;
nearest_point_idx = point_idx - point_start;
nearest_part_idx = part_idx - linestring_parts_start;
nearest_segment_idx = segment_idx - segment_start;
nearest_point = thrust::get<1>(distance_nearest_point_pair);
}
}
}
}
output_first[idx] =
thrust::make_tuple(nearest_point_idx, nearest_part_idx, nearest_segment_idx, nearest_point);
}
}

} // namespace detail

template <class Vec2dItA,
class Vec2dItB,
class OffsetIteratorA,
class OffsetIteratorB,
class OffsetIteratorC,
class OutputIt>
OutputIt pairwise_point_linestring_nearest_points(OffsetIteratorA points_geometry_offsets_first,
OffsetIteratorA points_geometry_offsets_last,
Vec2dItA points_first,
Vec2dItA points_last,
OffsetIteratorB linestring_geometry_offsets_first,
OffsetIteratorC linestring_part_offsets_first,
OffsetIteratorC linestring_part_offsets_last,
Vec2dItB linestring_points_first,
Vec2dItB linestring_points_last,
OutputIt output_first,
rmm::cuda_stream_view stream)
{
using T = iterator_vec_base_type<Vec2dItA>;

static_assert(is_same_floating_point<T, iterator_vec_base_type<Vec2dItB>>(),
"Coordinates must be the same floating point type.");

static_assert(is_same<vec_2d<T>, iterator_value_type<Vec2dItA>, iterator_value_type<Vec2dItB>>(),
"Inputs must be cuspatial::vec_2d<T>");

auto num_pairs = std::distance(points_geometry_offsets_first, points_geometry_offsets_last) - 1;

if (num_pairs == 0) return output_first;

std::size_t constexpr threads_per_block = 256;
std::size_t const num_blocks = (num_pairs + threads_per_block - 1) / threads_per_block;

detail::pairwise_point_linestring_nearest_points_kernel<<<num_blocks,
threads_per_block,
0,
stream.value()>>>(
points_geometry_offsets_first,
points_geometry_offsets_last,
points_first,
points_last,
linestring_geometry_offsets_first,
linestring_geometry_offsets_first + num_pairs + 1,
linestring_part_offsets_first,
linestring_part_offsets_last,
linestring_points_first,
linestring_points_last,
output_first);

CUSPATIAL_CUDA_TRY(cudaGetLastError());

return output_first + num_pairs;
}

} // namespace cuspatial
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/*
* 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 <rmm/cuda_stream_view.hpp>

namespace cuspatial {

/**
* @brief Compute the nearest points and geometry ID between pairs of multipoint and
* multilinestring
*
* The nearest point from a test point to a linestring is a point on the linestring that has
* the shortest distance to the test point compared to any other points on the linestring.
*
* The nearest point from a test multipoint to a multilinestring is the nearest point that
* has the shortest distance in all pairs of points and linestrings.
*
* In addition, this API writes these geometry and part ID where the nearest point locates to output
* iterators:
* - The point ID indicates which point in the multipoint is the nearest point.
* - The linestring ID is the offset within the multilinestring that contains the nearest point.
* - The segment ID is the offset within the linestring of the segment that contains the nearest
* point. It is the same as the ID of the starting point of the segment.
*
* @tparam Cart2dItA iterator type for point array of the point element of each pair. Must meet
* the requirements of [LegacyRandomAccessIterator][LinkLRAI] and be device-accessible.
* @tparam Cart2dItB iterator type for point array of the linestring element of each pair. Must meet
* the requirements of [LegacyRandomAccessIterator][LinkLRAI] and be device-accessible.
* @tparam OffsetIteratorA iterator type for `point_geometry_offset` array. Must meet the
* requirements of [LegacyRandomAccessIterator][LinkLRAI] and be device-accessible.
* @tparam OffsetIteratorB iterator type for `linestring_geometry_offset` array. Must meet the
* requirements of [LegacyRandomAccessIterator][LinkLRAI] and be device-accessible.
* @tparam OffsetIteratorC iterator type for `linestring_part_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] and be device-accessible.
isVoid marked this conversation as resolved.
Show resolved Hide resolved
*
* @param point_geometry_offset_first beginning of the range of multipoint geometries of each
* pair
* @param point_geometry_offset_last end of the range of multipoint geometries of each pair
* @param points_first beginning of the range of point values
* @param points_last end of the range of the point values
* @param linestring_geometry_offset_first beginning of the range of offsets to the multilinestring
* geometry of each pair, the end range is implied by linestring_geometry_offset_first +
* std::distance(`point_geometry_offset_first`, `point_geometry_offset_last`)
* @param linestring_offsets_first beginning of the range of offsets to the starting point
* of each linestring
* @param linestring_offsets_last end of the range of offsets to the starting point
* of each linestring
* @param linestring_points_first beginning of the range of linestring points
* @param linestring_points_last end of the range of linestring points
* @param output_first A zipped-iterator of 4 outputs. The first element should be compatible
* with iterator_value_type<OffsetIteratorA>, stores the geometry ID of the nearest point in
* multipoint. The second element should be compatible with iterator_value_type<OffsetIteratorB>,
* stores the geometry ID of the nearest linestring. The third element should be compatible with
* iterator_value_type<OffsetIteratorC>, stores the part ID to the nearest segment. The forth
* element should be compatible with vec_2d, stores the coordinate of the nearest point on the
* (multi)linestring.
* @param stream The CUDA stream to use for device memory operations and kernel launches.
* @return Output iterator to the element past the last tuple computed.
*
* @pre all input iterators for coordinates must have `cuspatial::vec_2d` type,
* and must have the same base floating point type.
*
* [LinkLRAI]: https://en.cppreference.com/w/cpp/named_req/RandomAccessIterator
* "LegacyRandomAccessIterator"
*/
template <class Vec2dItA,
class Vec2dItB,
class OffsetIteratorA,
class OffsetIteratorB,
class OffsetIteratorC,
class OutputIt>
OutputIt pairwise_point_linestring_nearest_points(
OffsetIteratorA points_geometry_offsets_first,
OffsetIteratorA points_geometry_offsets_last,
Vec2dItA points_first,
Vec2dItA points_last,
OffsetIteratorB linestring_geometry_offsets_first,
OffsetIteratorC linestring_part_offsets_first,
OffsetIteratorC linestring_part_offsets_last,
Vec2dItB linestring_points_first,
Vec2dItB linestring_points_last,
OutputIt output_first,
rmm::cuda_stream_view stream = rmm::cuda_stream_default);
} // namespace cuspatial

#include <cuspatial/experimental/detail/point_linestring_nearest_points.cuh>
30 changes: 30 additions & 0 deletions cpp/include/cuspatial/experimental/type_utils.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include <cuspatial/traits.hpp>
#include <cuspatial/vec_2d.hpp>

#include <thrust/iterator/permutation_iterator.h>
#include <thrust/iterator/transform_iterator.h>
#include <thrust/iterator/transform_output_iterator.h>
#include <thrust/iterator/zip_iterator.h>
Expand Down Expand Up @@ -132,6 +133,35 @@ auto interleaved_iterator_to_vec_2d_iterator(Iter d_points_begin)
return detail::make_counting_transform_iterator(0, interleaved_to_vec_2d<T>{d_points_begin});
}

struct strided_functor {
std::size_t _stride;
strided_functor(std::size_t stride) : _stride(stride) {}
auto __device__ operator()(std::size_t i) { return i * _stride; }
};

/**
* @brief Create an output iterator to `vec_2d` data from an iterator to an interleaved array.
*
* @tparam Iter type of iterator to interleaved data. Must meet the requirements of
* [LegacyRandomAccessIterator][LinkLRAI], be mutable and be device-accessible.
* @param d_points_begin Iterator to beginning of interleaved data.
* @return Iterator to `vec_2d`
*/
template <typename Iter>
auto vec_2d_iterator_to_output_interleaved_iterator(Iter d_points_begin)
{
using T = typename std::iterator_traits<Iter>::value_type;
auto fixed_stride_2_functor = strided_functor(2);
auto even_positions = thrust::make_permutation_iterator(
d_points_begin, detail::make_counting_transform_iterator(0, fixed_stride_2_functor));
auto odd_positions = thrust::make_permutation_iterator(
thrust::next(d_points_begin),
detail::make_counting_transform_iterator(0, fixed_stride_2_functor));
auto zipped_outputs =
thrust::make_zip_iterator(thrust::make_tuple(even_positions, odd_positions));
return thrust::make_transform_output_iterator(zipped_outputs, detail::vec_2d_to_tuple<T>());
}

/**
* @} // end of doxygen group
*/
Expand Down
Loading