import pandas as pd import geopandas as gpd from shapely.geometry import Point from model_data.utils import setup_logger 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_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