Source code for stactools.core.utils.raster_footprint
"""Generate convex hulls of valid raster data for use in STAC Item
geometries."""
import logging
import warnings
from enum import Enum, auto
from typing import Any, Dict, Iterator, List, Optional, Tuple, Type, TypeVar, Union
import numpy as np
import numpy.typing as npt
import rasterio
import rasterio.features
from pystac import Item
from rasterio import Affine, DatasetReader
from rasterio.crs import CRS
from shapely.geometry import mapping, shape
from shapely.geometry.multipolygon import MultiPolygon
from shapely.geometry.polygon import Polygon, orient
from shapely.ops import unary_union
from stactools.core.geometry import mutual_intersection
from ..projection import reproject_shape
logger = logging.getLogger(__name__)
# Roughly 1 centimeter in geodetic coordinates
DEFAULT_PRECISION = 7
T = TypeVar("T", bound="RasterFootprint")
[docs]
class FootprintMergeStrategy(Enum):
"""Strategy for handling the aggregation of differing asset footprints."""
FIRST = auto()
"""Use the footprint of the first matching asset."""
UNION = auto()
"""Union the geometries of all matching assets."""
INTERSECTION = auto()
"""Use the mutual intersection of all matching asset footprints."""
[docs]
def densify_by_factor(
point_list: List[Tuple[float, float]], factor: int
) -> List[Tuple[float, float]]:
"""Densifies the number of points in a list of points by a ``factor``. For
example, a list of 5 points and a factor of 2 will result in 10 points (one
new point between each original adjacent points).
Derived from code found at
https://stackoverflow.com/questions/64995977/generating-equidistance-points-along-the-boundary-of-a-polygon-but-cw-ccw
Args:
point_list (List[Tuple[float, float]]): The list of points to be
densified.
factor (int): The factor by which to densify the points. A larger
densification factor should be used when reprojection results in
greater curvature from the original geometry.
Returns:
List[Tuple[float, float]]: A list of the densified points.
""" # noqa: E501
points: Any = np.asarray(point_list)
densified_number = len(points) * factor
existing_indices = np.arange(0, densified_number, factor)
interp_indices = np.arange(existing_indices[-1] + 1)
interp_x = np.interp(interp_indices, existing_indices, points[:, 0])
interp_y = np.interp(interp_indices, existing_indices, points[:, 1])
return [(x, y) for x, y in zip(interp_x, interp_y)]
[docs]
def densify_by_distance(
point_list: List[Tuple[float, float]], distance: float
) -> List[Tuple[float, float]]:
"""Densifies the number of points in a list of points by inserting new
points at intervals between each set of successive points. For example, if
two successive points in the list are separated by 10 units and a
``distance`` of 2 is provided, 4 new points will be added between the two
original points (one new point every 2 units of ``distance``).
Derived from code found at
https://stackoverflow.com/questions/64995977/generating-equidistance-points-along-the-boundary-of-a-polygon-but-cw-ccw
Args:
point_list (List[Tuple[float, float]]): The list of points to be
densified.
distance (float): The interval at which to insert additional points. A
smaller densification distance should be used when reprojection
results in greater curvature from the original geometry.
Returns:
List[Tuple[float, float]]: A list of the densified points.
"""
points: Any = np.asarray(point_list)
dxdy = points[1:, :] - points[:-1, :]
segment_lengths = np.sqrt(np.sum(np.square(dxdy), axis=1))
steps = segment_lengths / distance
coordinate_steps = dxdy / steps.reshape(-1, 1)
densified_points = np.empty((len(point_list) - 1,), dtype="O")
for index in range(len(point_list) - 1):
step = np.arange(steps[index])
densified_points[index] = (
np.array((step, step)).T * coordinate_steps[index] + points[index]
)
final_point = points[-1].reshape(1, -1)
densified_array = np.concatenate((*densified_points, final_point), axis=0)
return [(float(row[0]), float(row[1])) for row in densified_array]
[docs]
def reproject_polygon(
polygon: Polygon,
crs: CRS,
precision: Optional[int] = DEFAULT_PRECISION,
dst_crs: CRS = "EPSG:4326",
) -> Polygon:
"""DEPRECATED.
.. deprecated:: 0.5.0
Use :func:`projection.reproject_shape` instead.
Projects a polygon and rounds the projected vertex coordinates to ``precision``.
Duplicate points caused by rounding are removed.
Args:
polygon (Polygon): The polygon to reproject.
crs (CRS): The CRS of the input polygon.
precision (int): The number of decimal places to include in the final
polygon vertex coordinates.
dst_crs (CRS): The CRS of the output polygon. Defaults to EPSG 4326
Returns:
Polygon: Polygon in 'dst_crs'. Default to EPSG 4326
"""
warnings.warn(
"``utils.reproject_polygon`` is deprecated and will be removed in v0.6.0. "
"Use ``projection.reproject_shape`` instead.",
DeprecationWarning,
)
return reproject_shape(
src_crs=crs, dst_crs=dst_crs, geom=polygon, precision=precision
)
[docs]
class RasterFootprint:
"""An object for creating a convex hull polygon around all areas within an
raster that have data values (i.e., they do not have the nodata value).
This convex hull is termed the "footprint" of the raster data and is
returned by the :meth:`footprint` method as a polygon in a GeoJSON
dictionary for use as the geometry attribute of a STAC Item.
Two important operations during this calculation are the densification of
the footprint in the native CRS and simplification of the footprint after
reprojection. If the initial low-vertex polygon in the native
CRS is not densified, this can result in a reprojected polygon that does not
accurately represent the data footprint. For example, a MODIS asset
represented by a rectangular 5 point Polygon in a sinusoidal projection will
reproject to a parallelogram in EPSG 4326, when it would be more accurately
represented by a polygon with two parallel sides and two curved sides. The
difference between these representations is greater the further away from
the meridian and equator the asset is located.
After reprojection, the footprint may have more points than
desired. This can be simplified to a polygon with fewer points that maintain
a maximum distance to the original geometry.
Args:
data_array (numpy.NDArray[Any]): The raster data used for the
footprint calculation.
crs (CRS): Coordinate reference system of the raster data.
dst_crs (CRS): Coordinate reference system of the footprint data. Defaults
to EPSG 4236.
transform (Affine): Matrix defining the transformation from pixel to CRS
coordinates.
precision (int): The number of decimal places to include in the
final footprint coordinates.
densification_factor (Optional[int]): The factor by which to
increase point density within the footprint polygon before
projection. A factor of 2 would double the density of
points (placing one new point between each existing pair of points),
a factor of 3 would place two points between each point, etc. Higher
densities produce higher fidelity footprints in areas of high
projection distortion. Mutually exclusive with
``densification_distance``.
densification_distance (Optional[float]): The distance by which to
increase point density within the footprint polygon before
projection. If the distance is set to 2 and the segment
length between two polygon vertices is 10, 4 new vertices would be
created along the segment. Higher densities produce higher
fidelity footprints in areas of high projection distortion.
Mutually exclusive with ``densification_factor``.
simplify_tolerance (Optional[float]): Distance, in degrees, within
which all locations on the simplified polygon will be to the original
polygon.
no_data (Optional[Union[int, float]]): The nodata value in
``data_array``. If set to None, this will return a footprint
including nodata values.
"""
crs: CRS
"""Coordinate reference system of the raster data."""
dst_crs: CRS = "EPSG:4326"
"""Coordinate reference system of the footprint."""
data_array: npt.NDArray[Any]
"""2D or 3D array of raster data."""
densification_distance: Optional[float]
"""Optional distance for densifying polygon vertices before reprojection."""
densification_factor: Optional[int]
"""Optional factor for densifying polygon vertices before reprojection."""
no_data: Optional[Union[int, float]]
"""Optional value defining pixels to exclude from the footprint."""
precision: int
"""Number of decimal places in the final footprint coordinates."""
simplify_tolerance: Optional[float]
"""Optional maximum allowable error when simplifying the reprojected
polygon."""
transform: Affine
"""Transformation matrix from pixel to CRS coordinates."""
def __init__(
self,
data_array: npt.NDArray[Any],
crs: CRS,
transform: Affine,
*,
dst_crs: CRS = "EPSG:4326",
precision: int = DEFAULT_PRECISION,
densification_factor: Optional[int] = None,
densification_distance: Optional[float] = None,
simplify_tolerance: Optional[float] = None,
no_data: Optional[Union[int, float]] = None,
) -> None:
if data_array.ndim == 2:
data_array = data_array[np.newaxis, :]
self.data_array = data_array
self.crs = crs
self.dst_crs = dst_crs
self.transform = transform
self.precision = precision
if densification_factor is not None and densification_distance is not None:
raise ValueError(
"Only one of 'densification_factor' or 'densification_distance' "
"can be specified."
)
self.densification_factor = densification_factor
self.densification_distance = densification_distance
self.simplify_tolerance = simplify_tolerance
self.no_data = no_data
[docs]
def footprint(self) -> Optional[Dict[str, Any]]:
"""Produces the footprint surrounding data (not nodata) pixels in the
source image. If the footprint is unable to be computed, None is
returned.
Returns:
Optional[Dict[str, Any]]: A GeoJSON dictionary containing the
footprint polygon.
"""
mask = self.data_mask()
polygon = self.data_extent(mask)
if polygon is None:
return None
polygon = self.densify_polygon(polygon)
polygon = self.reproject_polygon(polygon)
polygon = self.simplify_polygon(polygon)
return mapping(polygon) # type: ignore
[docs]
def data_mask(self) -> npt.NDArray[np.uint8]:
"""Produces a mask of valid data in the source image. Nodata pixels
values are set to 0, data pixels are set to 1.
Returns:
numpy.NDArray[numpy.uint8]: A 2D array containing 0s and 1s for
nodata/data pixels.
"""
assert self.data_array.ndim == 3
shape = self.data_array.shape
if self.no_data is not None:
mask: npt.NDArray[np.uint8] = np.full(shape, 0, dtype=np.uint8)
if np.isnan(self.no_data):
mask[~np.isnan(self.data_array)] = 1
else:
mask[self.data_array != self.no_data] = 1
mask = np.sum(mask, axis=0, dtype=np.uint8) # type: ignore
mask[mask > 0] = 1
else:
mask = np.full(shape, 1, dtype=np.uint8)
return mask
[docs]
def data_extent(self, mask: npt.NDArray[np.uint8]) -> Optional[Polygon]:
"""Produces the data footprint in the native CRS.
Args:
mask (numpy.NDArray[numpy.uint8]): A 2D array containing 0s and 1s for
nodata/data pixels.
Returns:
Optional[Polygon]: A native CRS polygon of the convex hull of data
pixels.
"""
data_polygons = [
shape(polygon_dict)
for polygon_dict, region_value in rasterio.features.shapes(
mask, transform=self.transform
)
if region_value == 1
]
if not data_polygons:
return None
elif len(data_polygons) == 1:
polygon = data_polygons[0]
else:
polygon = MultiPolygon(data_polygons).convex_hull
return orient(polygon)
[docs]
def densify_polygon(self, polygon: Polygon) -> Polygon:
"""Adds vertices to the footprint polygon in the native CRS using
either ``self.densification_factor`` or
``self.densification_distance``.
Args:
polygon (Polygon): Footprint polygon in the native CRS.
Returns:
Polygon: Densified footprint polygon in the native CRS.
"""
assert not (self.densification_factor and self.densification_distance)
if self.densification_factor is not None:
return Polygon(
densify_by_factor(polygon.exterior.coords, self.densification_factor)
)
elif self.densification_distance is not None:
return Polygon(
densify_by_distance(
polygon.exterior.coords, self.densification_distance
)
)
else:
return polygon
[docs]
def reproject_polygon(self, polygon: Polygon) -> Polygon:
"""Projects a polygon and rounds the projected vertex coordinates to
``self.precision``.
Duplicate points caused by rounding are removed.
Args:
polygon (Polygon): Footprint polygon in the native CRS.
Returns:
Polygon: Footprint polygon in 'dst_crs'.
"""
return reproject_shape(
src_crs=self.crs,
dst_crs=self.dst_crs,
geom=polygon,
precision=self.precision,
)
[docs]
def simplify_polygon(self, polygon: Polygon) -> Polygon:
"""Reduces the number of polygon vertices such that the simplified
polygon shape is no further away than the original polygon vertices
than ``self.simplify_tolerance``.
Args:
polygon (Polygon): Polygon to be simplified.
Returns:
Polygon: Reduced vertex polygon.
"""
if self.simplify_tolerance is not None:
return orient(
polygon.simplify(
tolerance=self.simplify_tolerance, preserve_topology=False
)
)
return polygon
[docs]
@classmethod
def from_href(
cls: Type[T],
href: str,
*,
dst_crs: CRS = "EPSG:4326",
precision: int = DEFAULT_PRECISION,
densification_factor: Optional[int] = None,
densification_distance: Optional[float] = None,
simplify_tolerance: Optional[float] = None,
no_data: Optional[Union[int, float]] = None,
bands: List[int] = [1],
) -> T:
"""Produces a :class:`RasterFootprint` instance from an image href.
The href can point to any file that is openable by rasterio.
Args:
href (str): The href of the image to process.
dst_crs (CRS): Coordinate reference system of the footprint data. Defaults
to EPSG 4236.
precision (int): The number of decimal places to include in the
final footprint coordinates.
densification_factor (Optional[int]): The factor by which to
increase point density within the footprint polygon before
projection. A factor of 2 would double the density
of points (placing one new point between each existing pair of
points), a factor of 3 would place two points between each point,
etc. Higher densities produce higher fidelity footprints in
areas of high projection distortion. Mutually exclusive with
``densification_distance``.
densification_distance (Optional[float]): The distance by which to
increase point density within the footprint polygon before
projection. If the distance is set to 2 and the
segment length between two polygon vertices is 10, 4 new
vertices would be created along the segment. Higher densities
produce higher fidelity footprints in areas of high projection
distortion. Mutually exclusive with ``densification_factor``.
simplify_tolerance (Optional[float]): Distance, in degrees, within
which all locations on the simplified polygon will be to the
original polygon.
no_data (Optional[Union[int, float]]): Explicitly sets the nodata
value if not in source image metadata. If set to None, this will
return a footprint including nodata values.
bands (List[int]): The bands to use to compute the footprint.
Defaults to [1]. If an empty list is provided, the bands will be
ORd together; e.g., for a pixel to be outside of the footprint,
all bands must have nodata in that pixel.
Returns:
RasterFootprint: A :class:`RasterFootprint` instance.
"""
with rasterio.open(href) as source:
return cls.from_rasterio_dataset_reader(
reader=source,
dst_crs=dst_crs,
no_data=no_data,
bands=bands,
precision=precision,
densification_factor=densification_factor,
densification_distance=densification_distance,
simplify_tolerance=simplify_tolerance,
)
[docs]
@classmethod
def from_rasterio_dataset_reader(
cls: Type[T],
reader: DatasetReader,
*,
dst_crs: CRS = "EPSG:4326",
precision: int = DEFAULT_PRECISION,
densification_factor: Optional[int] = None,
densification_distance: Optional[float] = None,
simplify_tolerance: Optional[float] = None,
no_data: Optional[Union[int, float]] = None,
bands: List[int] = [1],
) -> T:
"""Produces a :class:`RasterFootprint` instance from a
:class:`rasterio.io.DatasetReader` object, i.e., an opened dataset
object returned by a :func:`rasterio.open` call.
Args:
reader (DatasetReader): A rasterio dataset reader object for the
image to process.
dst_crs (CRS): Coordinate reference system of the footprint data. Defaults
to EPSG 4236.
precision (int): The number of decimal places to include in the
final footprint coordinates.
densification_factor (Optional[int]): The factor by which to
increase point density within the footprint polygon before
projection. A factor of 2 would double the density
of points (placing one new point between each existing pair of
points), a factor of 3 would place two points between each point,
etc. Higher densities produce higher fidelity footprints in
areas of high projection distortion. Mutually exclusive with
``densification_distance``.
densification_distance (Optional[float]): The distance by which to
increase point density within the footprint polygon before
projection. If the distance is set to 2 and the
segment length between two polygon vertices is 10, 4 new
vertices would be created along the segment. Higher densities
produce higher fidelity footprints in areas of high projection
distortion. Mutually exclusive with ``densification_factor``.
simplify_tolerance (Optional[float]): Distance, in degrees, within
which all locations on the simplified polygon will be to the
original polygon.
no_data (Optional[Union[int, float]]): Explicitly sets the nodata
value if not in source image metadata. If set to None, this will
return a footprint including nodata values.
bands (List[int]): The bands to use to compute the footprint.
Defaults to [1]. If an empty list is provided, the bands will be
ORd together; e.g., for a pixel to be outside of the footprint,
all bands must have nodata in that pixel.
Returns:
RasterFootprint: A :class:`RasterFootprint` instance.
"""
if not reader.indexes:
raise ValueError(
"Raster footprint cannot be computed for an asset with no bands."
)
if len(set(reader.nodatavals)) != 1:
raise ValueError("All raster bands must have the same 'nodata' value.")
if not bands:
bands = reader.indexes
if no_data is None:
no_data = reader.nodata
band_data = []
for index in bands:
band_data.append(reader.read(index))
return cls(
data_array=np.asarray(band_data),
crs=reader.crs,
dst_crs=dst_crs,
transform=reader.transform,
no_data=no_data,
precision=precision,
densification_factor=densification_factor,
densification_distance=densification_distance,
simplify_tolerance=simplify_tolerance,
)
[docs]
@classmethod
def update_geometry_from_asset_footprint(
cls,
item: Item,
*,
asset_names: List[str] = [],
dst_crs: CRS = "EPSG:4326",
precision: int = DEFAULT_PRECISION,
densification_factor: Optional[int] = None,
densification_distance: Optional[float] = None,
simplify_tolerance: Optional[float] = None,
no_data: Optional[Union[int, float]] = None,
bands: List[int] = [1],
skip_errors: bool = True,
footprint_merge_strategy: FootprintMergeStrategy = FootprintMergeStrategy.FIRST,
) -> bool:
"""Accepts an Item and an optional list of asset names within that
Item, and updates the geometry of that Item in-place with the data
footprint derived from the first of the assets that exists in the Item.
The Item bbox is also updated in-place to bound the new footprint
extents.
See :class:`RasterFootprint` for details on the data footprint
calculation.
Args:
item (Item): The PySTAC Item to update.
asset_names (List[str]): The names of the assets for which to attempt to
extract footprints. The first successful footprint will be used. If
the list is empty, all assets will be tried until one is successful.
dst_crs (CRS): Coordinate reference system of the footprint data. Defaults
to EPSG 4236.
precision (int): The number of decimal places to include in the final
footprint coordinates.
densification_factor (Optional[int]): The factor by which to
increase point density within the footprint polygon before
projection. A factor of 2 would double the density
of points (placing one new point between each existing pair of
points), a factor of 3 would place two points between each point,
etc. Higher densities produce higher fidelity footprints in
areas of high projection distortion. Mutually exclusive with
``densification_distance``.
densification_distance (Optional[float]): The distance by which to
increase point density within the footprint polygon before
projection. If the distance is set to 2 and the
segment length between two polygon vertices is 10, 4 new
vertices would be created along the segment. Higher densities
produce higher fidelity footprints in areas of high projection
distortion. Mutually exclusive with ``densification_factor``.
simplify_tolerance (Optional[float]): Distance, in degrees, within which
all locations on the simplified polygon will be to the original
polygon.
no_data (Optional[Union[int, float]]): Explicitly sets the nodata value
if not in source image metadata. If set to None, this will return
a footprint including nodata values.
bands (List[int]): The bands to use to compute the footprint.
Defaults to [1]. If an empty list is provided, the bands will be ORd
together; e.g., for a pixel to be outside of the footprint, all
bands must have nodata in that pixel.
skip_errors (bool): If False, raise an error for a missing href or
footprint calculation failure.
footprint_aggregator (FootprintMergeStrategy): Provides a
means to control how the footprints of assets are aggregated;
see :class:`FootprintMergeStrategy` for details; defaults to using
the `FIRST` strategy
Returns:
bool: True if the Item geometry was successfully updated, False if not.
"""
asset_extent_iterator = cls.data_footprints_for_data_assets(
item,
asset_names=asset_names,
dst_crs=dst_crs,
precision=precision,
densification_factor=densification_factor,
densification_distance=densification_distance,
simplify_tolerance=simplify_tolerance,
no_data=no_data,
bands=bands,
skip_errors=skip_errors,
)
if footprint_merge_strategy == FootprintMergeStrategy.FIRST:
asset_name_extent = next(asset_extent_iterator, None)
if asset_name_extent is None:
return False
_, extent = asset_name_extent
else:
extents = [shape(extent) for _, extent in asset_extent_iterator]
if extents == []:
return False
if footprint_merge_strategy == FootprintMergeStrategy.INTERSECTION:
extent = mutual_intersection(extents)
elif footprint_merge_strategy == FootprintMergeStrategy.UNION:
extent = unary_union(extents)
else:
raise Exception(
f"Unrecognized aggregation strategy: {footprint_merge_strategy}"
)
item.geometry = extent
item.bbox = list(shape(extent).bounds)
return True
[docs]
@classmethod
def data_footprints_for_data_assets(
cls,
item: Item,
*,
asset_names: List[str] = [],
dst_crs: CRS = "EPSG:4326",
precision: int = DEFAULT_PRECISION,
densification_factor: Optional[int] = None,
densification_distance: Optional[float] = None,
simplify_tolerance: Optional[float] = None,
no_data: Optional[Union[int, float]] = None,
bands: List[int] = [1],
skip_errors: bool = True,
) -> Iterator[Tuple[str, Dict[str, Any]]]:
"""Accepts an Item and an optional list of asset names within that
Item, and produces an iterator over the same asset names (if they
exist) and dictionaries representing GeoJSON Polygons of the data
footprints of the assets.
See :class:`RasterFootprint` for details on the data footprint
calculation.
Args:
item (Item): The PySTAC Item to update.
asset_names (List[str]): The names of the assets for which to attempt to
extract footprints. The first successful footprint will be used. If
the list is empty, all assets will be tried until one is successful.
dst_crs (CRS): Coordinate reference system of the footprint data. Defaults
to EPSG 4236.
precision (int): The number of decimal places to include in the final
footprint coordinates.
densification_factor (Optional[int]): The factor by which to
increase point density within the footprint polygon before
projection. A factor of 2 would double the density
of points (placing one new point between each existing pair of
points), a factor of 3 would place two points between each point,
etc. Higher densities produce higher fidelity footprints in
areas of high projection distortion. Mutually exclusive with
``densification_distance``.
densification_distance (Optional[float]): The distance by which to
increase point density within the footprint polygon before
projection. If the distance is set to 2 and the
segment length between two polygon vertices is 10, 4 new
vertices would be created along the segment. Higher densities
produce higher fidelity footprints in areas of high projection
distortion. Mutually exclusive with ``densification_factor``.
simplify_tolerance (Optional[float]): Distance, in degrees, within which
all locations on the simplified polygon will be to the original
polygon.
no_data (Optional[Union[int, float]]): Explicitly sets the nodata value
if not in source image metadata. If set to None, this will return
a footprint including nodata values.
bands (List[int]): The bands to use to compute the footprint.
Defaults to [1]. If an empty list is provided, the bands will be ORd
together; e.g., for a pixel to be outside of the footprint, all
bands must have nodata in that pixel.
skip_errors (bool): If False, raise an error for a missing href or
footprint calculation failure.
Returns:
Iterator[Tuple[str, Dict[str, Any]]]: Iterator of the asset name and
dictionary representing a GeoJSON Polygon of the data footprint for
each asset.
"""
def handle_error(message: str) -> None:
if skip_errors:
logger.error(message)
else:
raise Exception(message)
for name, asset in item.assets.items():
if not asset_names or name in asset_names:
href = asset.get_absolute_href()
if href is None:
handle_error(
f"Could not determine extent for asset '{name}', missing href"
)
else:
extent = cls.from_href(
href=href,
dst_crs=dst_crs,
no_data=no_data,
bands=bands,
precision=precision,
densification_factor=densification_factor,
densification_distance=densification_distance,
simplify_tolerance=simplify_tolerance,
).footprint()
if extent:
yield name, extent
else:
handle_error(f"Could not determine extent for asset '{name}'")
[docs]
def update_geometry_from_asset_footprint(
item: Item,
*,
asset_names: List[str] = [],
precision: int = DEFAULT_PRECISION,
densification_factor: Optional[int] = None,
simplify_tolerance: Optional[float] = None,
no_data: Optional[Union[int, float]] = None,
bands: List[int] = [1],
skip_errors: bool = True,
) -> bool:
"""DEPRECATED.
.. deprecated:: 0.4.4
Use :meth:`RasterFootprint.update_geometry_from_asset_footprint`
instead.
Accepts an Item and an optional list of asset names within that Item, and
updates the geometry of that Item in-place with the data footprint derived
from the first of the assets that exists in the Item. The Item bbox is also
updated in-place to bound the new footprint extents.
See :class:`RasterFootprint` for details on the data footprint
calculation.
Args:
item (Item): The PySTAC Item to update.
asset_names (List[str]): The names of the assets for which to attempt to
extract footprints. The first successful footprint will be used. If
the list is empty, all assets will be tried until one is successful.
precision (int): The number of decimal places to include in the final
footprint coordinates.
densification_factor (Optional[int]): The factor by which to
increase point density within the polygon before projection to
EPSG 4326. A factor of 2 would double the density of points (placing
one new point between each existing pair of points), a factor of
3 would place two points between each point, etc. Higher
densities produce higher fidelity footprints in areas of high
projection distortion.
simplify_tolerance (Optional[float]): Distance, in degrees, within which
all locations on the simplified polygon will be to the original
polygon.
no_data (Optional[Union[int, float]]): Explicitly sets the nodata value
if not in source image metadata. If set to None, this will return
a footprint including nodata values.
bands (List[int]): The bands to use to compute the footprint.
Defaults to [1]. If an empty list is provided, the bands will be ORd
together; e.g., for a pixel to be outside of the footprint, all
bands must have nodata in that pixel.
skip_errors (bool): If False, raise an error for a missing href or
footprint calculation failure.
Returns:
bool: True if the Item geometry was successfully updated, False if not.
"""
warnings.warn(
"update_geometry_from_asset_footprint() is deprecated, use "
"RasterFootprint.update_geometry_from_asset_footprint() instead",
DeprecationWarning,
)
return RasterFootprint.update_geometry_from_asset_footprint(
item,
asset_names=asset_names,
precision=precision,
densification_factor=densification_factor,
simplify_tolerance=simplify_tolerance,
no_data=no_data,
bands=bands,
skip_errors=skip_errors,
)
[docs]
def data_footprints_for_data_assets(
item: Item,
*,
asset_names: List[str] = [],
precision: int = DEFAULT_PRECISION,
densification_factor: Optional[int] = None,
simplify_tolerance: Optional[float] = None,
no_data: Optional[Union[int, float]] = None,
bands: List[int] = [1],
skip_errors: bool = True,
) -> Iterator[Tuple[str, Dict[str, Any]]]:
"""DEPRECATED.
.. deprecated:: 0.4.4
Use :meth:`RasterFootprint.data_footprints_for_data_assets`
instead.
Accepts an Item and an optional list of asset names within that Item, and
produces an iterator over the same asset names (if they exist) and
dictionaries representing GeoJSON Polygons of the data footprints of the
assets.
See :class:`RasterFootprint` for details on the data footprint
calculation.
Args:
item (Item): The PySTAC Item to update.
asset_names (List[str]): The names of the assets for which to attempt to
extract footprints. The first successful footprint will be used. If
the list is empty, all assets will be tried until one is successful.
precision (int): The number of decimal places to include in the final
footprint coordinates.
densification_factor (Optional[int]): The factor by which to
increase point density within the polygon before projection to
EPSG 4326. A factor of 2 would double the density of points (placing
one new point between each existing pair of points), a factor of
3 would place two points between each point, etc. Higher
densities produce higher fidelity footprints in areas of high
projection distortion.
simplify_tolerance (Optional[float]): Distance, in degrees, within which
all locations on the simplified polygon will be to the original
polygon.
no_data (Optional[Union[int, float]]): Explicitly sets the nodata value
if not in source image metadata. If set to None, this will return
a footprint including nodata values.
bands (List[int]): The bands to use to compute the footprint.
Defaults to [1]. If an empty list is provided, the bands will be ORd
together; e.g., for a pixel to be outside of the footprint, all
bands must have nodata in that pixel.
skip_errors (bool): If False, raise an error for a missing href or
footprint calculation failure.
Returns:
Iterator[Tuple[str, Dict[str, Any]]]: Iterator of the asset name and
dictionary representing a GeoJSON Polygon of the data footprint for
each asset.
"""
warnings.warn(
"data_footprints_for_data_assets() is deprecated, use "
"RasterFootprint.data_footprints_for_data_assets() instead",
DeprecationWarning,
)
return RasterFootprint.data_footprints_for_data_assets(
item,
asset_names=asset_names,
precision=precision,
densification_factor=densification_factor,
simplify_tolerance=simplify_tolerance,
no_data=no_data,
bands=bands,
skip_errors=skip_errors,
)
[docs]
def data_footprint(
href: str,
*,
precision: int = DEFAULT_PRECISION,
densification_factor: Optional[int] = None,
simplify_tolerance: Optional[float] = None,
no_data: Optional[Union[int, float]] = None,
bands: List[int] = [1],
) -> Optional[Dict[str, Any]]:
"""DEPRECATED.
.. deprecated:: 0.4.4
Call :meth:`~RasterFootprint.footprint` on a :class:`RasterFootprint`
instance created with :meth:`RasterFootprint.from_href` instead.
Produces a data footprint from the href of a raster file.
See :class:`RasterFootprint` for details on the data footprint
calculation.
Args:
href (str): The href of the image to process.
precision (int): The number of decimal places to include in the final
footprint coordinates.
densification_factor (Optional[int]): The factor by which to
increase point density within the polygon before projection to
EPSG 4326. A factor of 2 would double the density of points (placing
one new point between each existing pair of points), a factor of
3 would place two points between each point, etc. Higher
densities produce higher fidelity footprints in areas of high
projection distortion.
simplify_tolerance (Optional[float]): Distance, in degrees, within which
all locations on the simplified polygon will be to the original
polygon.
no_data (Optional[Union[int, float]]): Explicitly sets the nodata value
if not in source image metadata. If set to None, this will return
a footprint including nodata values.
bands (List[int]): The bands to use to compute the footprint. Defaults
to [1]. If an empty list is provided, the bands will be ORd
together; e.g. for a pixel to be outside of the footprint, all bands
must have nodata in that pixel.
Returns:
Optional[Dict[str, Any]]: A dictionary representing a GeoJSON Polygon of
the data footprint of the raster data retrieved from the given ``href``.
"""
warnings.warn(
"data_footprint() is deprecated, use RasterFootprint.data_footprint() "
"on a RasterFootprint instance created with "
"RasterFootprint.from_href() instead",
DeprecationWarning,
)
return RasterFootprint.from_href(
href=href,
no_data=no_data,
bands=bands,
precision=precision,
densification_factor=densification_factor,
simplify_tolerance=simplify_tolerance,
).footprint()
[docs]
def densify_reproject_simplify(
polygon: Polygon,
crs: CRS,
*,
dst_crs: CRS = "EPSG:4326",
densification_factor: Optional[int] = None,
precision: int = DEFAULT_PRECISION,
simplify_tolerance: Optional[float] = None,
) -> Polygon:
"""Densifies the input polygon, reprojects it, and simplifies
the resulting polygon.
See :class:`RasterFootprint` for details on densification and
simplification.
Args:
polygon (Polygon): The input polygon.
crs (CRS): The CRS of the input polygon.
dst_crs (CRS): The CRS of the output polygon. Defaults to EPSG 4326
densification_factor (Optional[int]): The factor by which to
increase point density within the polygon before projection.
A factor of 2 would double the density of points (placing
one new point between each existing pair of points), a factor of
3 would place two points between each point, etc. Higher
densities produce higher fidelity footprints in areas of high
projection distortion.
precision (int): The number of decimal places to include in the final
polygon vertex coordinates.
simplify_tolerance (Optional[float]): Distance, in degrees, within which
all locations on the simplified polygon will be to the original
polygon.
Returns:
Polygon: The reprojected Polygon.
"""
if densification_factor is not None:
polygon = Polygon(
densify_by_factor(polygon.exterior.coords, factor=densification_factor)
)
polygon = reproject_shape(
geom=polygon, src_crs=crs, dst_crs=dst_crs, precision=precision
)
if simplify_tolerance is not None:
polygon = polygon.simplify(
tolerance=simplify_tolerance, preserve_topology=False
)
return polygon