from __future__ import annotations
import json
from pathlib import Path
from typing import Union
from geopandas import read_file
from pyproj import CRS, Transformer
from shapely.geometry import LineString, Polygon, mapping
from shapely.ops import transform
from mappymatch.constructs.trace import Trace
from mappymatch.utils.crs import LATLON_CRS
[docs]
class Geofence:
"""
A geofence is basically a shapely polygon with a CRS
Args:
geom: The polygon geometry of the geofence
crs: The CRS of the geofence
"""
def __init__(self, crs: CRS, geometry: Polygon):
self.crs = crs
self.geometry = geometry
[docs]
@classmethod
def from_geojson(cls, file: Union[Path, str]) -> Geofence:
"""
Creates a new geofence from a geojson file.
Args:
file: The path to the geojson file
Returns:
A new geofence
"""
filepath = Path(file)
frame = read_file(filepath)
if len(frame) > 1:
raise TypeError(
"found multiple polygons in the input; please only provide one"
)
elif frame.crs is None:
raise TypeError(
"no crs information found in the file; please make sure file has a crs"
)
polygon = frame.iloc[0].geometry
return Geofence(crs=frame.crs, geometry=polygon)
[docs]
@classmethod
def from_trace(
cls,
trace: Trace,
padding: float = 1e3,
crs: CRS = LATLON_CRS,
buffer_res: int = 2,
) -> Geofence:
"""
Create a new geofence from a trace.
This is done by computing a radial buffer around the
entire trace (as a line).
Args:
trace: The trace to compute the bounding polygon for.
padding: The padding (in meters) around the trace line.
crs: The coordinate reference system to use.
buffer_res: The resolution of the surrounding buffer.
Returns:
The computed bounding polygon.
"""
trace_line_string = LineString([c.geom for c in trace.coords])
# Add buffer to LineString.
polygon = trace_line_string.buffer(padding, buffer_res)
if trace.crs != crs:
project = Transformer.from_crs(
trace.crs, crs, always_xy=True
).transform
polygon = transform(project, polygon)
return Geofence(crs=crs, geometry=polygon)
return Geofence(crs=trace.crs, geometry=polygon)
[docs]
def to_geojson(self) -> str:
"""
Converts the geofence to a geojson string.
"""
if self.crs != LATLON_CRS:
transformer = Transformer.from_crs(self.crs, LATLON_CRS)
geometry: Polygon = transformer.transform(self.geometry) # type: ignore
else:
geometry = self.geometry
return json.dumps(mapping(geometry))