diff --git a/python/cuspatial/benchmarks/api/bench_api.py b/python/cuspatial/benchmarks/api/bench_api.py index 261746f1c..0ad32b4ed 100644 --- a/python/cuspatial/benchmarks/api/bench_api.py +++ b/python/cuspatial/benchmarks/api/bench_api.py @@ -121,14 +121,18 @@ def bench_points_in_spatial_window(benchmark, gpu_dataframe): geometry = gpu_dataframe["geometry"] mean_x, std_x = (geometry.polygons.x.mean(), geometry.polygons.x.std()) mean_y, std_y = (geometry.polygons.y.mean(), geometry.polygons.y.std()) + points = cuspatial.GeoSeries.from_points_xy( + cudf.DataFrame( + {"x": geometry.polygons.x, "y": geometry.polygons.y} + ).interleave_columns() + ) benchmark( cuspatial.points_in_spatial_window, + points, mean_x - std_x, mean_x + std_x, mean_y - std_y, mean_y + std_y, - geometry.polygons.x, - geometry.polygons.y, ) @@ -136,13 +140,16 @@ def bench_quadtree_on_points(benchmark, gpu_dataframe): polygons = gpu_dataframe["geometry"].polygons x_points = (cupy.random.random(10000000) - 0.5) * 360 y_points = (cupy.random.random(10000000) - 0.5) * 180 + points = cuspatial.GeoSeries.from_points_xy( + cudf.DataFrame({"x": x_points, "y": y_points}).interleave_columns() + ) + scale = 5 max_depth = 7 min_size = 125 benchmark( cuspatial.quadtree_on_points, - x_points, - y_points, + points, polygons.x.min(), polygons.x.max(), polygons.y.min(), @@ -160,9 +167,11 @@ def bench_quadtree_point_in_polygon(benchmark, polygons): scale = 5 max_depth = 7 min_size = 125 + points = cuspatial.GeoSeries.from_points_xy( + cudf.DataFrame({"x": x_points, "y": y_points}).interleave_columns() + ) point_indices, quadtree = cuspatial.quadtree_on_points( - x_points, - y_points, + points, polygons.x.min(), polygons.x.max(), polygons.y.min(), @@ -215,9 +224,11 @@ def bench_quadtree_point_to_nearest_linestring(benchmark): polygons = gpu_countries["geometry"].polygons points_x = gpu_cities["geometry"].points.x points_y = gpu_cities["geometry"].points.y + points = cuspatial.GeoSeries.from_points_xy( + cudf.DataFrame({"x": points_x, "y": points_y}).interleave_columns() + ) point_indices, quadtree = cuspatial.quadtree_on_points( - points_x, - points_y, + points, polygons.x.min(), polygons.x.max(), polygons.y.min(), diff --git a/python/cuspatial/cuspatial/core/spatial/filtering.py b/python/cuspatial/cuspatial/core/spatial/filtering.py index d05e6dd49..be7689e1d 100644 --- a/python/cuspatial/cuspatial/core/spatial/filtering.py +++ b/python/cuspatial/cuspatial/core/spatial/filtering.py @@ -1,13 +1,14 @@ -# Copyright (c) 2022, NVIDIA CORPORATION. +# Copyright (c) 2022-2023, NVIDIA CORPORATION. from cudf import DataFrame from cudf.core.column import as_column from cuspatial._lib import spatial_window -from cuspatial.utils.column_utils import normalize_point_columns +from cuspatial.core.geoseries import GeoSeries +from cuspatial.utils.column_utils import contains_only_points -def points_in_spatial_window(min_x, max_x, min_y, max_y, xs, ys): +def points_in_spatial_window(points: GeoSeries, min_x, max_x, min_y, max_y): """Return only the subset of coordinates that fall within a rectangular window. @@ -19,32 +20,40 @@ def points_in_spatial_window(min_x, max_x, min_y, max_y, xs, ys): Parameters ---------- - min_x + points: GeoSeries + A geoseries of points + min_x: float lower x-coordinate of the query window - max_x + max_x: float upper x-coordinate of the query window - min_y + min_y: float lower y-coordinate of the query window - max_y + max_y: float upper y-coordinate of the query window - xs - column of x-coordinates that may fall within the window - ys - column of y-coordinates that may fall within the window Returns ------- - result : cudf.DataFrame - subset of `(x, y)` pairs above that fall within the window + result : GeoSeries + subset of `points` above that fall within the window Notes ----- * Swaps ``min_x`` and ``max_x`` if ``min_x > max_x`` * Swaps ``min_y`` and ``max_y`` if ``min_y > max_y`` """ - xs, ys = normalize_point_columns(as_column(xs), as_column(ys)) - return DataFrame._from_data( + + if len(points) == 0: + return GeoSeries([]) + + if not contains_only_points(points): + raise ValueError("GeoSeries must contain only points.") + + xs = as_column(points.points.x) + ys = as_column(points.points.y) + + res_xy = DataFrame._from_data( *spatial_window.points_in_spatial_window( min_x, max_x, min_y, max_y, xs, ys ) - ) + ).interleave_columns() + return GeoSeries.from_points_xy(res_xy) diff --git a/python/cuspatial/cuspatial/core/spatial/indexing.py b/python/cuspatial/cuspatial/core/spatial/indexing.py index 435355819..52d63e274 100644 --- a/python/cuspatial/cuspatial/core/spatial/indexing.py +++ b/python/cuspatial/cuspatial/core/spatial/indexing.py @@ -1,18 +1,19 @@ -# Copyright (c) 2022, NVIDIA CORPORATION. +# Copyright (c) 2022-2023, NVIDIA CORPORATION. import warnings from cudf import DataFrame, Series from cudf.core.column import as_column +from cuspatial import GeoSeries from cuspatial._lib.quadtree import ( quadtree_on_points as cpp_quadtree_on_points, ) -from cuspatial.utils.column_utils import normalize_point_columns +from cuspatial.utils.column_utils import contains_only_points def quadtree_on_points( - xs, ys, x_min, x_max, y_min, y_max, scale, max_depth, max_size + points: GeoSeries, x_min, x_max, y_min, y_max, scale, max_depth, max_size ): """ Construct a quadtree from a set of points for a given area-of-interest @@ -20,10 +21,8 @@ def quadtree_on_points( Parameters ---------- - xs - Column of x-coordinates for each point. - ys - Column of y-coordinates for each point. + points + Series of points. x_min The lower-left x-coordinate of the area of interest bounding box. x_max @@ -157,7 +156,12 @@ def quadtree_on_points( Length: 120, dtype: int32 """ - xs, ys = normalize_point_columns(as_column(xs), as_column(ys)) + if not len(points) == 0 and not contains_only_points(points): + raise ValueError("GeoSeries must contain only points.") + + xs = as_column(points.points.x) + ys = as_column(points.points.y) + x_min, x_max, y_min, y_max = ( min(x_min, x_max), max(x_min, x_max), diff --git a/python/cuspatial/cuspatial/tests/spatial/filtering/test_points_in_spatial_window.py b/python/cuspatial/cuspatial/tests/spatial/filtering/test_points_in_spatial_window.py index 677f37eca..214a44b82 100644 --- a/python/cuspatial/cuspatial/tests/spatial/filtering/test_points_in_spatial_window.py +++ b/python/cuspatial/cuspatial/tests/spatial/filtering/test_points_in_spatial_window.py @@ -1,26 +1,25 @@ -# Copyright (c) 2019, NVIDIA CORPORATION. +# Copyright (c) 2019-2023, NVIDIA CORPORATION. +import geopandas as gpd import pytest - -import cudf +from geopandas.testing import assert_geoseries_equal +from shapely.geometry import Point import cuspatial def test_zeros(): result = cuspatial.points_in_spatial_window( # noqa: F841 - 0, 0, 0, 0, cudf.Series([0.0]), cudf.Series([0.0]) + cuspatial.GeoSeries([Point(0, 0)]), 0, 0, 0, 0 ) assert result.empty def test_centered(): - result = cuspatial.points_in_spatial_window( - -1, 1, -1, 1, cudf.Series([0.0]), cudf.Series([0.0]) - ) - cudf.testing.assert_frame_equal( - result, cudf.DataFrame({"x": [0.0], "y": [0.0]}) - ) + s = cuspatial.GeoSeries([Point(0, 0)]) + result = cuspatial.points_in_spatial_window(s, -1, 1, -1, 1) + + assert_geoseries_equal(result.to_geopandas(), gpd.GeoSeries([Point(0, 0)])) @pytest.mark.parametrize( @@ -29,38 +28,48 @@ def test_centered(): def test_corners(coords): x, y = coords result = cuspatial.points_in_spatial_window( - -1.1, 1.1, -1.1, 1.1, cudf.Series([x]), cudf.Series([y]) - ) - cudf.testing.assert_frame_equal( - result, cudf.DataFrame({"x": [x], "y": [y]}) + cuspatial.GeoSeries([Point(x, y)]), -1.1, 1.1, -1.1, 1.1 ) + assert_geoseries_equal(result.to_geopandas(), gpd.GeoSeries([Point(x, y)])) def test_pair(): result = cuspatial.points_in_spatial_window( - -1.1, 1.1, -1.1, 1.1, cudf.Series([0.0, 1.0]), cudf.Series([1.0, 0.0]) + cuspatial.GeoSeries([Point(0, 1), Point(1, 0)]), -1.1, 1.1, -1.1, 1.1 ) - cudf.testing.assert_frame_equal( - result, cudf.DataFrame({"x": [0.0, 1.0], "y": [1.0, 0.0]}) + assert_geoseries_equal( + result.to_geopandas(), gpd.GeoSeries([Point(0, 1), Point(1, 0)]) ) def test_oob(): result = cuspatial.points_in_spatial_window( - -1, 1, -1, 1, cudf.Series([-2.0, 2.0]), cudf.Series([2.0, -2.0]) + cuspatial.GeoSeries([Point(-2.0, 2.0), Point(2.0, -2.0)]), + -1, + 1, + -1, + 1, ) - cudf.testing.assert_frame_equal(result, cudf.DataFrame({"x": [], "y": []})) + assert_geoseries_equal(result.to_geopandas(), gpd.GeoSeries([])) def test_half(): result = cuspatial.points_in_spatial_window( + cuspatial.GeoSeries( + [ + Point(-1.0, 1.0), + Point(1.0, -1.0), + Point(3.0, 3.0), + Point(-3.0, -3.0), + ] + ), -2, 2, -2, 2, - cudf.Series([-1.0, 1.0, 3.0, -3.0]), - cudf.Series([1.0, -1.0, 3.0, -3.0]), ) - cudf.testing.assert_frame_equal( - result, cudf.DataFrame({"x": [-1.0, 1.0], "y": [1.0, -1.0]}) + + assert_geoseries_equal( + result.to_geopandas(), + gpd.GeoSeries([Point(-1.0, 1.0), Point(1.0, -1.0)]), ) diff --git a/python/cuspatial/cuspatial/tests/spatial/indexing/test_indexing.py b/python/cuspatial/cuspatial/tests/spatial/indexing/test_indexing.py index 4163f547b..9ede78630 100644 --- a/python/cuspatial/cuspatial/tests/spatial/indexing/test_indexing.py +++ b/python/cuspatial/cuspatial/tests/spatial/indexing/test_indexing.py @@ -1,6 +1,7 @@ -# Copyright (c) 2020, NVIDIA CORPORATION. +# Copyright (c) 2020-2023, NVIDIA CORPORATION. import numpy as np import pytest +from shapely.geometry import Point import cudf @@ -13,8 +14,7 @@ def test_empty(): # empty should not throw order, quadtree = cuspatial.quadtree_on_points( - cudf.Series([]), # x - cudf.Series([]), # y + cuspatial.GeoSeries([]), *bbox_1, # bbox 1, # scale 1, # max_depth @@ -34,11 +34,9 @@ def test_empty(): ) -@pytest.mark.parametrize("dtype", ["float32", "float64"]) -def test_one_point(dtype): +def test_one_point(): order, quadtree = cuspatial.quadtree_on_points( - cudf.Series([0.5]).astype(dtype), # x - cudf.Series([0.5]).astype(dtype), # y + cuspatial.GeoSeries([Point(0.5, 0.5)]), *bbox_1, # bbox 1, # scale 1, # max_depth @@ -58,11 +56,9 @@ def test_one_point(dtype): ) -@pytest.mark.parametrize("dtype", ["float32", "float64"]) -def test_two_points(dtype): +def test_two_points(): order, quadtree = cuspatial.quadtree_on_points( - cudf.Series([0.5, 1.5]).astype(dtype), # x - cudf.Series([0.5, 1.5]).astype(dtype), # y + cuspatial.GeoSeries([Point(0.5, 0.5), Point(1.5, 1.5)]), *bbox_2, # bbox 1, # scale 1, # max_depth @@ -84,161 +80,162 @@ def test_two_points(dtype): @pytest.mark.parametrize("dtype", ["float32", "float64"]) def test_small_number_of_points(dtype): + x = cudf.Series( + [ + 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, + ] + ).astype( + dtype + ) # x + y = cudf.Series( + [ + 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, + ] + ).astype(dtype) + xy = cudf.DataFrame({"x": x, "y": y}).interleave_columns() + points = cuspatial.GeoSeries.from_points_xy(xy) order, quadtree = cuspatial.quadtree_on_points( - cudf.Series( - [ - 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, - ] - ).astype( - dtype - ), # x - cudf.Series( - [ - 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, - ] - ).astype( - dtype - ), # y + points, 0, # x_min 8, # x_max 0, # y_min 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 13e5fa0ed..190fca450 100644 --- a/python/cuspatial/cuspatial/tests/spatial/join/test_spatial_join.py +++ b/python/cuspatial/cuspatial/tests/spatial/join/test_spatial_join.py @@ -210,9 +210,16 @@ @pytest.mark.parametrize("dtype", [np.float32, np.float64]) def test_empty(dtype): + points = cuspatial.GeoSeries.from_points_xy( + cudf.DataFrame( + { + "x": cudf.Series([], dtype=dtype), # x + "y": cudf.Series([], dtype=dtype), # y + } + ).interleave_columns() + ) order, quadtree = cuspatial.quadtree_on_points( - cudf.Series([], dtype=dtype), # x - cudf.Series([], dtype=dtype), # y + points, *bbox_1, # bbox 1, # scale 1, # max_depth @@ -254,11 +261,18 @@ def test_polygon_join_small(dtype): min_size = 12 points_x = small_points_x.astype(dtype) points_y = small_points_y.astype(dtype) + points = cuspatial.GeoSeries.from_points_xy( + cudf.DataFrame( + { + "x": points_x, # x + "y": points_y, # y + } + ).interleave_columns() + ) poly_points_x = small_poly_xs.astype(dtype) poly_points_y = small_poly_ys.astype(dtype) point_indices, quadtree = cuspatial.quadtree_on_points( - points_x, - points_y, + points, x_min, x_max, y_min, @@ -310,11 +324,18 @@ def test_linestring_join_small(dtype): expansion_radius = 2.0 points_x = small_points_x.astype(dtype) points_y = small_points_y.astype(dtype) + points = cuspatial.GeoSeries.from_points_xy( + cudf.DataFrame( + { + "x": points_x, # x + "y": points_y, # y + } + ).interleave_columns() + ) linestring_points_x = small_poly_xs.astype(dtype) linestring_points_y = small_poly_ys.astype(dtype) point_indices, quadtree = cuspatial.quadtree_on_points( - points_x, - points_y, + points, x_min, x_max, y_min, @@ -411,11 +432,18 @@ def test_quadtree_point_in_polygon_small(dtype): min_size = 12 points_x = small_points_x.astype(dtype) points_y = small_points_y.astype(dtype) + points = cuspatial.GeoSeries.from_points_xy( + cudf.DataFrame( + { + "x": points_x, # x + "y": points_y, # y + } + ).interleave_columns() + ) poly_points_x = small_poly_xs.astype(dtype) poly_points_y = small_poly_ys.astype(dtype) point_indices, quadtree = cuspatial.quadtree_on_points( - points_x, - points_y, + points, x_min, x_max, y_min, @@ -501,11 +529,18 @@ def run_test_quadtree_point_to_nearest_linestring_small( expansion_radius = 2.0 points_x = small_points_x.astype(dtype) points_y = small_points_y.astype(dtype) + points = cuspatial.GeoSeries.from_points_xy( + cudf.DataFrame( + { + "x": points_x, # x + "y": points_y, # y + } + ).interleave_columns() + ) linestring_points_x = small_poly_xs.astype(dtype) linestring_points_y = small_poly_ys.astype(dtype) point_indices, quadtree = cuspatial.quadtree_on_points( - points_x, - points_y, + points, x_min, x_max, y_min,