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

Header-only polygon_bounding_boxes and linestring_bounding_boxes, make_geometry_id_iterator utility, and box<T> class. #820

Merged
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
a23f06a
Add failing tests and empty header-only implementation
harrism Nov 23, 2022
dcc0f7f
Working header-only API
harrism Nov 23, 2022
df8d8f9
Port column-based API to call header-only API
harrism Nov 23, 2022
fe7bf68
Merge branch 'branch-23.02' into fea-header-only-polygon-bboxes
harrism Nov 24, 2022
44e2bd2
Fix tests, add expansion_radius
harrism Nov 24, 2022
5b87d80
Reinstate early out for empty
harrism Nov 24, 2022
02ad17b
tests
harrism Nov 24, 2022
2675fef
Add make_box_output_iterator and make_geometry_id_iterator factories.
harrism Nov 30, 2022
947da30
box type
harrism Nov 30, 2022
18642c1
use make_geometry_id_iterator
harrism Nov 30, 2022
2fdd802
Use box type
harrism Nov 30, 2022
bec3e95
doc
harrism Nov 30, 2022
7f85e72
Correct tparam name in doc
harrism Nov 30, 2022
1ebaea8
implementation tparam names
harrism Nov 30, 2022
a9bf663
header-only linestring_bounding_boxes refactor
harrism Nov 30, 2022
2df0fae
Merge branch 'branch-23.02' into fea-header-only-polygon-bboxes
harrism Dec 6, 2022
6f5bf15
Add deduction guides.
harrism Dec 7, 2022
de41f67
Fix index_to_geometry_id to actually return an ID and fix examples in…
harrism Dec 7, 2022
26acde0
Merge branch 'branch-23.02' into fea-header-only-polygon-bboxes
harrism Dec 13, 2022
63f770b
Merge branch 'branch-23.02' into fea-header-only-polygon-bboxes
harrism Dec 13, 2022
4b27c39
Generalize expect_segment_equivalent to expect_vec_2d_pair_equivalent…
harrism Dec 13, 2022
c86a332
Document segment and make more generic.
harrism Dec 13, 2022
79a9168
doc
harrism Dec 13, 2022
e0fc84a
doc
harrism Dec 13, 2022
fb1f2be
Missed first->v1, second -> v2
harrism Dec 13, 2022
fc2b339
Comply with GeoArrow spec for geometry and part offsets.
harrism Dec 13, 2022
fd8a67e
Add column API doc precondition about offsets for GeoArrow
harrism Dec 13, 2022
586e471
Fix python tests and column-based API offset size expectations
harrism Dec 14, 2022
53d3e0e
As above for linestring
harrism Dec 14, 2022
bce13d3
Add insufficient offsets test
harrism Dec 14, 2022
69ea0a1
include
harrism Dec 14, 2022
d0f7541
Add missing param docs
harrism Dec 14, 2022
434bf2f
Merge branch 'branch-23.02' into fea-header-only-polygon-bboxes
harrism Dec 14, 2022
b22019a
Fix quadtree bounding box tests for arrow offsets
harrism Dec 14, 2022
54f3bb0
Fix spatial join pytest
harrism Dec 14, 2022
d630cbb
Merge branch 'branch-23.02' into fea-header-only-polygon-bboxes
harrism Dec 14, 2022
7e1576a
Fix segment conflicts
harrism Dec 14, 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
17 changes: 5 additions & 12 deletions cpp/include/cuspatial/experimental/detail/bounding_box.cuh
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

#pragma once

#include <cuspatial/experimental/iterator_factory.cuh>
#include <cuspatial/traits.hpp>
#include <cuspatial/vec_2d.hpp>

