@@ -457,3 +457,115 @@ def test_polygonize_dask_cupy_matches_numpy(chunks):
457457 assert val in areas_dcp , f"Value { val } missing from dask+cupy result"
458458 assert_allclose (areas_dcp [val ], areas_np [val ],
459459 err_msg = f"Area mismatch for value { val } " )
460+
461+
462+ # --- Performance-related regression tests (#1008) ---
463+
464+ def test_polygonize_1008_large_boundary_buffer_growth ():
465+ """Single-pass _follow buffer growth: polygon with > 64 boundary points.
466+
467+ A thin snake-like region forces the boundary tracer to produce many
468+ vertices, exercising the dynamic buffer doubling in _follow.
469+ """
470+ # Horizontal snake: 1-pixel-wide path zigzagging across a 60-column raster.
471+ data = np .zeros ((6 , 60 ), dtype = np .int32 )
472+ data [0 , :] = 1 # row 0: left to right
473+ data [1 , 59 ] = 1 # turn down
474+ data [2 , :] = 1 # row 2: right to left (fills whole row)
475+ data [3 , 0 ] = 1 # turn down
476+ data [4 , :] = 1 # row 4: left to right
477+
478+ raster = xr .DataArray (data )
479+ values , polygons = polygonize (raster , connectivity = 4 )
480+
481+ # The value-1 polygon should have many boundary points (> 64).
482+ val1_idx = [i for i , v in enumerate (values ) if v == 1 ]
483+ assert len (val1_idx ) >= 1
484+ for idx in val1_idx :
485+ for ring in polygons [idx ]:
486+ assert ring .shape [1 ] == 2
487+ assert np .array_equal (ring [0 ], ring [- 1 ])
488+
489+ # Total area must equal raster size.
490+ total_area = sum (
491+ assert_polygon_valid_and_get_area (p ) for p in polygons )
492+ assert_allclose (total_area , data .size )
493+
494+
495+ def test_polygonize_1008_jit_merge_helpers ():
496+ """JIT-compiled _simplify_ring, _signed_ring_area, _point_in_ring."""
497+ from ..polygonize import _point_in_ring , _signed_ring_area , _simplify_ring
498+
499+ # Unit square: CCW exterior.
500+ square = np .array ([
501+ [0 , 0 ], [1 , 0 ], [1 , 1 ], [0 , 1 ], [0 , 0 ]], dtype = np .float64 )
502+
503+ assert_allclose (_signed_ring_area (square ), 1.0 )
504+
505+ # Point inside.
506+ assert _point_in_ring (0.5 , 0.5 , square ) is True
507+ # Point outside.
508+ assert _point_in_ring (2.0 , 2.0 , square ) is False
509+
510+ # Square with collinear midpoints on each edge.
511+ with_collinear = np .array ([
512+ [0 , 0 ], [0.5 , 0 ], [1 , 0 ], [1 , 0.5 ], [1 , 1 ],
513+ [0.5 , 1 ], [0 , 1 ], [0 , 0.5 ], [0 , 0 ]], dtype = np .float64 )
514+ simplified = _simplify_ring (with_collinear )
515+ # Should remove the midpoints, leaving 4 corners + closing point.
516+ assert simplified .shape == (5 , 2 )
517+ assert_allclose (_signed_ring_area (simplified ), 1.0 )
518+
519+ # Ring with no collinear points should be returned unchanged.
520+ triangle = np .array ([
521+ [0 , 0 ], [2 , 0 ], [1 , 2 ], [0 , 0 ]], dtype = np .float64 )
522+ assert _simplify_ring (triangle ) is triangle
523+
524+
525+ @dask_array_available
526+ def test_polygonize_1008_dask_merge_many_boundary_polygons ():
527+ """Dask merge with many boundary-crossing polygons of the same value.
528+
529+ Checkerboard pattern in small chunks forces many boundary polygons
530+ through the merge path, exercising the JIT-compiled helpers.
531+ """
532+ # 8x8 checkerboard, chunks of 4x4.
533+ data = np .zeros ((8 , 8 ), dtype = np .int32 )
534+ data [::2 , ::2 ] = 1
535+ data [1 ::2 , 1 ::2 ] = 1
536+
537+ raster_np = xr .DataArray (data )
538+ vals_np , polys_np = polygonize (raster_np , connectivity = 4 )
539+ areas_np = _area_by_value (vals_np , polys_np )
540+
541+ raster_da = xr .DataArray (da .from_array (data , chunks = (4 , 4 )))
542+ vals_da , polys_da = polygonize (raster_da , connectivity = 4 )
543+ areas_da = _area_by_value (vals_da , polys_da )
544+
545+ for val in areas_np :
546+ assert val in areas_da
547+ assert_allclose (areas_da [val ], areas_np [val ],
548+ err_msg = f"Area mismatch for value { val } " )
549+
550+
551+ @pytest .mark .skipif (gpd is None , reason = "geopandas not installed" )
552+ def test_polygonize_1008_geopandas_batch_with_holes ():
553+ """Batch shapely construction: mix of hole-free and holed polygons."""
554+ # Outer ring of 0s with inner block of 1s containing a 2 (hole in 1).
555+ data = np .zeros ((6 , 6 ), dtype = np .int32 )
556+ data [1 :5 , 1 :5 ] = 1
557+ data [2 :4 , 2 :4 ] = 2
558+
559+ raster = xr .DataArray (data )
560+ df = polygonize (raster , return_type = "geopandas" , connectivity = 4 )
561+
562+ assert isinstance (df , gpd .GeoDataFrame )
563+ assert len (df ) == 3 # values 0, 1, 2
564+
565+ # Value 1 polygon should have a hole (the 2-region).
566+ row_1 = df [df .DN == 1 ].iloc [0 ]
567+ geom = row_1 .geometry
568+ assert len (list (geom .interiors )) == 1
569+
570+ # Total area should equal raster size.
571+ assert_allclose (df .geometry .area .sum (), 36.0 )
0 commit comments