Pipeline

0. BlocksNet Installation

Import geopandas and set a local coordinate reference system (in EPSG format)

[ ]:
import geopandas as gpd

local_crs = 32637

1. Generation of urban blocks layer

Import block generation class from the library

[ ]:
from blocksnet import BlocksGenerator

Read parquet/geojson files with the geometries needed for block generation. Reproject them to local coordinate reference system.

[ ]:
city_geometry = gpd.read_file("/content/vologda_city_geometry.geojson").to_crs(local_crs)
water_geometry = gpd.read_file("/content/water_vologda.geojson").to_crs(local_crs)
roads_geometry = gpd.read_file("/content/roads_vologda.geojson").to_crs(local_crs)
railways_geometry = gpd.read_file("/content/railways_vologda.geojson").to_crs(local_crs)

Initialize BlocksGenerator with our geometries. Then call generate_blocks method to get a GeoDataFrame with block geometries.

[ ]:
blocks = BlocksGenerator(
  territory=city_geometry,
  water=water_geometry,
  roads=roads_geometry,
  railways=railways_geometry
).generate_blocks()
GENERATING BLOCKS
Setting up enclosures...
Filling holes...
Dropping overlapping blocks...
Calculating blocks area...
Blocks generated.

[ ]:
blocks
geometry
0 POLYGON ((550762.290 6565038.844, 550768.857 6...
1 POLYGON ((550768.857 6565033.563, 550762.290 6...
2 POLYGON ((544937.479 6564706.592, 544935.619 6...
3 POLYGON ((544806.647 6565359.291, 544814.358 6...
4 POLYGON ((544793.346 6565395.228, 544800.150 6...
... ...
675 POLYGON ((550330.064 6563176.320, 550349.264 6...
676 POLYGON ((546106.838 6566506.429, 546183.774 6...
677 POLYGON ((552977.299 6562202.373, 553051.882 6...
678 POLYGON ((556605.841 6559581.667, 555984.050 6...
679 POLYGON ((552990.050 6562231.172, 552917.365 6...

680 rows × 1 columns

[ ]:
blocks.plot()
<Axes: >
../_images/examples_pipeline_12_1.png

The resulting blocks can be saved to a file.

[ ]:
blocks.to_file('blocks.geojson')

2. Generation of Intermodal Urban Graph

At this stage, you can generate an intermodal graph by passing the blocks GeoDataFrame to GraphGenerator

[ ]:
from blocksnet import GraphGenerator

intermodal_graph = GraphGenerator(territory=blocks).get_graph('intermodal') #вместо intermodal можно передать drive, если необходимо посчитать время по графу дорог
Graph made for 'walk' network type
Graph made for 'bus'
Graph made for 'trolleybus'
Graph made for 'tram'
Graph made for 'subway'

Vizualization of the resulting intermodal graph

[ ]:
GraphGenerator.plot(intermodal_graph)
../_images/examples_pipeline_19_0.png

Saving the graph as a geojson file

[ ]:
import osmnx as ox
nodes, edges = ox.graph_to_gdfs(intermodal_graph)
nodes.to_file('graph_nodes.geojson')
edges[['length', 'weight', 'transport_type', 'geometry']].to_file('graph_edges.geojson')

3. Generation of Block Adjacency Matrix

Now, you can build a block adjacency matrix using the blocks layer and intermodal graph acquired on the previous stages. This matrix is obtained by finding the shortest travel time between each pair of blocks in the intermodal graph.

[ ]:
from blocksnet import AdjacencyCalculator

adjacency_matrix = AdjacencyCalculator(blocks=blocks, graph=intermodal_graph).get_dataframe()

Output of the obtained block adjacency matrix.

[ ]:
adjacency_matrix.head()
0 1 2 3 4 5 6 7 8 9 ... 670 671 672 673 674 675 676 677 678 679
0 0.0 5.8 63.8 69.2 67.1 52.3 63.5 63.3 61.0 63.4 ... 29.8 32.2 35.3 22.4 28.2 29.6 36.8 27.8 37.4 24.1
1 5.8 0.0 66.0 71.4 69.3 54.5 65.7 65.5 63.2 65.6 ... 32.0 30.9 33.3 20.4 26.2 27.6 39.0 25.8 39.6 26.3
2 53.4 55.6 0.0 48.8 46.7 76.6 45.7 47.1 49.4 51.8 ... 70.1 70.3 65.1 59.8 63.8 66.6 79.9 65.2 79.1 66.3
3 46.2 48.4 48.5 0.0 2.1 39.6 8.7 10.1 12.4 14.8 ... 58.0 59.7 57.9 52.6 51.7 54.5 63.0 58.0 71.9 59.1
4 44.1 46.3 46.4 2.1 0.0 37.5 6.6 8.0 10.3 12.7 ... 55.9 57.6 55.8 50.5 49.6 52.4 60.9 55.9 69.8 57.0

5 rows × 680 columns

Optionally, save the adjacency matrix as a file.

[ ]:
adjacency_matrix.to_pickle('adjacency_matrix.pickle')

4. Инициализация модели города

Now with the resulting matrix and block layer we can initialize a city model.

[ ]:
from blocksnet import City

vologda = City(
  blocks_gdf=blocks,
  adjacency_matrix=adjacency_matrix,
)

With print you can get information about the number of blocks in a city, types of city services (by default) and local coordinate reference system For each type of service you can see its name, normative availability in minutes and the value of normative availability per 1000 people (how many people out of 1000 need this type of service).

[ ]:
print(vologda)
CRS:          : EPSG:32637
Blocks count  : 680
Service types :
    3.5.1      school               15 min     120/1000 population
    3.5.1      kindergarten         7 min      61/1000 population
    3.4.2      hospital             60 min     9/1000 population
    3.4.1      polyclinic           10 min     13/1000 population
    5.1.3      pitch                60 min     10/1000 population
    5.1.2      swimming_pool        30 min     8/1000 population
    5.1.1      stadium              30 min     10/1000 population
    3.6.1      theatre              60 min     6/1000 population
    3.6.1      museum               60 min     1/1000 population
    3.6.1      cinema               60 min     9/1000 population
    4.2        mall                 30 min     8/1000 population
    4.4        convenience          5 min      180/1000 population
    4.4        supermarket          15 min     900/1000 population
    3.7.1      religion             30 min     10/1000 population
    4.3        marketplace          30 min     800/1000 population
    4.8.1      bowling_alley        60 min     9/1000 population

With this model, you can already perform basic analysis of urban environment. However, for more advanced analysis, information about existing service types should be added. Each of the service objects mush have the following attributes:

  • geometry - Point

  • capacity - int, capacity (power) of a service

[ ]:
school = gpd.read_file("/content/school_Vologda.geojson").to_crs(local_crs).rename(columns={'Вместимость':'capacity'})
kindergarten = gpd.read_file("/content/kindergarten_Vologda.geojson").to_crs(local_crs).rename(columns={'Вместимость':'capacity'})

vologda.update_services('school', school)
vologda.update_services('kindergarten', kindergarten)

And information about buildings in the city as well:

  • geometry - Point

  • population - int, population of a building (non-negative)

  • floors - float, number of levels in a building (non-negative)

  • area - float, area of building foundation, in square meters (non-negative)

  • living_area - float, living area, in square meters (non-negative)

  • is_living - bool, living (True) or non-living (False)

[ ]:
buildings = gpd.read_file("/content/buildings_vologda.geojson").to_crs(local_crs)
vologda.update_buildings(buildings)

Visualizing our model with blocks and aggregated information

[ ]:
vologda.plot()
../_images/examples_pipeline_39_0.png

5. Methods for Analysis of Urban Environment

5.1. Accessibility

With the Accessibility class, you can get a layer with information about block accessibility (relative to the chosen block).

[ ]:
block = vologda[135] # choose a block
[ ]:
from blocksnet.method import Accessibility

acc = Accessibility(city_model=vologda)
calc = acc.calculate(block)
calc.head()
id geometry distance
0 0 POLYGON ((550762.290 6565038.844, 550768.857 6... 15.8
1 1 POLYGON ((550768.857 6565033.563, 550762.290 6... 19.8
2 2 POLYGON ((544937.479 6564706.592, 544935.619 6... 70.9
3 3 POLYGON ((544806.647 6565359.291, 544814.358 6... 70.5
4 4 POLYGON ((544793.346 6565395.228, 544800.150 6... 68.4

Visualization of the resulting accessibility GeoDataFrame

[ ]:
acc.plot(calc)
../_images/examples_pipeline_46_0.png

You can also visualize normative availabiility of a selected service type

[ ]:
service_type = vologda['school']
[ ]:
acc.plot(calc, service_type)
../_images/examples_pipeline_49_0.png

Saving the results to a geojson file

[ ]:
calc.to_file('accessibility.geojson')

5.2. Connectivity

Other methods are used in a similar way. Here is an example of blocks’ transport connectivity calculation using Connectivity class.

[ ]:
from blocksnet.method import Connectivity

conn = Connectivity(city_model=vologda)
calc = conn.calculate()

Visualization of connectivity.

[ ]:
conn.plot(calc)
../_images/examples_pipeline_56_0.png

5.3. Service Provision

You can also assess service type provision for any block. Normative capacity and block population are used in assessment.

[ ]:
from blocksnet.method import Provision

prov = Provision(city_model=vologda)
calc_lp = prov.calculate(service_type)

Visualization of service provision.

[ ]:
prov.plot(calc_lp)
../_images/examples_pipeline_61_0.png

Save result to a file.

[ ]:
calc_lp.to_file('provision_lp.geojson')

You can also use iterative (greedy) method of population distribution for provision assessment.

[ ]:
calc_it = prov.calculate(service_type, method='iterative')

Visualization.

[ ]:
prov.plot(calc_it)
../_images/examples_pipeline_67_0.png

Getting statistical parameters of provision.

[ ]:
Provision.stat_provision(calc_it)
{'mean': 0.5715653412739928,
 'median': 0.7559055118110236,
 'min': 0.0,
 'max': 1.0}

Getting city-wide provision estimate.

[ ]:
Provision.total_provision(calc_it)
0.39514769318355997

Saving results to a file.

[ ]:
calc_it.to_file('provision_lp.geojson')

You can modify parameters of any block and perform provision assessment again. Let’s take a block with id=110 and set its popalation to 1000 people.

[ ]:
import pandas as pd
update = {
  110: {
    'population': 1000,
  }
}
update_df = pd.DataFrame.from_dict(update, orient='index')
[ ]:
calc_update = prov.calculate('school', update_df)

Visualization of provision after modification of block parameters.

[ ]:
prov.plot(calc_update)
../_images/examples_pipeline_78_0.png

Saving the results as a geojson file.

[ ]:
calc_update.to_file('provision_update.geojson')
[ ]:
prov.plot_delta(calc_lp, calc_update)
/usr/local/lib/python3.10/dist-packages/geopandas/geodataframe.py:1538: SettingWithCopyWarning:
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
  super().__setitem__(key, value)
../_images/examples_pipeline_81_1.png

5.4. Method for Optimization of Built Environment Parameters

Selecting the scenario (concept) of city development includes specifiying weights for each of the service types.

[ ]:
scenario = {
    'school': 0.6,
    'kindergarten': 0.4
}

Assessment of scenario provision.

[ ]:
gdfs, total = prov.calculate_scenario(scenario)
print(total)
0.5961190137320266

Visalization of scenario provision and saving to a file.

[ ]:
for service_type, gdf in gdfs.items():
  print(service_type)
  prov.plot(gdf)
  gdf.to_file(f'provision_{service_type}.geojson')
school
kindergarten
../_images/examples_pipeline_88_1.png
../_images/examples_pipeline_88_2.png

Selection of 15 random blocks to modify.

[ ]:
blocks_gdf = vologda.get_blocks_gdf()
selected_blocks_gdf = vologda.get_blocks_gdf().sample(15)
[ ]:
ax = blocks_gdf.plot(color='#ddd')
selected_blocks_gdf.plot(color='#333', ax=ax)
ax.set_axis_off()
../_images/examples_pipeline_91_0.png
[ ]:
from blocksnet.method import Genetic
gen = Genetic(city_model=vologda, SCENARIO=scenario)
selected_blocks = list(selected_blocks_gdf.index)
res = gen.calculate(comb_len=3, selected_blocks=selected_blocks)

Dictionary acquired after scenario optimization indicates suggested service capacities in the given blocks.

[ ]:
res
{360: {'school': 1350, 'kindergarten': 250},
 380: {'school': 550, 'kindergarten': 0},
 5: {'school': 900, 'kindergarten': 250},
 65: {'school': 550, 'kindergarten': 180},
 561: {'school': 250, 'kindergarten': 0},
 226: {'school': 1400, 'kindergarten': 0},
 54: {'school': 600, 'kindergarten': 280},
 10: {'school': 900, 'kindergarten': 0},
 290: {'school': 1150, 'kindergarten': 0},
 172: {'school': 0, 'kindergarten': 250},
 270: {'school': 1950, 'kindergarten': 0},
 199: {'school': 1700, 'kindergarten': 280}}

Visualization of the results.

[ ]:
res_df = pd.DataFrame.from_dict(res, orient='index')
res_df.head()
school kindergarten
360 1350 250
380 550 0
5 900 250
65 550 180
561 250 0
[ ]:
res_blocks_gdf = blocks_gdf.join(res_df, how='left')

Visualization of capacity for suggested schools.

[ ]:
ax = res_blocks_gdf.plot(color='#ddd')
res_blocks_gdf.plot(column='school', cmap='Greens', ax=ax, legend=True)
ax.set_axis_off()
../_images/examples_pipeline_99_0.png

Visalization of capacity for suggested kindergartens.

[ ]:
ax = res_blocks_gdf.plot(color='#ddd')
res_blocks_gdf.plot(column='kindergarten', cmap='Greens', ax=ax, legend=True)
ax.set_axis_off()
../_images/examples_pipeline_101_0.png
[ ]:
res_blocks_gdf.to_file('update.geojson')

Here we can ensure that the resulting development plan improves service provision for whole city while prioritizing schools.

[ ]:
gdfs, total = prov.calculate_scenario(scenario, res_df)
print(total)
0.6492021778309691
[ ]:
for service_type, gdf in gdfs.items():
  print(service_type)
  prov.plot(gdf)
  gdf.to_file(f'provision_upd_{service_type}.geojson')
school
kindergarten
../_images/examples_pipeline_105_1.png
../_images/examples_pipeline_105_2.png