Expand All @@ -33,32 +34,24 @@ namespace detail {

template <typename T>
struct point_bounding_box {
using point_tuple = thrust::tuple<cuspatial::vec_2d<T>, cuspatial::vec_2d<T>>;

vec_2d<T> box_offset{};

CUSPATIAL_HOST_DEVICE point_bounding_box(T expansion_radius = T{0})
: box_offset{expansion_radius, expansion_radius}
{
}

inline __host__ __device__ point_tuple operator()(vec_2d<T> const& point)
inline CUSPATIAL_HOST_DEVICE box<T> operator()(vec_2d<T> const& point)
{
return point_tuple{point - box_offset, point + box_offset};
return box<T>{point - box_offset, point + box_offset};
}
};

template <typename T>
struct box_minmax {
using point_tuple = thrust::tuple<cuspatial::vec_2d<T>, cuspatial::vec_2d<T>>;

inline __host__ __device__ point_tuple operator()(point_tuple const& a, point_tuple const& b)
inline CUSPATIAL_HOST_DEVICE box<T> operator()(box<T> const& a, box<T> const& b)
{
// structured binding doesn't seem to work with thrust::tuple
vec_2d<T> p1, p2, p3, p4;
thrust::tie(p1, p2) = a;
thrust::tie(p3, p4) = b;
return {box_min(box_min(p1, p2), p3), box_max(box_max(p1, p2), p4)};
return {box_min(box_min(a.v1, a.v2), b.v1), box_max(box_max(a.v1, a.v2), b.v2)};
}
};

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* 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/experimental/bounding_box.cuh>
#include <cuspatial/experimental/iterator_factory.cuh>
#include <cuspatial/traits.hpp>

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

#include <thrust/gather.h>
#include <thrust/scan.h>
#include <thrust/scatter.h>

namespace cuspatial {

thomcom marked this conversation as resolved.
Show resolved Hide resolved
template <class OffsetIteratorA,
class OffsetIteratorB,
class VertexIterator,
class BoundingBoxIterator,
class T,
class IndexT>
BoundingBoxIterator polygon_bounding_boxes(OffsetIteratorA polygon_offsets_first,
OffsetIteratorA polygon_offsets_last,
OffsetIteratorB polygon_ring_offsets_first,
OffsetIteratorB polygon_ring_offsets_last,
VertexIterator polygon_vertices_first,
VertexIterator polygon_vertices_last,
BoundingBoxIterator bounding_boxes_first,
T expansion_radius,
rmm::cuda_stream_view stream)
{
static_assert(is_floating_point<T>(), "Only floating point polygon vertices supported");

static_assert(is_same<vec_2d<T>, iterator_value_type<VertexIterator>>(),
"Input vertices must be cuspatial::vec_2d");

static_assert(cuspatial::is_integral<iterator_value_type<OffsetIteratorA>,
iterator_value_type<OffsetIteratorB>>(),
"OffsetIterators must have integral value type.");

auto const num_polys = std::distance(polygon_offsets_first, polygon_offsets_last);
auto const num_rings = std::distance(polygon_ring_offsets_first, polygon_ring_offsets_last);
auto const num_poly_vertices = std::distance(polygon_vertices_first, polygon_vertices_last);

CUSPATIAL_EXPECTS(num_rings >= num_polys, "Each polygon must have at least one ring");

CUSPATIAL_EXPECTS(num_poly_vertices >= num_polys * 3,
"Each ring must have at least three vertices");

if (num_polys == 0 || num_rings == 0 || num_poly_vertices == 0) { return bounding_boxes_first; }

auto vertex_ids_iter = make_geometry_id_iterator<IndexT>(
polygon_offsets_first, polygon_offsets_last, polygon_ring_offsets_first);

return point_bounding_boxes(vertex_ids_iter,
vertex_ids_iter + num_poly_vertices,
polygon_vertices_first,
bounding_boxes_first,
expansion_radius,
stream);
}
} // namespace cuspatial
54 changes: 54 additions & 0 deletions cpp/include/cuspatial/experimental/geometry/box.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* 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/vec_2d.hpp>

namespace cuspatial {

/**
* @addtogroup types
* @{
*/

/**
* @brief A generic axis-aligned box type.
*
* @tparam T the base type for the coordinates
* @tparam Vertex the vector type to use for vertices, vec_2d<T> by default
*/
template <typename T, typename Vertex = cuspatial::vec_2d<T>>
class alignas(sizeof(Vertex)) box {
public:
Vertex v1;
harrism marked this conversation as resolved.
Show resolved Hide resolved
Vertex v2;

private:
/**
* @brief Compare two bounding boxes for equality.
*/
friend bool CUSPATIAL_HOST_DEVICE operator==(box<T> const& lhs, box<T> const& rhs)
{
return (lhs.v1 == rhs.v1);
thomcom marked this conversation as resolved.
Show resolved Hide resolved
}
};

/**
* @} // end of doxygen group
*/

} // namespace cuspatial
143 changes: 142 additions & 1 deletion cpp/include/cuspatial/experimental/iterator_factory.cuh
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@

#include <cuspatial/detail/iterator.hpp>
#include <cuspatial/error.hpp>
#include <cuspatial/experimental/geometry/box.hpp>
#include <cuspatial/traits.hpp>
#include <cuspatial/vec_2d.hpp>

#include <thrust/binary_search.h>
#include <thrust/detail/raw_reference_cast.h>
#include <thrust/iterator/permutation_iterator.h>
#include <thrust/iterator/transform_iterator.h>
Expand Down Expand Up @@ -57,6 +59,18 @@ struct vec_2d_to_tuple {
}
};

