1
1
# AUTOGENERATED! DO NOT EDIT! File to edit: ../../notebooks/15_polygon_fill.ipynb.
2
2
3
3
# %% 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' ]
5
5
6
6
# %% ../../notebooks/15_polygon_fill.ipynb 5
7
7
from typing import List , Tuple , Set , Optional , Dict , Union
8
8
9
9
import numpy as np
10
10
import pandas as pd
11
+ import geopandas as gpd
11
12
import polars as pl
12
13
13
14
# %% ../../notebooks/15_polygon_fill.ipynb 11
@@ -18,7 +19,8 @@ def voxel_traversal_2d(
18
19
) -> List [Tuple [int , int ]]:
19
20
"""
20
21
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
22
24
"""
23
25
24
26
# Setup initial conditions
@@ -190,8 +192,9 @@ def voxel_traversal_scanline_fill(
190
192
debug : bool = False , # if true, prints diagnostic info for both voxel traversal and scanline fill algorithms
191
193
) -> Set [Tuple [int , int ]]:
192
194
"""
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.
195
198
"""
196
199
197
200
vertices = list (zip (vertices_df [x_col ].to_list (), vertices_df [y_col ].to_list ()))
@@ -205,3 +208,99 @@ def voxel_traversal_scanline_fill(
205
208
polygon_pixels .update (scanline_fill (vertices , debug ))
206
209
207
210
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