Source code for cityImage.land_use
import pandas as pd
import numpy as np
import geopandas as gpd
from shapely.geometry import Point, Polygon
pd.set_option("display.precision", 3)
[docs]def classify_land_use(buildings_gdf, new_land_use_field, land_use_field, categories, strings):
"""
The function reclassifies land-use descriptors in a land-use field according to the categorisation presented below.
(Not exhaustive)
Parameters
----------
buildings_gdf: Polygon GeoDataFrame
The buildings GeoDataFrame.
land_use: string
The land use field in the buildings_gdf.
Returns
-------
buildings_gdf: Polygon GeoDataFrame
The updated buildings' GeoDataFrame.
"""
buildings_gdf = buildings_gdf.copy()
# Create a new column with the same values as the land_use_field column
buildings_gdf[new_land_use_field] = buildings_gdf[land_use_field].copy()
# reclassifying: replacing original values with relative categories
buildings_gdf[new_land_use_field] = buildings_gdf[land_use_field]
for n, category in enumerate(categories):
buildings_gdf[new_land_use_field] = buildings_gdf[new_land_use_field].map(lambda x: strings[n] if x in category else x)
return buildings_gdf
[docs]def land_use_from_other_gdf(buildings_gdf, other_gdf, new_land_use_field, land_use_field):
"""
It assigns land-use attributes to buildings in a buildings GeoDataFrame, looking for possible matches in "other_gdf", a Polygon or Point GeoDataFrame
Polygon: Possible matches here means the buildings in "other_gdf" whose area of interesection with the examined building (y), covers at least
60% of the building's (y) area. The best match is chosen.
Point: Possible matches are identified when points lie within the buildings_gdf polygons. The most represented category is chosen when more than one points
lies inside a building footprint.
Parameters
----------
buildings_gdf: Polygon GeoDataFrame
The buildings GeoDataFrame.
other_gdf: Point or Polygon GeoDataFrame
The GeoDataFrame wherein looking for land_use attributes.
new_land_use_field: str
Name of the column in buildings_gdf to which assign the land_use descriptor.
land_use_field: str
Name of the column in other_gdf wherein the land_use attribute is stored.
Returns
-------
buildings_gdf: Polygon GeoDataFrame
The updated buildings' GeoDataFrame.
"""
buildings_gdf = buildings_gdf.copy()
buildings_gdf[new_land_use_field] = None
if (other_gdf.iloc[0].geometry.geom_type == 'Point'):
other_gdf["nr"] = 1
# spatial index
sindex = other_gdf.sindex
if (other_gdf.iloc[0].geometry.geom_type == 'Point'):
buildings_gdf[new_land_use_field] = buildings_gdf.geometry.apply(lambda row: _land_use_from_points(row, other_gdf,
sindex, land_use_field))
else:
buildings_gdf[new_land_use_field] = buildings_gdf.geometry.apply(lambda row: _land_use_from_polygons(row, other_gdf,
sindex, land_use_field))
return buildings_gdf
def _land_use_from_polygons(building_geometry, other_gdf, other_gdf_sindex, land_use_field):
"""
It assigns land-use attributes to a building, looking for possible matches in a"other_gdf", a Polygon GeoDataFrame.
Parameters
----------
buildings_gdf: Polygon GeoDataFrame
The buildings GeoDataFrame.
other_gdf: Polygon GeoDataFrame
The GeoDataFrame wherein looking for land_use attributes
other_gdf_sindex: Spatial Index
The Spatial Index on the other GeoDataFrame.
land_use_field: str
name of the column in other_gdf wherein the land_use attribute is stored.
Returns
-------
Object
The assigned land-use attribute or None if no match is found.
"""
# Find the possible matches
possible_matches_index = list(other_gdf_sindex.intersection(building_geometry.bounds))
possible_matches = other_gdf.iloc[possible_matches_index]
# Keep only the polygons that intersect with the building
pm = possible_matches[possible_matches.intersects(building_geometry)]
if len(pm) == 0:
return None # no intersecting features in the other_gdf
# calculate area of intersection between building and each possible match
pm["area_int"] = pm.loc[pm.intersects(building_geometry), 'geometry'].apply(lambda row: row.intersection(building_geometry).area)
# sort the matches based on the extent of the area of intersection
pm = pm.sort_values(by="area_int", ascending=False)
# Assign the match land-use category if the area of intersection covers at least 60% of the building's area
if pm["area_int"].iloc[0] >= (building_geometry.area * 0.60):
return pm[land_use_field].iloc[0]
return None
def _land_use_from_points(building_geometry, other_gdf, other_source_gdf_sindex, land_use_field):
"""
It assigns land-use attributes to a building, looking for possible matches in a "other_gdf", a Point GeoDataFrame.
Parameters
----------
building_geometry: Polygon
A building's geometry.
other_gdf: Point GeoDataFrame
The GeoDataFrame wherein looking for land_use attributes.
other_gdf_sindex: Spatial Index
The Spatial Index on the other GeoDataFrame.
land_use_field: str
name of the column in other_gdf wherein the land_use attribute is stored.
Returns
-------
Object
The assigned land-use attribute or None if no match is found.
"""
possible_matches_index = list(other_source_gdf_sindex.intersection(building_geometry.bounds))
possible_matches = other_source_gdf.iloc[possible_matches_index]
pm = possible_matches[possible_matches.intersects(building_geometry)]
if (len(pm)==0):
return None # no intersecting features in the other_source_gdf
# counting nr of features and using the most represented one
pm.groupby([land_use_field],as_index=False)["nr"].sum().sort_values(by="nr", ascending=False).reset_index()
return pm[land_use_field].iloc[0]