/**
* @internal
* @brief Helper to convert a `bouinding_box` into a tuple of elements
thomcom marked this conversation as resolved.
Show resolved Hide resolved
*/
template <typename T, typename Box = box<T>>
struct box_to_tuple {
__device__ thrust::tuple<T, T, T, T> operator()(Box const& box)
harrism marked this conversation as resolved.
Show resolved Hide resolved
{
return thrust::make_tuple(box.v1.x, box.v1.y, box.v2.x, box.v2.y);
}
};

/**
* @internal
* @brief Generic to convert any iterator pointing to interleaved xy range into
Expand Down Expand Up @@ -120,6 +134,26 @@ struct strided_functor {
auto __device__ operator()(std::size_t i) { return i * stride; }
};

/**
* @internal
* @brief Functor to transform an index into a geometry ID determined by a range of offsets.
*/
template <class IndexT, class GeometryIter>
struct index_to_geometry_id {
GeometryIter geometry_begin;
GeometryIter geometry_end;

index_to_geometry_id(GeometryIter begin, GeometryIter end)
: geometry_begin(begin), geometry_end(end)
{
}

CUSPATIAL_HOST_DEVICE auto operator()(IndexT idx)
{
return thrust::prev(thrust::upper_bound(thrust::seq, geometry_begin, geometry_end, idx));
}
harrism marked this conversation as resolved.
Show resolved Hide resolved
};

} // namespace detail

/**
Expand Down Expand Up @@ -153,7 +187,7 @@ auto make_vec_2d_iterator(FirstIter first, SecondIter second)
using T = typename std::iterator_traits<FirstIter>::value_type;
static_assert(is_same<T, iterator_value_type<SecondIter>>(), "Iterator value_type mismatch");

auto zipped = thrust::make_zip_iterator(thrust::make_tuple(first, second));
auto zipped = thrust::make_zip_iterator(first, second);
return thrust::make_transform_iterator(zipped, detail::tuple_to_vec_2d<T>());
}

Expand Down Expand Up @@ -224,6 +258,113 @@ auto make_vec_2d_output_iterator(Iter d_points_begin)
return thrust::make_transform_output_iterator(zipped_outputs, detail::vec_2d_to_tuple<T>());
}

/**
* @brief Create an output iterator to `box` data from multiple output iterators.
*
* Creates an output iterator from separate coordinate iterators to which
* can be written interleaved x/y data for box vertices (2 per box). This allows
* using four separate arrays of output data with APIs that expect an iterator to
* structured box data.
*
* @tparam MinXIter Iterator type to the x-coordinate of the first box vertex. Must meet the
* requirements of [LegacyRandomAccessIterator][LinkLRAI], be mutable and be device-accessible.
* @tparam MinYIter Iterator type to the y-coordinate of the first box vertex. Must meet the
* requirements of [LegacyRandomAccessIterator][LinkLRAI], be mutable and be device-accessible.
* @tparam MaxXIter Iterator type to the x-coordinate of the second box vertex. Must meet the
* requirements of [LegacyRandomAccessIterator][LinkLRAI], be mutable and be device-accessible.
* @tparam MaxYIter Iterator type to the y-coordinate of the second box vertex. Must meet the
* requirements of [LegacyRandomAccessIterator][LinkLRAI], be mutable and be device-accessible.
* @param min_x Iterator to beginning of `x` data for first box vertices.
* @param min_y Iterator to beginning of `y` data for first box vertices.
* @param max_x Iterator to beginning of `x` data for second box vertices.
* @param max_y Iterator to beginning of `y` data for second box vertices.
* @return Iterator to `box`
*
* @pre Input iterators must iterate on the same data type.
*
* [LinkLRAI]: https://en.cppreference.com/w/cpp/named_req/RandomAccessIterator
* "LegacyRandomAccessIterator"
*/
template <typename MinXIter, typename MinYIter, typename MaxXIter, typename MaxYIter>
auto make_box_output_iterator(MinXIter min_x, MinYIter min_y, MaxXIter max_x, MaxYIter max_y)
{
using T = typename std::iterator_traits<MinXIter>::value_type;
auto zipped_out = thrust::make_zip_iterator(min_x, min_y, max_x, max_y);
return thrust::transform_output_iterator(zipped_out, detail::box_to_tuple<T>());
}

