Skip to content

Commit 95b8d66

Browse files
committed
Add ASV benchmarks for rasterize (#989)
Seven benchmark classes covering polygons (simple grid + complex irregular), lines, points, mixed geometry types, GeoDataFrame input parsing, and polygon count scaling. Each benchmarked at 100-3000px resolution on both numpy and cupy backends.
1 parent 49f0c9f commit 95b8d66

File tree

1 file changed

+255
-0
lines changed

1 file changed

+255
-0
lines changed

benchmarks/benchmarks/rasterize.py

Lines changed: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
1+
import numpy as np
2+
from shapely.geometry import box, LineString, MultiPoint, Point, Polygon
3+
4+
from xrspatial.rasterize import rasterize
5+
6+
try:
7+
import cupy
8+
_has_cupy = True
9+
except ImportError:
10+
_has_cupy = False
11+
12+
try:
13+
import geopandas as gpd
14+
_has_geopandas = True
15+
except ImportError:
16+
_has_geopandas = False
17+
18+
19+
def _make_polygon_grid(nx, ny, n_polys_x=10, n_polys_y=5):
20+
"""Generate a grid of non-overlapping rectangular polygons."""
21+
rng = np.random.default_rng(55128)
22+
dx = 360.0 / n_polys_x
23+
dy = 180.0 / n_polys_y
24+
geoms = []
25+
vals = []
26+
for iy in range(n_polys_y):
27+
for ix in range(n_polys_x):
28+
# Slightly inset so polygons don't share edges
29+
x0 = -180.0 + ix * dx + 0.1
30+
y0 = -90.0 + iy * dy + 0.1
31+
x1 = x0 + dx - 0.2
32+
y1 = y0 + dy - 0.2
33+
geoms.append(box(x0, y0, x1, y1))
34+
vals.append(rng.uniform(1, 100))
35+
return geoms, vals
36+
37+
38+
def _make_complex_polygons(n_polys=20, n_vertices=64):
39+
"""Generate irregular polygons with many vertices."""
40+
rng = np.random.default_rng(99231)
41+
geoms = []
42+
vals = []
43+
for i in range(n_polys):
44+
cx = rng.uniform(-150, 150)
45+
cy = rng.uniform(-70, 70)
46+
r = rng.uniform(5, 20)
47+
angles = np.sort(rng.uniform(0, 2 * np.pi, n_vertices))
48+
radii = r * (0.7 + 0.3 * rng.uniform(size=n_vertices))
49+
xs = cx + radii * np.cos(angles)
50+
ys = cy + radii * np.sin(angles)
51+
coords = list(zip(xs, ys))
52+
coords.append(coords[0])
53+
geoms.append(Polygon(coords))
54+
vals.append(float(i + 1))
55+
return geoms, vals
56+
57+
58+
def _make_line_network(n_lines=50, n_vertices=10):
59+
"""Generate a set of polylines with multiple vertices."""
60+
rng = np.random.default_rng(31782)
61+
geoms = []
62+
vals = []
63+
for i in range(n_lines):
64+
xs = np.cumsum(rng.uniform(-5, 5, n_vertices)) + rng.uniform(-150, 150)
65+
ys = np.cumsum(rng.uniform(-5, 5, n_vertices)) + rng.uniform(-70, 70)
66+
xs = np.clip(xs, -180, 180)
67+
ys = np.clip(ys, -90, 90)
68+
geoms.append(LineString(zip(xs, ys)))
69+
vals.append(float(i + 1))
70+
return geoms, vals
71+
72+
73+
def _make_point_cloud(n_points=500):
74+
"""Generate scattered points."""
75+
rng = np.random.default_rng(44091)
76+
xs = rng.uniform(-180, 180, n_points)
77+
ys = rng.uniform(-90, 90, n_points)
78+
geoms = [Point(x, y) for x, y in zip(xs, ys)]
79+
vals = rng.uniform(1, 100, n_points).tolist()
80+
return geoms, vals
81+
82+
83+
# -------------------------------------------------------------------------
84+
# Polygon rasterization benchmarks
85+
# -------------------------------------------------------------------------
86+
87+
class RasterizePolygons:
88+
params = ([100, 300, 1000, 3000], ["numpy", "cupy"])
89+
param_names = ("nx", "backend")
90+
91+
def setup(self, nx, backend):
92+
if backend == "cupy" and not _has_cupy:
93+
raise NotImplementedError("CuPy not available")
94+
ny = nx // 2
95+
self.geoms, self.vals = _make_polygon_grid(nx, ny)
96+
self.pairs = list(zip(self.geoms, self.vals))
97+
self.bounds = (-180, -90, 180, 90)
98+
self.width = nx
99+
self.height = ny
100+
self.use_cuda = (backend == "cupy")
101+
102+
def time_rasterize_polygons(self, nx, backend):
103+
rasterize(self.pairs, width=self.width, height=self.height,
104+
bounds=self.bounds, fill=0, use_cuda=self.use_cuda)
105+
106+
107+
class RasterizeComplexPolygons:
108+
params = ([100, 300, 1000, 3000], ["numpy", "cupy"])
109+
param_names = ("nx", "backend")
110+
111+
def setup(self, nx, backend):
112+
if backend == "cupy" and not _has_cupy:
113+
raise NotImplementedError("CuPy not available")
114+
ny = nx // 2
115+
self.geoms, self.vals = _make_complex_polygons(n_polys=20,
116+
n_vertices=64)
117+
self.pairs = list(zip(self.geoms, self.vals))
118+
self.bounds = (-180, -90, 180, 90)
119+
self.width = nx
120+
self.height = ny
121+
self.use_cuda = (backend == "cupy")
122+
123+
def time_rasterize_complex_polygons(self, nx, backend):
124+
rasterize(self.pairs, width=self.width, height=self.height,
125+
bounds=self.bounds, fill=0, use_cuda=self.use_cuda)
126+
127+
128+
# -------------------------------------------------------------------------
129+
# Line rasterization benchmarks
130+
# -------------------------------------------------------------------------
131+
132+
class RasterizeLines:
133+
params = ([100, 300, 1000, 3000], ["numpy", "cupy"])
134+
param_names = ("nx", "backend")
135+
136+
def setup(self, nx, backend):
137+
if backend == "cupy" and not _has_cupy:
138+
raise NotImplementedError("CuPy not available")
139+
ny = nx // 2
140+
self.geoms, self.vals = _make_line_network(n_lines=50, n_vertices=10)
141+
self.pairs = list(zip(self.geoms, self.vals))
142+
self.bounds = (-180, -90, 180, 90)
143+
self.width = nx
144+
self.height = ny
145+
self.use_cuda = (backend == "cupy")
146+
147+
def time_rasterize_lines(self, nx, backend):
148+
rasterize(self.pairs, width=self.width, height=self.height,
149+
bounds=self.bounds, fill=0, use_cuda=self.use_cuda)
150+
151+
152+
# -------------------------------------------------------------------------
153+
# Point rasterization benchmarks
154+
# -------------------------------------------------------------------------
155+
156+
class RasterizePoints:
157+
params = ([100, 300, 1000, 3000], ["numpy", "cupy"])
158+
param_names = ("nx", "backend")
159+
160+
def setup(self, nx, backend):
161+
if backend == "cupy" and not _has_cupy:
162+
raise NotImplementedError("CuPy not available")
163+
ny = nx // 2
164+
self.geoms, self.vals = _make_point_cloud(n_points=500)
165+
self.pairs = list(zip(self.geoms, self.vals))
166+
self.bounds = (-180, -90, 180, 90)
167+
self.width = nx
168+
self.height = ny
169+
self.use_cuda = (backend == "cupy")
170+
171+
def time_rasterize_points(self, nx, backend):
172+
rasterize(self.pairs, width=self.width, height=self.height,
173+
bounds=self.bounds, fill=0, use_cuda=self.use_cuda)
174+
175+
176+
# -------------------------------------------------------------------------
177+
# Mixed geometry rasterization
178+
# -------------------------------------------------------------------------
179+
180+
class RasterizeMixed:
181+
params = ([100, 300, 1000, 3000], ["numpy", "cupy"])
182+
param_names = ("nx", "backend")
183+
184+
def setup(self, nx, backend):
185+
if backend == "cupy" and not _has_cupy:
186+
raise NotImplementedError("CuPy not available")
187+
ny = nx // 2
188+
poly_geoms, poly_vals = _make_polygon_grid(nx, ny, n_polys_x=5,
189+
n_polys_y=3)
190+
line_geoms, line_vals = _make_line_network(n_lines=30, n_vertices=8)
191+
pt_geoms, pt_vals = _make_point_cloud(n_points=200)
192+
self.pairs = (list(zip(poly_geoms, poly_vals))
193+
+ list(zip(line_geoms, line_vals))
194+
+ list(zip(pt_geoms, pt_vals)))
195+
self.bounds = (-180, -90, 180, 90)
196+
self.width = nx
197+
self.height = ny
198+
self.use_cuda = (backend == "cupy")
199+
200+
def time_rasterize_mixed(self, nx, backend):
201+
rasterize(self.pairs, width=self.width, height=self.height,
202+
bounds=self.bounds, fill=0, use_cuda=self.use_cuda)
203+
204+
205+
# -------------------------------------------------------------------------
206+
# GeoDataFrame input (measures parsing overhead)
207+
# -------------------------------------------------------------------------
208+
209+
class RasterizeGeoDataFrame:
210+
params = ([100, 300, 1000, 3000],)
211+
param_names = ("nx",)
212+
213+
def setup(self, nx):
214+
if not _has_geopandas:
215+
raise NotImplementedError("geopandas not available")
216+
ny = nx // 2
217+
geoms, vals = _make_polygon_grid(nx, ny)
218+
self.gdf = gpd.GeoDataFrame({'value': vals}, geometry=geoms)
219+
self.bounds = (-180, -90, 180, 90)
220+
self.width = nx
221+
self.height = ny
222+
223+
def time_rasterize_geodataframe(self, nx):
224+
rasterize(self.gdf, width=self.width, height=self.height,
225+
bounds=self.bounds, column='value', fill=0)
226+
227+
228+
# -------------------------------------------------------------------------
229+
# Scaling: many polygons
230+
# -------------------------------------------------------------------------
231+
232+
class RasterizeScaling:
233+
"""Measure how rasterize scales with polygon count at fixed resolution."""
234+
params = ([10, 50, 200, 1000], ["numpy", "cupy"])
235+
param_names = ("n_polys", "backend")
236+
237+
def setup(self, n_polys, backend):
238+
if backend == "cupy" and not _has_cupy:
239+
raise NotImplementedError("CuPy not available")
240+
rng = np.random.default_rng(82714)
241+
geoms = []
242+
vals = []
243+
for i in range(n_polys):
244+
cx = rng.uniform(-150, 150)
245+
cy = rng.uniform(-70, 70)
246+
r = rng.uniform(2, 10)
247+
geoms.append(Point(cx, cy).buffer(r, resolution=16))
248+
vals.append(float(i + 1))
249+
self.pairs = list(zip(geoms, vals))
250+
self.bounds = (-180, -90, 180, 90)
251+
self.use_cuda = (backend == "cupy")
252+
253+
def time_rasterize_scaling(self, n_polys, backend):
254+
rasterize(self.pairs, width=1000, height=500,
255+
bounds=self.bounds, fill=0, use_cuda=self.use_cuda)

0 commit comments

Comments
 (0)