Skip to content

Commit e6e18fb

Browse files
authored
Merge pull request #253 from joshuacortez/feat/fast-square-gridding
Add `FastSquareGridGenerator`
2 parents 54553e9 + 59664a3 commit e6e18fb

File tree

8 files changed

+1935
-419
lines changed

8 files changed

+1935
-419
lines changed

geowrangler/_modidx.py

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -109,8 +109,12 @@
109109
'geowrangler/distance_zonal_stats.py'),
110110
'geowrangler.distance_zonal_stats.create_distance_zonal_stats': ( 'distance_zonal_stats.html#create_distance_zonal_stats',
111111
'geowrangler/distance_zonal_stats.py')},
112-
'geowrangler.gridding_utils.polygon_fill': { 'geowrangler.gridding_utils.polygon_fill.interpolate_x': ( 'polygon_fill.html#interpolate_x',
112+
'geowrangler.gridding_utils.polygon_fill': { 'geowrangler.gridding_utils.polygon_fill.fast_polygon_fill': ( 'polygon_fill.html#fast_polygon_fill',
113+
'geowrangler/gridding_utils/polygon_fill.py'),
114+
'geowrangler.gridding_utils.polygon_fill.interpolate_x': ( 'polygon_fill.html#interpolate_x',
113115
'geowrangler/gridding_utils/polygon_fill.py'),
116+
'geowrangler.gridding_utils.polygon_fill.polygons_to_vertices': ( 'polygon_fill.html#polygons_to_vertices',
117+
'geowrangler/gridding_utils/polygon_fill.py'),
114118
'geowrangler.gridding_utils.polygon_fill.scanline_fill': ( 'polygon_fill.html#scanline_fill',
115119
'geowrangler/gridding_utils/polygon_fill.py'),
116120
'geowrangler.gridding_utils.polygon_fill.voxel_traversal_2d': ( 'polygon_fill.html#voxel_traversal_2d',
@@ -140,8 +144,6 @@
140144
'geowrangler/grids.py'),
141145
'geowrangler.grids.FastBingTileGridGenerator._lng_to_xtile': ( 'grids.html#fastbingtilegridgenerator._lng_to_xtile',
142146
'geowrangler/grids.py'),
143-
'geowrangler.grids.FastBingTileGridGenerator._polygons_to_vertices': ( 'grids.html#fastbingtilegridgenerator._polygons_to_vertices',
144-
'geowrangler/grids.py'),
145147
'geowrangler.grids.FastBingTileGridGenerator._xtile_to_lng': ( 'grids.html#fastbingtilegridgenerator._xtile_to_lng',
146148
'geowrangler/grids.py'),
147149
'geowrangler.grids.FastBingTileGridGenerator._xy_to_bbox': ( 'grids.html#fastbingtilegridgenerator._xy_to_bbox',
@@ -152,6 +154,26 @@
152154
'geowrangler/grids.py'),
153155
'geowrangler.grids.FastBingTileGridGenerator.generate_grid': ( 'grids.html#fastbingtilegridgenerator.generate_grid',
154156
'geowrangler/grids.py'),
157+
'geowrangler.grids.FastSquareGridGenerator': ( 'grids.html#fastsquaregridgenerator',
158+
'geowrangler/grids.py'),
159+
'geowrangler.grids.FastSquareGridGenerator.__init__': ( 'grids.html#fastsquaregridgenerator.__init__',
160+
'geowrangler/grids.py'),
161+
'geowrangler.grids.FastSquareGridGenerator._easting_to_xtile': ( 'grids.html#fastsquaregridgenerator._easting_to_xtile',
162+
'geowrangler/grids.py'),
163+
'geowrangler.grids.FastSquareGridGenerator._northing_to_ytile': ( 'grids.html#fastsquaregridgenerator._northing_to_ytile',
164+
'geowrangler/grids.py'),
165+
'geowrangler.grids.FastSquareGridGenerator._northingeasting_to_xy': ( 'grids.html#fastsquaregridgenerator._northingeasting_to_xy',
166+
'geowrangler/grids.py'),
167+
'geowrangler.grids.FastSquareGridGenerator._remove_out_of_bounds_polygons': ( 'grids.html#fastsquaregridgenerator._remove_out_of_bounds_polygons',
168+
'geowrangler/grids.py'),
169+
'geowrangler.grids.FastSquareGridGenerator._xtile_to_easting': ( 'grids.html#fastsquaregridgenerator._xtile_to_easting',
170+
'geowrangler/grids.py'),
171+
'geowrangler.grids.FastSquareGridGenerator._xy_to_bbox': ( 'grids.html#fastsquaregridgenerator._xy_to_bbox',
172+
'geowrangler/grids.py'),
173+
'geowrangler.grids.FastSquareGridGenerator._ytile_to_northing': ( 'grids.html#fastsquaregridgenerator._ytile_to_northing',
174+
'geowrangler/grids.py'),
175+
'geowrangler.grids.FastSquareGridGenerator.generate_grid': ( 'grids.html#fastsquaregridgenerator.generate_grid',
176+
'geowrangler/grids.py'),
155177
'geowrangler.grids.H3GridGenerator': ('grids.html#h3gridgenerator', 'geowrangler/grids.py'),
156178
'geowrangler.grids.H3GridGenerator.__init__': ( 'grids.html#h3gridgenerator.__init__',
157179
'geowrangler/grids.py'),
@@ -176,7 +198,10 @@
176198
'geowrangler.grids.get_intersect_partition': ( 'grids.html#get_intersect_partition',
177199
'geowrangler/grids.py'),
178200
'geowrangler.grids.get_parallel_intersects': ( 'grids.html#get_parallel_intersects',
179-
'geowrangler/grids.py')},
201+
'geowrangler/grids.py'),
202+
'geowrangler.grids.is_aoi_within_boundary': ( 'grids.html#is_aoi_within_boundary',
203+
'geowrangler/grids.py'),
204+
'geowrangler.grids.setup_boundary': ('grids.html#setup_boundary', 'geowrangler/grids.py')},
180205
'geowrangler.raster_process': { 'geowrangler.raster_process.query_window_by_gdf': ( 'raster_process.html#query_window_by_gdf',
181206
'geowrangler/raster_process.py'),
182207
'geowrangler.raster_process.query_window_by_polygon': ( 'raster_process.html#query_window_by_polygon',

geowrangler/gridding_utils/polygon_fill.py

Lines changed: 103 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
# AUTOGENERATED! DO NOT EDIT! File to edit: ../../notebooks/15_polygon_fill.ipynb.
22

33
# %% auto 0
4-
__all__ = ['voxel_traversal_2d', 'scanline_fill', 'voxel_traversal_scanline_fill']
4+
__all__ = ['voxel_traversal_2d', 'scanline_fill', 'voxel_traversal_scanline_fill', 'polygons_to_vertices', 'fast_polygon_fill']
55

66
# %% ../../notebooks/15_polygon_fill.ipynb 5
77
from typing import List, Tuple, Set, Optional, Dict, Union
88

99
import numpy as np
1010
import pandas as pd
11+
import geopandas as gpd
1112
import polars as pl
1213

1314
# %% ../../notebooks/15_polygon_fill.ipynb 11
@@ -18,7 +19,8 @@ def voxel_traversal_2d(
1819
) -> List[Tuple[int, int]]:
1920
"""
2021
Returns all pixels between two points as inspired by Amanatides & Woo's “A Fast Voxel Traversal Algorithm For Ray Tracing”
21-
Implementation adapted from https://www.redblobgames.com/grids/line-drawing/
22+
23+
Implementation adapted from https://www.redblobgames.com/grids/line-drawing/ in the supercover lines section
2224
"""
2325

2426
# Setup initial conditions
@@ -190,8 +192,9 @@ def voxel_traversal_scanline_fill(
190192
debug: bool = False, # if true, prints diagnostic info for both voxel traversal and scanline fill algorithms
191193
) -> Set[Tuple[int, int]]:
192194
"""
193-
Returns pixels that intersect a polygon
194-
This uses voxel traversal to fill the boundary, and scanline fill for the interior. All coordinates are assumed to be nonnegative integers
195+
Returns pixels that intersect a polygon.
196+
197+
This uses voxel traversal to fill the boundary, and scanline fill for the interior. All coordinates are assumed to be integers.
195198
"""
196199

197200
vertices = list(zip(vertices_df[x_col].to_list(), vertices_df[y_col].to_list()))
@@ -205,3 +208,99 @@ def voxel_traversal_scanline_fill(
205208
polygon_pixels.update(scanline_fill(vertices, debug))
206209

207210
return polygon_pixels
211+
212+
# %% ../../notebooks/15_polygon_fill.ipynb 27
213+
SUBPOLYGON_ID_COL = "__subpolygon_id__"
214+
PIXEL_DTYPE = pl.Int32
215+
216+
# %% ../../notebooks/15_polygon_fill.ipynb 28
217+
def polygons_to_vertices(
218+
polys_gdf: gpd.GeoDataFrame,
219+
unique_id_col: Optional[
220+
str
221+
] = None, # the ids under this column will be preserved in the output tiles
222+
) -> pl.DataFrame:
223+
224+
if unique_id_col is not None:
225+
duplicates_bool = polys_gdf[unique_id_col].duplicated()
226+
if duplicates_bool.any():
227+
raise ValueError(
228+
f"""{unique_id_col} is not unique!
229+
Found {duplicates_bool.sum():,} duplicates"""
230+
)
231+
polys_gdf = polys_gdf.set_index(unique_id_col)
232+
else:
233+
# reset index if it is not unique
234+
if polys_gdf.index.nunique() != len(polys_gdf.index):
235+
polys_gdf = polys_gdf.reset_index(drop=True)
236+
unique_id_col = polys_gdf.index.name
237+
238+
polys_gdf = polys_gdf.explode(index_parts=True)
239+
240+
is_poly_bool = polys_gdf.type == "Polygon"
241+
if not is_poly_bool.all():
242+
raise ValueError(
243+
f"""
244+
All geometries should be polygons or multipolygons but found
245+
{is_poly_bool.sum():,} after exploding the GeoDataFrame"""
246+
)
247+
248+
polys_gdf.index.names = [unique_id_col, SUBPOLYGON_ID_COL]
249+
vertices_df = polys_gdf.get_coordinates().reset_index()
250+
vertices_df = pl.from_pandas(vertices_df)
251+
252+
return vertices_df
253+
254+
# %% ../../notebooks/15_polygon_fill.ipynb 32
255+
def fast_polygon_fill(
256+
vertices_df: pl.DataFrame, # integer vertices of all polygons in the AOI
257+
unique_id_col: Optional[
258+
str
259+
] = None, # the ids under this column will be preserved in the output tiles
260+
) -> pl.DataFrame:
261+
262+
if unique_id_col is not None:
263+
id_cols = [SUBPOLYGON_ID_COL, unique_id_col]
264+
has_unique_id_col = True
265+
else:
266+
complement_cols = ["x", "y", SUBPOLYGON_ID_COL]
267+
unique_id_col = list(set(vertices_df.columns) - set(complement_cols))
268+
assert len(unique_id_col) == 1
269+
unique_id_col = unique_id_col[0]
270+
id_cols = [SUBPOLYGON_ID_COL, unique_id_col]
271+
has_unique_id_col = False
272+
273+
for col in id_cols:
274+
assert col in vertices_df, f"{col} should be column in vertices_df"
275+
276+
polygon_ids = vertices_df.select(id_cols).unique(maintain_order=True).rows()
277+
278+
tiles_in_geom = set()
279+
for polygon_id in polygon_ids:
280+
subpolygon_id, unique_id = polygon_id
281+
filter_expr = (pl.col(SUBPOLYGON_ID_COL) == subpolygon_id) & (
282+
pl.col(unique_id_col) == unique_id
283+
)
284+
poly_vertices = vertices_df.filter(filter_expr)
285+
286+
poly_vertices = poly_vertices.unique(maintain_order=True)
287+
_tiles_in_geom = voxel_traversal_scanline_fill(
288+
poly_vertices, x_col="x", y_col="y"
289+
)
290+
291+
if has_unique_id_col:
292+
_tiles_in_geom = [(x, y, unique_id) for (x, y) in _tiles_in_geom]
293+
294+
tiles_in_geom.update(_tiles_in_geom)
295+
296+
schema = {"x": PIXEL_DTYPE, "y": PIXEL_DTYPE}
297+
if has_unique_id_col:
298+
schema[unique_id_col] = vertices_df[unique_id_col].dtype
299+
300+
tiles_in_geom = pl.from_records(
301+
data=list(tiles_in_geom),
302+
orient="row",
303+
schema=schema,
304+
)
305+
306+
return tiles_in_geom

0 commit comments

Comments
 (0)