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
77from typing import List , Tuple , Set , Optional , Dict , Union
88
99import numpy as np
1010import pandas as pd
11+ import geopandas as gpd
1112import 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