################################################################################################### # Repository: https://github.com/lgervasoni/urbansprawl # MIT License ################################################################################################### import geopandas as gpd import osmnx as ox def proportional_population_downscaling(df_osm_built, df_insee): """ Performs a proportional population downscaling considering the surface dedicated to residential land use Associates the estimated population to each building in column 'population' Parameters ---------- df_osm_built : geopandas.GeoDataFrame input buildings with computed residential surface df_insee : geopandas.GeoDataFrame INSEE population data Returns ---------- """ if (df_insee.crs != df_osm_built.crs): # If projections do not match # First project to Lat-Long coordinates, then project to UTM coordinates df_insee = ox.project_gdf( ox.project_gdf(df_insee, to_latlong=True) ) # OSM Building geometries are already projected assert(df_insee.crs == df_osm_built.crs) df_osm_built['geom'] = df_osm_built.geometry df_osm_built_residential = df_osm_built[ df_osm_built.apply(lambda x: x.landuses_m2['residential'] > 0, axis = 1) ] # Loading/saving using geopandas loses the 'ellps' key df_insee.crs = df_osm_built_residential.crs # Intersecting gridded population - buildings sjoin = gpd.sjoin( df_insee, df_osm_built_residential, op='intersects') # Calculate area within square (percentage of building with the square) sjoin['residential_m2_within'] = sjoin.apply(lambda x: x.landuses_m2['residential'] * (x.geom.intersection(x.geometry).area / x.geom.area), axis=1 ) # Initialize df_insee['residential_m2_within'] = 0 # Sum residential area within square sum_m2_per_square = sjoin.groupby(sjoin.index)['residential_m2_within'].sum() # Assign total residential area within each square df_insee.loc[ sum_m2_per_square.index, "residential_m2_within" ] = sum_m2_per_square.values # Get number of M^2 / person df_insee[ "m2_per_person" ] = df_insee.apply(lambda x: x.residential_m2_within / x.pop_count, axis=1) def population_building(x, df_insee): # Sum of: For each square: M2 of building within square / M2 per person return ( x.get('m2',[]) / df_insee.loc[ x.get('idx',[]) ].m2_per_person ).sum() # Index: Buildings , Values: idx:Indices of gridded square population, m2: M2 within that square buildings_square_m2_association = sjoin.groupby('index_right').apply(lambda x: {'idx':list(x.index), 'm2':list(x.residential_m2_within)} ) # Associate df_osm_built.loc[ buildings_square_m2_association.index, "population" ] = buildings_square_m2_association.apply(lambda x: population_building(x,df_insee) ) # Drop unnecessary column df_osm_built.drop('geom', axis=1, inplace=True)