"""I/O functions for saving and loading collections of regions of interest."""
from __future__ import annotations
import json
from collections.abc import Sequence
from typing import TYPE_CHECKING, Any, TypeAlias
import shapely
from movement.roi.base import BaseRegionOfInterest
from movement.roi.line import LineOfInterest
from movement.roi.polygon import PolygonOfInterest
from movement.validators.files import (
ValidROICollectionGeoJSON,
validate_file_path,
)
if TYPE_CHECKING:
from pathlib import Path
# Type alias for collections of RoIs
ROICollection: TypeAlias = Sequence[BaseRegionOfInterest]
[docs]
def save_rois(
rois: ROICollection,
file: str | Path,
) -> None:
"""Save a collection of regions of interest (RoIs) to a GeoJSON file.
The RoIs are saved as a GeoJSON FeatureCollection, with each RoI
represented as a Feature containing its geometry and properties
(name and RoI type).
Parameters
----------
rois
The regions of interest to save.
file
Path to the output file.
Should have a ``.geojson`` or a ``.json`` extension.
See Also
--------
load_rois : Load a collection of RoIs from a GeoJSON file.
Examples
--------
Create a polygon and a line, then save them to a GeoJSON file:
>>> from movement.roi import LineOfInterest, PolygonOfInterest, save_rois
>>> square = PolygonOfInterest(
... [(0, 0), (1, 0), (1, 1), (0, 1)], name="square"
... )
>>> diagonal = LineOfInterest([(0, 0), (1, 1)], name="diagonal")
>>> save_rois([square, diagonal], "/path/to/rois.geojson")
"""
valid_path = validate_file_path(
file, permission="w", suffixes=ValidROICollectionGeoJSON.suffixes
)
features = [
{
"type": "Feature",
"geometry": json.loads(shapely.to_geojson(roi.region)),
"properties": {
"name": roi.name,
"roi_type": roi.__class__.__name__,
},
}
for roi in rois
]
feature_collection = {
"type": "FeatureCollection",
"features": features,
}
with open(valid_path, "w") as f:
json.dump(feature_collection, f, indent=2)
[docs]
def load_rois(file: str | Path) -> list[LineOfInterest | PolygonOfInterest]:
"""Load a collection of regions of interest (RoIs) from a GeoJSON file.
Parameters
----------
file
Path to the GeoJSON file to load.
Returns
-------
list of LineOfInterest or PolygonOfInterest
The loaded regions of interest.
See Also
--------
save_rois : Save a collection of RoIs to a GeoJSON file.
Examples
--------
Load a collection of RoIs from a GeoJSON file and inspect their names:
>>> from movement.roi import load_rois
>>> rois = load_rois("/path/to/rois.geojson")
>>> # rois is a list of RoI objects
>>> [roi.name for roi in rois]
["square", "diagonal"]
"""
validated_file = ValidROICollectionGeoJSON(file)
return [
_feature_to_roi(feature) for feature in validated_file.data["features"]
]
def _feature_to_roi(
feature: dict[str, Any],
) -> LineOfInterest | PolygonOfInterest:
"""Convert a validated GeoJSON feature to an RoI object."""
geometry = shapely.from_geojson(json.dumps(feature["geometry"]))
name = (feature.get("properties") or {}).get("name")
if isinstance(geometry, shapely.Polygon):
holes = [interior.coords for interior in geometry.interiors]
return PolygonOfInterest(
geometry.exterior.coords,
holes=holes,
name=name,
)
loop = isinstance(geometry, shapely.LinearRing)
return LineOfInterest(geometry.coords, loop=loop, name=name)