Skip to content

fishnet

create_fishnet(*, bounds, res)

Generate a fishnet of polygons from bounds.

The function generates a grid of polygons within the specified bounds, where each cell has dimensions defined by res. If the resolution does not perfectly divide the bounds' dimensions (i.e., if res is not a factor of (xmax - xmin) or (ymax - ymin)), the grid is still generated such that it fully covers the bounds. This can result in cells that extend beyond the specified bounds.

Parameters:

Name Type Description Default
bounds tuple[float, float, float, float]

(xmin, ymin, xmax, ymax)

required
res tuple[float, float] | float

Resolution as (width, height) or a single value for square cells.

required

Returns:

Type Description
GeometryArray

Shapely Polygons.

Source code in src/rastr/gis/fishnet.py
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
def create_fishnet(
    *, bounds: tuple[float, float, float, float], res: tuple[float, float] | float
) -> GeometryArray:
    """Generate a fishnet of polygons from bounds.

    The function generates a grid of polygons within the specified bounds, where each
    cell has dimensions defined by `res`. If the resolution does not perfectly divide
    the bounds' dimensions (i.e., if `res` is not a factor of (xmax - xmin) or
    (ymax - ymin)), the grid is still generated such that it fully covers the bounds.
    This can result in cells that extend beyond the specified bounds.

    Args:
        bounds: (xmin, ymin, xmax, ymax)
        res: Resolution as `(width, height)` or a single value for square cells.

    Returns:
        Shapely Polygons.
    """
    import geopandas as gpd

    res = _ensure_pair(res)
    cell_width, cell_height = res

    # Use the shared helper function to create the point grid
    xx, yy = create_point_grid(bounds=bounds, cell_size=res)

    # Create rectangles centered on each grid point
    polygons = box(
        xx.ravel() - cell_width / 2,
        yy.ravel() - cell_height / 2,
        xx.ravel() + cell_width / 2,
        yy.ravel() + cell_height / 2,
    )

    # GeoSeries.array is typed as ExtensionArray in geopandas stubs, but at runtime
    # this is a GeometryArray for polygon geometries.
    return cast("GeometryArray", gpd.GeoSeries(polygons).array)

create_point_grid(*, bounds, cell_size)

Create a regular grid of point coordinates for raster centers.

This function replicates the original grid generation logic that uses np.arange to ensure compatibility with existing code.

Parameters:

Name Type Description Default
bounds tuple[float, float, float, float]

(xmin, ymin, xmax, ymax) bounding box.

required
cell_size tuple[float, float] | float

Size of each grid cell as (width, height) or a single value for square cells.

required

Returns:

Type Description
tuple[NDArray, NDArray]

Tuple of (x_coords, y_coords) meshgrids for raster cell centers.

Source code in src/rastr/gis/fishnet.py
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
def create_point_grid(
    *, bounds: tuple[float, float, float, float], cell_size: tuple[float, float] | float
) -> tuple[NDArray, NDArray]:
    """Create a regular grid of point coordinates for raster centers.

    This function replicates the original grid generation logic that uses
    np.arange to ensure compatibility with existing code.

    Args:
        bounds: (xmin, ymin, xmax, ymax) bounding box.
        cell_size: Size of each grid cell as (width, height) or a single value for
            square cells.

    Returns:
        Tuple of (x_coords, y_coords) meshgrids for raster cell centers.
    """
    x_width, y_height = _ensure_pair(cell_size)

    xmin, ymin, xmax, ymax = bounds

    # Use the original logic with np.arange for exact compatibility
    x_coords = np.arange(xmin + x_width / 2, xmax + x_width / 2, x_width)
    y_coords = np.arange(ymax - y_height / 2, ymin - y_height / 2, -y_height)

    x_points, y_points = np.meshgrid(x_coords, y_coords)  # type: ignore[reportAssignmentType]
    return x_points, y_points

get_point_grid_shape(*, bounds, cell_size)

Calculate the shape of the point grid based on bounds and cell size.

Parameters:

Name Type Description Default
bounds tuple[float, float, float, float] | ArrayLike

(xmin, ymin, xmax, ymax) bounding box.

required
cell_size tuple[float, float] | float

Size of each grid cell as (width, height) or a single value for square cells.

required
Source code in src/rastr/gis/fishnet.py
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
def get_point_grid_shape(
    *,
    bounds: tuple[float, float, float, float] | ArrayLike,
    cell_size: tuple[float, float] | float,
) -> tuple[int, int]:
    """Calculate the shape of the point grid based on bounds and cell size.

    Args:
        bounds: (xmin, ymin, xmax, ymax) bounding box.
        cell_size: Size of each grid cell as (width, height) or a single value for
            square cells.
    """
    x_width, y_height = _ensure_pair(cell_size)

    xmin, ymin, xmax, ymax = np.asarray(bounds)
    ncols_exact = (xmax - xmin) / x_width
    nrows_exact = (ymax - ymin) / y_height

    # Use round for values very close to integers to avoid floating-point
    # sensitivity while maintaining ceil behavior for truly fractional values
    if np.isclose(ncols_exact, np.round(ncols_exact)):
        ncols = int(np.round(ncols_exact))
    else:
        ncols = int(np.ceil(ncols_exact))

    if np.isclose(nrows_exact, np.round(nrows_exact)):
        nrows = int(np.round(nrows_exact))
    else:
        nrows = int(np.ceil(nrows_exact))

    return nrows, ncols