Model/conservation_areas/ConservationAreaClient.py
Khalim Conn-Kowlessar fb0c5859b9 restructure wip
2023-07-20 12:37:03 +01:00

143 lines
5.2 KiB
Python

from enum import Enum
import geopandas as gpd
from shapely.geometry import Point
from utils.logger import setup_logger
from datatypes.datatypes import OpenUprnCoordinateData
logger = setup_logger()
class ConservationAreaClient:
"""
Class to interact and manupulate convervation area data. The historic england data
can be found at the following location:
https://opendata-historicengland.hub.arcgis.com/datasets/historicengland::conservation-areas/about
We also use a separate government conservation area dataset which can be found here:
https://www.planning.data.gov.uk/dataset/conservation-area
"""
SOURCES = ["historic_england"]
IN_CONSERVATION_AREA = "in_conservation_area"
NOT_IN_CONSERVATION_AREA = "not_in_conservation_area"
UNKNOWN = "unknown"
def __init__(self, historic_england_path, gov_path):
self.historic_england_path = historic_england_path
self.gov_path = gov_path
self.historic_england_data = None
self.gov_data = None
def read(self):
"""
Read the data
"""
logger.info("Reading in historic england conservation area shapefile")
self.historic_england_data = gpd.read_file(self.historic_england_path)
logger.info("Reading in Govenment conservation area geojson")
self.gov_data = gpd.read_file(self.gov_path)
self.gov_data = self.gov_data.drop(columns=["dataset"])
def is_in_conservation_area(self, coordinates: OpenUprnCoordinateData):
"""
Check if a property is in a conservation area
:param coordinates: dictionary, which should have the OpenUprnCoordinateData format
:return:
"""
if not coordinates:
raise ValueError("Coordinates have not been set, run get_coordinates() first")
is_in_conservation_area = self.is_in_conservation_area_historic_england(
x_bng=coordinates.X_COORDINATE,
y_bng=coordinates.Y_COORDINATE
)
if is_in_conservation_area != "unknown":
return is_in_conservation_area
if is_in_conservation_area == "unknown":
# We double check the secondary data source
backup = self.is_in_conservation_area_historic_gov(
longitude=coordinates.LONGITUDE,
latitude=coordinates.LATITUDE
)
if backup:
return ConservationAreaClient.IN_CONSERVATION_AREA
else:
return ConservationAreaClient.UNKNOWN
def is_in_conservation_area_historic_england(self, x_bng: float, y_bng: float) -> str:
"""
Check if a property is in a conservation area
:param x_bng: x coordinate in british national grid coordinates
:param y_bng: y coordinate in british national grid coordinates
"""
point = Point(x_bng, y_bng)
within_areas = self.historic_england_data.contains(point)
if within_areas.any():
names = self.historic_england_data.loc[within_areas, "NAME"]
# We want to deduce if we actually have data on this area
if all(names.values == "No data available for publication by HE"):
return self.UNKNOWN
return self.IN_CONSERVATION_AREA
return self.NOT_IN_CONSERVATION_AREA
def is_in_conservation_area_historic_gov(self, longitude: float, latitude: float) -> str:
"""
Check if a property is in a conservation area
:param longitude: longtitude coordinate
:param latitude: latitude coordinate
"""
point = Point(longitude, latitude)
return self.gov_data.contains(point).any()
def calculate_distance_to_nearest_conservation_area(self, x: float, y: float, source: str) -> float:
if source == "historic_england":
return self._distance_to_nearest_conservation_area_historic_england(x, y, self.historic_england_data)
@staticmethod
def _distance_to_nearest_conservation_area_historic_england(
x: float, y: float, conservation_areas: gpd.GeoDataFrame
) -> float:
"""
Calculate the distance from a given point to the nearest conservation area.
:param x: The x-coordinate of the point.
:param y: The y-coordinate of the point.
:param conservation_areas: A GeoDataFrame containing the conservation areas polygons.
:return: The distance in the same units as the coordinate system of the conservation areas.
:raises FileNotFoundError: If the conservation areas GeoDataFrame is not found.
:raises IndexError: If no nearest conservation area is found.
"""
# Convert the point coordinates to a Shapely Point object
point_geom = Point(x, y)
# Calculate the distance between the point and the conservation areas
distances = conservation_areas.geometry.distance(point_geom)
# Find the minimum distance. Since the data uses british national grid, the units are meters.
distance_meters = distances.min()
return distance_meters
class InConservationArea(Enum):
IN_CONSERVATION_AREA = ConservationAreaClient.IN_CONSERVATION_AREA
NOT_IN_CONSERVATION_AREA = ConservationAreaClient.NOT_IN_CONSERVATION_AREA
UNKNOWN = ConservationAreaClient.UNKNOWN