/**
* @brief Create an input iterator that generates sequential geometry IDs for each element based
* on the input offset range.
*
* This can be used for any single-level geometry offsets, e.g. multipoints, multilinestrings,
* (multi)trajectories. And using custom iterators it can be used for nested types.
*
* Example:
* @code
* auto offsets = std::vector<int>({0, 3, 5, 9});
* auto iter = make_geometry_id_iterator(offsets.begin(), offsets.end());
* auto ids = std::vector<int>(10);
* std::copy_n(iter, 10, ids.begin()); // ids now contains [0, 0, 0, 3, 3, 5, 5, 5, 5, 9]
* @endcode
*
* @tparam GeometryIter The offset iterator type. Must meet the requirements
* of [LegacyRandomAccessIterator][LinkLRAI] and be device-accessible.
* @param offsets_begin Beginning of range of geometry offsets
* @param offsets_end End of range of geometry offsets
* @return An iterator over unique IDs for each element of each geometry defined by the offsets
*
* [LinkLRAI]: https://en.cppreference.com/w/cpp/named_req/RandomAccessIterator
* "LegacyRandomAccessIterator"
*/
template <typename IndexT, typename GeometryIter>
auto make_geometry_id_iterator(GeometryIter offsets_begin, GeometryIter offsets_end)
{
return detail::make_counting_transform_iterator(
IndexT{0}, detail::index_to_geometry_id<IndexT, GeometryIter>{offsets_begin, offsets_end});
}

/**
* @brief Create an input iterator that generates sequential geometry IDs for each element of a
* nested geometry based on the input geometry and part offset ranges.
*
* This can be used for any two-level nested multigeometry offsets, e.g. multipolygons.
*
* Example:
* @code
* auto poly_offsets = std::vector<int>({0, 1, 3}); // poly 1 has 2 rings
* auto ring_offsets = std::vector<int>({0, 4, 7, 10});
* auto iter = make_geometry_id_iterator(poly_offsets.begin(),
* poly_offsets.end(),
* ring_offsets.begin());
* auto ids = std::vector<int>(13);
* std::copy_n(iter, 13, ids.begin());
harrism marked this conversation as resolved.
Show resolved Hide resolved
* // ids now contains [0, 0, 0, 0, 4, 4, 4, 4, 4, 4, 10, 10, 10]
* @endcode
*
* @tparam GeometryIter The offset iterator type. Must meet the requirements
* of [LegacyRandomAccessIterator][LinkLRAI] and be device-accessible.
* @param offsets_begin Beginning of range of geometry offsets
* @param offsets_end End of range of geometry offsets
harrism marked this conversation as resolved.
Show resolved Hide resolved
* @return An iterator over unique IDs for each element of each geometry defined by the offsets
*
* [LinkLRAI]: https://en.cppreference.com/w/cpp/named_req/RandomAccessIterator
* "LegacyRandomAccessIterator"
*/
template <typename IndexT, typename GeometryIter, typename PartIter>
auto make_geometry_id_iterator(GeometryIter geometry_offsets_begin,
GeometryIter geometry_offsets_end,
PartIter part_offsets_begin)
{
auto first_part_offsets_begin =
thrust::make_permutation_iterator(part_offsets_begin, geometry_offsets_begin);

return make_geometry_id_iterator<IndexT>(
first_part_offsets_begin,
thrust::next(first_part_offsets_begin,
std::distance(geometry_offsets_begin, geometry_offsets_end)));
}

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