import geopandas as gpd from shapely.geometry import Point from utils.logger import setup_logger from etl.spatial.ConservationAreaClient import read_shapefile_from_s3 from datatypes.datatypes import OpenUprnCoordinateData logger = setup_logger() class SpecialBuildingsClient: """ This class reads in data from Historic England, which can be used to determine if specific buildings are listed or heritage buildings """ def __init__(self, historic_england_listed_buildings_path, historic_england_heritage_buildings_path, bucket): self.historic_england_listed_buildings_path = historic_england_listed_buildings_path self.historic_england_heritage_buildings_path = historic_england_heritage_buildings_path self.bucket = bucket self.historic_england_listed_buildings = None self.historic_england_heritage_buildings = None def read(self): """ Read the data """ logger.info("Reading in historic england listed buildings shapefile") self.historic_england_listed_buildings = read_shapefile_from_s3( bucket_name=self.bucket, s3_file_key=self.historic_england_listed_buildings_path ) logger.info("Reading in historic england heritage buildings shapefile") self.historic_england_heritage_buildings = read_shapefile_from_s3( bucket_name=self.bucket, s3_file_key=self.historic_england_heritage_buildings_path ) # Convert the gov data to british national grid co-ordinates self.historic_england_heritage_buildings = self.historic_england_heritage_buildings.to_crs("EPSG:27700") def is_listed_building(self, coordinates: OpenUprnCoordinateData) -> bool: """ Check if a location specified by British National Grid coordinates is a listed building. :param coordinates: dictionary, which should have the OpenUprnCoordinateData format :return: "listed_building" if the location is within a listed building polygon, "not_listed_building" otherwise """ # Convert the coordinates to a Shapely Point object point = Point(coordinates.X_COORDINATE, coordinates.Y_COORDINATE) # Check if the point is within any of the listed building polygons within_listed_buildings = self.historic_england_listed_buildings.contains(point) if within_listed_buildings.any(): # If the point is within any listed building polygon, log the names of the buildings and return # "listed_building" names = self.historic_england_listed_buildings.loc[within_listed_buildings, "Name"] logger.info(f"The location is within the following listed buildings: {names.values}") return True # If the point is not within any listed building polygon, return "not_listed_building" return False def is_listed_building_vectorised(self, uprn_gdf: gpd.GeoDataFrame) -> gpd.GeoDataFrame: # Check against historic England listed buildings data joined_gdf_listed = gpd.sjoin(uprn_gdf, self.historic_england_listed_buildings, how="left", predicate="within") # Identify where we have matches uprn_is_listed = joined_gdf_listed[~joined_gdf_listed.index_right.isna()]["UPRN"].unique() # Populate the results in the input GeoDataFrame uprn_gdf['is_listed_building'] = False uprn_gdf.loc[uprn_gdf["UPRN"].isin(uprn_is_listed), 'is_listed_building'] = True return uprn_gdf def is_heritage_building_at_risk(self, coordinates: OpenUprnCoordinateData) -> bool: """ Check if a location specified by British National Grid coordinates is a heritage building at risk. :param coordinates: dictionary, which should have the OpenUprnCoordinateData format :return: "heritage_building_at_risk" if the location is within a heritage building at risk polygon, "not_heritage_building_at_risk" otherwise """ # Convert the coordinates to a Shapely Point object point = Point(coordinates.X_COORDINATE, coordinates.Y_COORDINATE) # Check if the point is within any of the heritage building at risk polygons within_heritage_buildings_at_risk = self.historic_england_heritage_buildings.contains(point) if within_heritage_buildings_at_risk.any(): # If the point is within any heritage building at risk polygon, log the names of the buildings and return # "heritage_building_at_risk" names = self.historic_england_heritage_buildings.loc[within_heritage_buildings_at_risk, "EntryName"] logger.info(f"The location is within the following heritage buildings at risk: {names.values}") return True # If the point is not within any heritage building at risk polygon, return "not_heritage_building_at_risk" return False def is_heritage_building_at_risk_vectorised(self, uprn_gdf: gpd.GeoDataFrame) -> gpd.GeoDataFrame: # Check against historic England heritage buildings data joined_gdf_heritage = gpd.sjoin( uprn_gdf, self.historic_england_heritage_buildings, how="left", predicate="within" ) # Identify where we have matches uprn_is_heritage = joined_gdf_heritage[~joined_gdf_heritage.index_right.isna()]["UPRN"].unique() # Populate the results in the input GeoDataFrame uprn_gdf['is_heritage_building'] = False uprn_gdf.loc[uprn_gdf["UPRN"].isin(uprn_is_heritage), 'is_heritage_building'] = True return uprn_gdf