Spaces:
Runtime error
Runtime error
Commit ·
124003a
1
Parent(s): 7c7f766
demo version
Browse files- README.md +4 -0
- docs/gui.rst +16 -0
- docs/install.rst +0 -1
- docs/quickstart.rst +11 -5
- src/{app.py → gui/app_datagenerator.py} +0 -0
- src/gui/app_engine.py +394 -0
- src/gui/engine.py +286 -0
- src/gui/sandbox.py +100 -0
- src/tests/nairobi_business_buildings.geojson +0 -0
- src/tests/nairobi_business_household.json +0 -0
- src/tests/nairobi_business_individual.json +3 -0
- src/tests/nairobi_business_landuse.geojson +0 -0
- src/tests/nairobi_flood_depth_50yr.geojson +3 -0
- src/tests/nairobi_flood_vulnerability.json +0 -0
README.md
CHANGED
|
@@ -1 +1,5 @@
|
|
| 1 |
# TomorrowCities Python Library
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
# TomorrowCities Python Library
|
| 2 |
+
|
| 3 |
+
## Running GUI
|
| 4 |
+
|
| 5 |
+
###
|
docs/gui.rst
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
:: _gui:
|
| 2 |
+
|
| 3 |
+
Graphical User Interfaces
|
| 4 |
+
=========================
|
| 5 |
+
There are several graphical user interfaces (GUIs)
|
| 6 |
+
prepared to show case the features of the library.
|
| 7 |
+
The codes related to GUIs are in the ``src/gui`` directory.
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
Launching
|
| 11 |
+
=========
|
| 12 |
+
There are different ways to launch the GUIs depending
|
| 13 |
+
on how you obtain the library.
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
|
docs/install.rst
CHANGED
|
@@ -1,6 +1,5 @@
|
|
| 1 |
:: _install:
|
| 2 |
|
| 3 |
-
============
|
| 4 |
Installation
|
| 5 |
============
|
| 6 |
|
|
|
|
| 1 |
:: _install:
|
| 2 |
|
|
|
|
| 3 |
Installation
|
| 4 |
============
|
| 5 |
|
docs/quickstart.rst
CHANGED
|
@@ -1,16 +1,22 @@
|
|
| 1 |
-
===========
|
| 2 |
Quick Start
|
| 3 |
===========
|
| 4 |
|
| 5 |
.. highlight:: python
|
| 6 |
|
| 7 |
-
After :
|
| 8 |
-
|
|
|
|
| 9 |
|
| 10 |
.. code-block:: python
|
| 11 |
|
| 12 |
import tomorrowcities as tc
|
| 13 |
|
| 14 |
-
dg = tc.DataGenerator(parameter_file='
|
| 15 |
-
land_use_file='
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
|
|
|
|
|
|
|
| 1 |
Quick Start
|
| 2 |
===========
|
| 3 |
|
| 4 |
.. highlight:: python
|
| 5 |
|
| 6 |
+
After :doc:`/install`, you can import `tomorrowcities`
|
| 7 |
+
package and start generating exposure data or calculating
|
| 8 |
+
impact metrics.
|
| 9 |
|
| 10 |
.. code-block:: python
|
| 11 |
|
| 12 |
import tomorrowcities as tc
|
| 13 |
|
| 14 |
+
dg = tc.DataGenerator(parameter_file='distribution_table.xlsx',
|
| 15 |
+
land_use_file='landuse.zip')
|
| 16 |
+
|
| 17 |
+
building, household, individual, land_use = dg.generate(seed=42)
|
| 18 |
+
|
| 19 |
+
metrics = dg.run_engine(building, household, individual, land_use,
|
| 20 |
+
hazard_scenario="FLOOD", hazard_data="flood.xlsx", policies=None):
|
| 21 |
+
|
| 22 |
|
src/{app.py → gui/app_datagenerator.py}
RENAMED
|
File without changes
|
src/gui/app_engine.py
ADDED
|
@@ -0,0 +1,394 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import solara
|
| 2 |
+
from solara.components.file_drop import FileInfo
|
| 3 |
+
import os
|
| 4 |
+
os.environ['USE_PYGEOS'] = '0'
|
| 5 |
+
import geopandas as gpd
|
| 6 |
+
import pandas as pd
|
| 7 |
+
import json
|
| 8 |
+
import numpy as np
|
| 9 |
+
import ipyleaflet
|
| 10 |
+
from engine import compute_flood
|
| 11 |
+
import random
|
| 12 |
+
from matplotlib.figure import Figure
|
| 13 |
+
import matplotlib.pyplot as plt
|
| 14 |
+
import math
|
| 15 |
+
|
| 16 |
+
plt.switch_backend("agg")
|
| 17 |
+
|
| 18 |
+
# Static parameters
|
| 19 |
+
initial_building_columns = set(['zoneID', 'bldID', 'nHouse', 'residents', 'specialFac', 'expStr', 'fptarea', 'repValue'])
|
| 20 |
+
building_columns = set(['geometry','metric1','metric2','metric3','metric4','metric5','metric6','metric7','zoneID', 'bldID', 'nHouse', 'residents', 'specialFac', 'expStr', 'fptarea', 'repValue'])
|
| 21 |
+
landuse_columns = set(['geometry', 'zoneID', 'LuF', 'population', 'densityCap', 'floorARat', 'setback', 'avgIncome'])
|
| 22 |
+
household_columns = set(['hhID', 'nInd', 'income', 'bldID', 'CommFacID'])
|
| 23 |
+
individual_columns = set(['indivId', 'hhID', 'gender', 'age', 'eduAttStat', 'head', 'indivFacID'])
|
| 24 |
+
intensity_columns = set(['geometry','im'])
|
| 25 |
+
vulnerabillity_columns = set(['expstr', 'hw0', 'hw0_5', 'hw1', 'hw1_5', 'hw2', 'hw3', 'hw4', 'hw5','hw6'])
|
| 26 |
+
all_layers = ["Landuse", "Buildings", "Household","Individual","Intensity","Vulnerability"]
|
| 27 |
+
metrics_template = {"metric1": {"desc": "Number of workers unemployed", "value": 0, "max_value": 0},
|
| 28 |
+
"metric2": {"desc": "Number of children with no access to education", "value": 0, "max_value": 0},
|
| 29 |
+
"metric3": {"desc": "Number of households with no access to hospital", "value": 0, "max_value": 0},
|
| 30 |
+
"metric4": {"desc": "Number of individuals with no access to hospital", "value": 0, "max_value": 0},
|
| 31 |
+
"metric5": {"desc": "Number of homeless households", "value": 0, "max_value": 0},
|
| 32 |
+
"metric6": {"desc": "Number of homeless individuals", "value": 0, "max_value": 0},
|
| 33 |
+
"metric7": {"desc": "Population displacement", "value": 0, "max_value": 0},}
|
| 34 |
+
|
| 35 |
+
layers = solara.reactive([])
|
| 36 |
+
|
| 37 |
+
base_map = ipyleaflet.basemaps.OpenStreetMap.BZH
|
| 38 |
+
|
| 39 |
+
default_zoom = 14
|
| 40 |
+
default_radius = 10
|
| 41 |
+
default_center = (41.03,28.94)
|
| 42 |
+
|
| 43 |
+
center = solara.reactive(default_center)
|
| 44 |
+
zoom = solara.reactive(default_zoom)
|
| 45 |
+
bounds = solara.reactive(None)
|
| 46 |
+
radius = solara.reactive(default_radius)
|
| 47 |
+
|
| 48 |
+
building_df = solara.reactive(None)
|
| 49 |
+
landuse_geojson = solara.reactive(None)
|
| 50 |
+
clicked_df = solara.reactive(pd.DataFrame(columns=['attribute','value']))
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
def building_click_handler(event=None, feature=None, id=None, properties=None):
|
| 54 |
+
df = pd.DataFrame(columns=['attribute','value'])
|
| 55 |
+
df['attribute'] = [k for k in properties.keys() if k != 'style']
|
| 56 |
+
df['value']= [str(properties[k]) for k in properties.keys() if k != 'style']
|
| 57 |
+
clicked_df.set(df)
|
| 58 |
+
|
| 59 |
+
def landuse_click_handler(event=None, feature=None, id=None, properties=None):
|
| 60 |
+
df = pd.DataFrame(columns=['attribute','value'])
|
| 61 |
+
df['attribute'] = [k for k in properties.keys() if k != 'style']
|
| 62 |
+
df['value']= [str(properties[k]) for k in properties.keys() if k != 'style']
|
| 63 |
+
clicked_df.set(df)
|
| 64 |
+
|
| 65 |
+
def landuse_colors(feature):
|
| 66 |
+
luf_type = feature['properties']['LuF']
|
| 67 |
+
if luf_type == 'RESIDENTIAL (HIGH DENSITY)':
|
| 68 |
+
luf_color = {
|
| 69 |
+
'color': 'black',
|
| 70 |
+
'fillColor': '#A0522D', # sienna
|
| 71 |
+
}
|
| 72 |
+
elif luf_type == 'HISTORICAL PRESERVATION AREA':
|
| 73 |
+
luf_color = {
|
| 74 |
+
'color': 'black',
|
| 75 |
+
'fillColor': '#673147', # plum
|
| 76 |
+
}
|
| 77 |
+
elif luf_type == 'RESIDENTIAL (MODERATE DENSITY)':
|
| 78 |
+
luf_color = {
|
| 79 |
+
'color': 'black',
|
| 80 |
+
'fillColor': '#cd853f', # peru
|
| 81 |
+
}
|
| 82 |
+
elif luf_type == 'COMMERCIAL AND RESIDENTIAL':
|
| 83 |
+
luf_color = {
|
| 84 |
+
'color': 'black',
|
| 85 |
+
'fillColor': 'red',
|
| 86 |
+
}
|
| 87 |
+
elif luf_type == 'CITY CENTER':
|
| 88 |
+
luf_color = {
|
| 89 |
+
'color': 'black',
|
| 90 |
+
'fillColor': '#E6E6FA', # lavender
|
| 91 |
+
}
|
| 92 |
+
elif luf_type == 'INDUSTRY':
|
| 93 |
+
luf_color = {
|
| 94 |
+
'color': 'black',
|
| 95 |
+
'fillColor': 'grey',
|
| 96 |
+
}
|
| 97 |
+
elif luf_type == 'RESIDENTIAL (LOW DENSITY)':
|
| 98 |
+
luf_color= {
|
| 99 |
+
'color': 'black',
|
| 100 |
+
'fillColor': '#D2B48C', # tan
|
| 101 |
+
}
|
| 102 |
+
elif luf_type == 'RESIDENTIAL (GATED NEIGHBORHOOD)':
|
| 103 |
+
luf_color= {
|
| 104 |
+
'color': 'black',
|
| 105 |
+
'fillColor': 'orange',
|
| 106 |
+
}
|
| 107 |
+
elif luf_type == 'AGRICULTURE':
|
| 108 |
+
luf_color= {
|
| 109 |
+
'color': 'black',
|
| 110 |
+
'fillColor': 'yellow',
|
| 111 |
+
}
|
| 112 |
+
elif luf_type == 'FOREST':
|
| 113 |
+
luf_color= {
|
| 114 |
+
'color': 'black',
|
| 115 |
+
'fillColor': 'green',
|
| 116 |
+
}
|
| 117 |
+
elif luf_type == 'VACANT ZONE':
|
| 118 |
+
luf_color = {
|
| 119 |
+
'color': 'black',
|
| 120 |
+
'fillColor': '#90EE90', # lightgreen
|
| 121 |
+
}
|
| 122 |
+
elif luf_type == 'RECREATION AREA':
|
| 123 |
+
luf_color = {
|
| 124 |
+
'color': 'black',
|
| 125 |
+
'fillColor': '#32CD32', #lime
|
| 126 |
+
}
|
| 127 |
+
else:
|
| 128 |
+
luf_color = {
|
| 129 |
+
'color': 'black',
|
| 130 |
+
'fillColor': random.choice(['red', 'yellow', 'green', 'orange','blue']),
|
| 131 |
+
}
|
| 132 |
+
return luf_color
|
| 133 |
+
|
| 134 |
+
def building_colors(feature):
|
| 135 |
+
print(feature['properties'])
|
| 136 |
+
#damage = 0
|
| 137 |
+
#for metric in metrics_template.keys():
|
| 138 |
+
# damage += feature['properties'][metric]
|
| 139 |
+
# metric5 is the number of damaged households
|
| 140 |
+
if feature['properties']['metric5'] > 0:
|
| 141 |
+
return {'fillColor': 'red', 'color': 'red'}
|
| 142 |
+
else:
|
| 143 |
+
return {'fillColor': 'gray', 'color': 'blue'}
|
| 144 |
+
|
| 145 |
+
@solara.component
|
| 146 |
+
def MapComponent():
|
| 147 |
+
|
| 148 |
+
extra_layers = [l['geojson'] for l in layers.value if 'geojson' in l.keys()]
|
| 149 |
+
|
| 150 |
+
if building_df.value is not None:
|
| 151 |
+
json_data = json.loads(building_df.value.to_json())
|
| 152 |
+
building_layer = ipyleaflet.GeoJSON(data=json_data,
|
| 153 |
+
style={'opacity': 1, 'fillOpacity': 0.5, 'weight': 1},
|
| 154 |
+
hover_style={'color': 'red', 'dashArray': '0', 'fillOpacity': 0.5},
|
| 155 |
+
style_callback=building_colors)
|
| 156 |
+
building_layer.on_click(building_click_handler)
|
| 157 |
+
extra_layers.append(building_layer)
|
| 158 |
+
|
| 159 |
+
|
| 160 |
+
|
| 161 |
+
|
| 162 |
+
print('rendering map, number of extra layers',len(extra_layers))
|
| 163 |
+
|
| 164 |
+
ipyleaflet.Map.element(
|
| 165 |
+
zoom=zoom.value,
|
| 166 |
+
on_zoom=zoom.set,
|
| 167 |
+
on_bounds=bounds.set,
|
| 168 |
+
center=center.value,
|
| 169 |
+
on_center=center.set,
|
| 170 |
+
scroll_wheel_zoom=True,
|
| 171 |
+
dragging=True,
|
| 172 |
+
double_click_zoom=True,
|
| 173 |
+
touch_zoom=True,
|
| 174 |
+
box_zoom=True,
|
| 175 |
+
keyboard=True,
|
| 176 |
+
layers=[ipyleaflet.TileLayer.element(url=base_map.build_url())]+extra_layers,
|
| 177 |
+
)
|
| 178 |
+
|
| 179 |
+
@solara.component
|
| 180 |
+
def DialWidget(desc, value, max_value=10000):
|
| 181 |
+
if max_value == 0:
|
| 182 |
+
max_value = 10000
|
| 183 |
+
fig = Figure(tight_layout=True,dpi=30,frameon=False)
|
| 184 |
+
fig.set_size_inches(1.5,1)
|
| 185 |
+
ax = fig.subplots()
|
| 186 |
+
ax.axis('equal')
|
| 187 |
+
ax.axis('off')
|
| 188 |
+
|
| 189 |
+
ax.set_xticks([])
|
| 190 |
+
ax.set_yticks([])
|
| 191 |
+
|
| 192 |
+
t = np.linspace(0, math.pi, 100)
|
| 193 |
+
|
| 194 |
+
cos = np.cos(t)
|
| 195 |
+
sin = np.sin(t)
|
| 196 |
+
|
| 197 |
+
ax.plot(cos,sin, linewidth=2)
|
| 198 |
+
value_t = math.pi * (1 - (value / max_value))
|
| 199 |
+
|
| 200 |
+
fill_color = 'red'
|
| 201 |
+
if value_t > math.pi / 2 :
|
| 202 |
+
x1 = np.linspace(-1,np.cos(value_t),100)
|
| 203 |
+
y1 = np.sqrt(1 - x1**2)
|
| 204 |
+
ax.fill_between(x1,y1,color=fill_color)
|
| 205 |
+
x1 = np.linspace(np.cos(value_t),0,100)
|
| 206 |
+
y1 = np.tan(value_t) * x1
|
| 207 |
+
ax.fill_between(x1,y1,color=fill_color)
|
| 208 |
+
else:
|
| 209 |
+
x = np.linspace(-1, np.cos(value_t),100)
|
| 210 |
+
y1 = np.sqrt(1-x**2)
|
| 211 |
+
y2a = np.zeros(100)
|
| 212 |
+
y2b = np.tan(value_t) * x
|
| 213 |
+
y2 = np.maximum(y2a,y2b)
|
| 214 |
+
ax.fill_between(x,y1,y2,color=fill_color)
|
| 215 |
+
|
| 216 |
+
ax.text(0,0.5,value,fontdict={'fontsize':20},verticalalignment="center",
|
| 217 |
+
horizontalalignment="center",color="black")
|
| 218 |
+
ax.set_xlim(-1.1,1.1)
|
| 219 |
+
ax.set_ylim(-0.2,1.2)
|
| 220 |
+
solara.FigureMatplotlib(fig)
|
| 221 |
+
|
| 222 |
+
@solara.component
|
| 223 |
+
def VisioningScenarioViewer():
|
| 224 |
+
|
| 225 |
+
# State variables
|
| 226 |
+
loading, set_loading = solara.use_state(-1)
|
| 227 |
+
error_message, set_error_message = solara.use_state("")
|
| 228 |
+
|
| 229 |
+
selected_layer, set_selected_layer = solara.use_state(None)
|
| 230 |
+
metrics, set_metrics = solara.use_state(metrics_template)
|
| 231 |
+
|
| 232 |
+
def load(file: FileInfo):
|
| 233 |
+
try:
|
| 234 |
+
json_string = file['data'].decode('utf-8')
|
| 235 |
+
json_hash = hash(json_string)
|
| 236 |
+
json_data = json.loads(json_string)
|
| 237 |
+
print(json_data.keys())
|
| 238 |
+
# Load into dataframes
|
| 239 |
+
if "features" in json_data.keys():
|
| 240 |
+
# Add zero metrics to building layer
|
| 241 |
+
if set(json_data['features'][0]['properties'].keys()) == initial_building_columns:
|
| 242 |
+
for metric in metrics.keys():
|
| 243 |
+
for i in range(len(json_data['features'])):
|
| 244 |
+
json_data['features'][i]['properties'][metric] = 0
|
| 245 |
+
|
| 246 |
+
df = gpd.GeoDataFrame.from_features(json_data['features'])
|
| 247 |
+
|
| 248 |
+
if set(df.columns) == building_columns:
|
| 249 |
+
building_df.set(df)
|
| 250 |
+
|
| 251 |
+
if set(df.columns) == landuse_columns:
|
| 252 |
+
landuse_geojson.set(json_data)
|
| 253 |
+
|
| 254 |
+
|
| 255 |
+
new_center = (df.geometry.centroid.y.mean(), df.geometry.centroid.x.mean())
|
| 256 |
+
center.set(new_center)
|
| 257 |
+
print(df.head())
|
| 258 |
+
print(df.columns)
|
| 259 |
+
else:
|
| 260 |
+
df = pd.read_json(json_string)
|
| 261 |
+
|
| 262 |
+
existing_hashes = [l['hash'] for l in layers.value]
|
| 263 |
+
if json_hash in existing_hashes:
|
| 264 |
+
set_error_message("File already uploaded")
|
| 265 |
+
return
|
| 266 |
+
else:
|
| 267 |
+
new_layer = {'fileinfo': file, 'df': df, 'hash': json_hash}
|
| 268 |
+
df_columns = set(df.columns)
|
| 269 |
+
if df_columns == building_columns:
|
| 270 |
+
new_layer['name'] = 'Buildings'
|
| 271 |
+
elif df_columns == landuse_columns:
|
| 272 |
+
new_layer['name'] = 'Landuse'
|
| 273 |
+
new_layer['geojson'] = ipyleaflet.GeoJSON(data=json_data,
|
| 274 |
+
style={'opacity': 1, 'dashArray': '9', 'fillOpacity': 0.5, 'weight': 1},
|
| 275 |
+
hover_style={'color': 'white', 'dashArray': '0', 'fillOpacity': 0.5},
|
| 276 |
+
style_callback=landuse_colors)
|
| 277 |
+
new_layer['geojson'].on_click(landuse_click_handler)
|
| 278 |
+
|
| 279 |
+
elif df_columns == intensity_columns:
|
| 280 |
+
locs = np.array([df.geometry.y.to_list(), df.geometry.x.to_list(), df.im.to_list()]).transpose().tolist()
|
| 281 |
+
new_layer['name'] = 'Intensity'
|
| 282 |
+
new_layer['geojson'] = ipyleaflet.Heatmap(locations=locs, radius=radius.value)
|
| 283 |
+
elif df_columns == household_columns:
|
| 284 |
+
new_layer['name'] = 'Household'
|
| 285 |
+
elif df_columns == individual_columns:
|
| 286 |
+
new_layer['name'] = 'Individual'
|
| 287 |
+
elif df_columns == vulnerabillity_columns:
|
| 288 |
+
new_layer['name'] = 'Vulnerability'
|
| 289 |
+
|
| 290 |
+
layers.set(layers.value + [new_layer])
|
| 291 |
+
print(file['name'], list(df.columns))
|
| 292 |
+
|
| 293 |
+
#set_run_allowed(is_ready_to_run())
|
| 294 |
+
set_error_message("")
|
| 295 |
+
except UnicodeDecodeError:
|
| 296 |
+
set_error_message(f'{file["name"]} is not a text file')
|
| 297 |
+
except Exception as e:
|
| 298 |
+
set_error_message(f'file: {file["name"]} Exception:{e}')
|
| 299 |
+
|
| 300 |
+
|
| 301 |
+
def progress(ratio):
|
| 302 |
+
print(f"loading {ratio}")
|
| 303 |
+
set_loading(ratio)
|
| 304 |
+
|
| 305 |
+
def is_ready_to_run():
|
| 306 |
+
layer_names = set([l['name'] for l in layers.value])
|
| 307 |
+
return set(all_layers) == layer_names
|
| 308 |
+
|
| 309 |
+
def get_building_layer():
|
| 310 |
+
dfs = {l['name']: l for l in layers.value}
|
| 311 |
+
if 'Buildings' in dfs.keys():
|
| 312 |
+
return dfs['Buildings']
|
| 313 |
+
else:
|
| 314 |
+
return None
|
| 315 |
+
|
| 316 |
+
def compute():
|
| 317 |
+
print("I'm computing")
|
| 318 |
+
dfs = {l['name']: l['df'] for l in layers.value}
|
| 319 |
+
print(dfs.keys())
|
| 320 |
+
|
| 321 |
+
if is_ready_to_run():
|
| 322 |
+
metrics, df_metrics = compute_flood(dfs['Buildings'],
|
| 323 |
+
dfs['Household'],
|
| 324 |
+
dfs['Individual'],
|
| 325 |
+
dfs['Intensity'],
|
| 326 |
+
dfs['Vulnerability'],
|
| 327 |
+
"flood")
|
| 328 |
+
print(metrics)
|
| 329 |
+
set_metrics(metrics['metrics'])
|
| 330 |
+
|
| 331 |
+
updated_df = building_df.value
|
| 332 |
+
for metric in df_metrics.keys():
|
| 333 |
+
updated_df[metric] = list(df_metrics[metric][metric])
|
| 334 |
+
building_df.set(updated_df)
|
| 335 |
+
|
| 336 |
+
#building_layer['geodata'] = ipyleaflet.GeoData(geo_dataframe=building_df,
|
| 337 |
+
# hover_style={'color': 'red', 'dashArray': '0', 'fillOpacity': 0.5},
|
| 338 |
+
# style_callback=building_colors)
|
| 339 |
+
#layers.set(layers)
|
| 340 |
+
|
| 341 |
+
with solara.Row():
|
| 342 |
+
solara.FileDrop(label="Drop layers", on_total_progress=progress, on_file=load,lazy=False)
|
| 343 |
+
solara.Info(f'Uploading {loading}%')
|
| 344 |
+
if error_message != "":
|
| 345 |
+
solara.Error(error_message)
|
| 346 |
+
solara.Button(label="Compute", on_click=compute, outlined=True)
|
| 347 |
+
|
| 348 |
+
building_layer = get_building_layer()
|
| 349 |
+
for metric in metrics.keys():
|
| 350 |
+
if building_df.value is not None and bounds is not None:
|
| 351 |
+
((ymin,xmin),(ymax,xmax)) = bounds.value
|
| 352 |
+
value = int(building_df.value.cx[xmin:xmax,ymin:ymax][metric].sum())
|
| 353 |
+
else:
|
| 354 |
+
value = 0
|
| 355 |
+
DialWidget(metric, value, max_value=10000)
|
| 356 |
+
#solara.Info(label=f'{metric}: {value}', dense=True)
|
| 357 |
+
|
| 358 |
+
with solara.Columns([80, 20]):
|
| 359 |
+
MapComponent()
|
| 360 |
+
solara.DataFrame(df=clicked_df.value, scrollable=True)
|
| 361 |
+
|
| 362 |
+
with solara.Card("Dataframes"):
|
| 363 |
+
solara.ToggleButtonsSingle(selected_layer, all_layers, on_value=set_selected_layer)
|
| 364 |
+
solara.Markdown(f"**Selected**: {selected_layer}")
|
| 365 |
+
found_layer = None
|
| 366 |
+
for layer in layers.value:
|
| 367 |
+
if layer['name'] == selected_layer:
|
| 368 |
+
found_layer = layer
|
| 369 |
+
break
|
| 370 |
+
|
| 371 |
+
if found_layer:
|
| 372 |
+
df = found_layer['df']
|
| 373 |
+
|
| 374 |
+
# Generate new dataframe
|
| 375 |
+
if 'geometry' in list(df.columns):
|
| 376 |
+
df_new = gpd.GeoDataFrame(df)
|
| 377 |
+
else:
|
| 378 |
+
df_new = pd.DataFrame(df)
|
| 379 |
+
|
| 380 |
+
# Filter geopandas
|
| 381 |
+
if 'geometry' in list(df.columns):
|
| 382 |
+
((ymin,xmin),(ymax,xmax)) = bounds.value
|
| 383 |
+
df_filtered = df_new.cx[xmin:xmax,ymin:ymax]
|
| 384 |
+
if selected_layer == 'Intensity':
|
| 385 |
+
solara.DataFrame(df=pd.DataFrame(df_filtered))
|
| 386 |
+
else:
|
| 387 |
+
solara.DataFrame(df=pd.DataFrame(df_filtered.drop(columns='geometry')))
|
| 388 |
+
else:
|
| 389 |
+
solara.DataFrame(df=df_new)
|
| 390 |
+
else:
|
| 391 |
+
solara.Info(f'No data uploaded yet for layer: {selected_layer}')
|
| 392 |
+
@solara.component
|
| 393 |
+
def Page():
|
| 394 |
+
VisioningScenarioViewer()
|
src/gui/engine.py
ADDED
|
@@ -0,0 +1,286 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#%%
|
| 2 |
+
import warnings
|
| 3 |
+
import json
|
| 4 |
+
import sys
|
| 5 |
+
import argparse
|
| 6 |
+
import io
|
| 7 |
+
import os
|
| 8 |
+
import pandas as pd
|
| 9 |
+
import psycopg2
|
| 10 |
+
import geopandas
|
| 11 |
+
import numpy as np
|
| 12 |
+
from scipy.stats import norm
|
| 13 |
+
from scipy.interpolate import interp1d
|
| 14 |
+
|
| 15 |
+
def compute_flood(gdf_buildings, df_household, df_individual,gdf_intensity, df_flood, hazard_type):
|
| 16 |
+
|
| 17 |
+
column_names = {'zoneID':'zoneid','bldID':'bldid','nHouse':'nhouse',
|
| 18 |
+
'specialFac':'specialfac','expStr':'expstr','repValue':'repvalue',
|
| 19 |
+
'xCoord':'xcoord','yCoord':'ycoord','hhID':'hhid','nInd':'nind',
|
| 20 |
+
'CommFacID':'commfacid','indivId':'individ','eduAttStat':'eduattstat',
|
| 21 |
+
'indivFacID':'indivfacid','VALUE':'im'}
|
| 22 |
+
|
| 23 |
+
gdf_buildings = gdf_buildings.rename(columns=column_names)
|
| 24 |
+
df_household = df_household.rename(columns=column_names)
|
| 25 |
+
df_individual = df_individual.rename(columns=column_names)
|
| 26 |
+
gdf_intensity = gdf_intensity.rename(columns=column_names)
|
| 27 |
+
|
| 28 |
+
# Damage States
|
| 29 |
+
DS_NO = 1
|
| 30 |
+
DS_SLIGHT = 2
|
| 31 |
+
DS_MODERATE = 3
|
| 32 |
+
DS_EXTENSIZE = 4
|
| 33 |
+
DS_COLLAPSED = 5
|
| 34 |
+
|
| 35 |
+
# Hazard Types
|
| 36 |
+
HAZARD_EARTHQUAKE = "earthquake"
|
| 37 |
+
HAZARD_FLOOD = "flood"
|
| 38 |
+
HAZARD_DEBRIS = "debris"
|
| 39 |
+
|
| 40 |
+
policies = []
|
| 41 |
+
threshold = 1
|
| 42 |
+
threshold_flood = 0.2
|
| 43 |
+
threshold_flood_distance = 10
|
| 44 |
+
epsg = 3857
|
| 45 |
+
#epsg = 21037 # Arc 1960 / UTM zone 37S
|
| 46 |
+
|
| 47 |
+
# Replace strange TypeX LRS with RCi
|
| 48 |
+
df_flood['expstr'] = df_flood['expstr'].str.replace('Type[0-9]+','RCi',regex=True)
|
| 49 |
+
|
| 50 |
+
number_of_unique_buildings = len(pd.unique(gdf_buildings['bldid']))
|
| 51 |
+
print('number of unique building', number_of_unique_buildings)
|
| 52 |
+
print('number of records in building layer ', len(gdf_buildings['bldid']))
|
| 53 |
+
|
| 54 |
+
# Convert both to the same target coordinate system
|
| 55 |
+
gdf_buildings = gdf_buildings.set_crs("EPSG:4326",allow_override=True)
|
| 56 |
+
gdf_intensity = gdf_intensity.set_crs("EPSG:4326",allow_override=True)
|
| 57 |
+
|
| 58 |
+
gdf_buildings = gdf_buildings.to_crs(f"EPSG:{epsg}")
|
| 59 |
+
gdf_intensity = gdf_intensity.to_crs(f"EPSG:{epsg}")
|
| 60 |
+
|
| 61 |
+
print(gdf_buildings.head())
|
| 62 |
+
print(gdf_intensity.head())
|
| 63 |
+
#%%
|
| 64 |
+
gdf_building_intensity = geopandas.sjoin_nearest(gdf_buildings,gdf_intensity,
|
| 65 |
+
how='left', rsuffix='intensity',distance_col='distance')
|
| 66 |
+
#%%
|
| 67 |
+
gdf_building_intensity = gdf_building_intensity.drop_duplicates(subset=['bldid'], keep='first')
|
| 68 |
+
# %%
|
| 69 |
+
# TODO: Check if the logic makes sense
|
| 70 |
+
if hazard_type == HAZARD_FLOOD:
|
| 71 |
+
away_from_flood = gdf_building_intensity['distance'] > threshold_flood_distance
|
| 72 |
+
print('threshold_flood_distance',threshold_flood_distance)
|
| 73 |
+
print('number of distant buildings', len(gdf_building_intensity.loc[away_from_flood, 'im']))
|
| 74 |
+
gdf_building_intensity.loc[away_from_flood, 'im'] = 0
|
| 75 |
+
# %%
|
| 76 |
+
gdf_building_intensity[['material','code_level','storeys','occupancy']] = \
|
| 77 |
+
gdf_building_intensity['expstr'].str.split('+',expand=True)
|
| 78 |
+
gdf_building_intensity['height'] = gdf_building_intensity['storeys'].str.extract(r'([0-9]+)s').astype('int')
|
| 79 |
+
# %%
|
| 80 |
+
lr = (gdf_building_intensity['height'] <= 4)
|
| 81 |
+
mr = (gdf_building_intensity['height'] >= 5) & (gdf_building_intensity['height'] <= 8)
|
| 82 |
+
hr = (gdf_building_intensity['height'] >= 9)
|
| 83 |
+
gdf_building_intensity.loc[lr, 'height_level'] = 'LR'
|
| 84 |
+
gdf_building_intensity.loc[mr, 'height_level'] = 'MR'
|
| 85 |
+
gdf_building_intensity.loc[hr, 'height_level'] = 'HR'
|
| 86 |
+
# %%
|
| 87 |
+
gdf_building_intensity['vulnstreq'] = \
|
| 88 |
+
gdf_building_intensity[['material','code_level','height_level']] \
|
| 89 |
+
.agg('+'.join,axis=1)
|
| 90 |
+
# %%
|
| 91 |
+
if hazard_type == HAZARD_EARTHQUAKE:
|
| 92 |
+
bld_eq = gdf_building_intensity.merge(df_eq, on='vulnstreq', how='left')
|
| 93 |
+
nulls = bld_eq['muds1_g'].isna()
|
| 94 |
+
bld_eq.loc[nulls, ['muds1_g','muds2_g','muds3_g','muds4_g']] = [0.048,0.203,0.313,0.314]
|
| 95 |
+
bld_eq.loc[nulls, ['sigmads1','sigmads2','sigmads3','sigmads4']] = [0.301,0.276,0.252,0.253]
|
| 96 |
+
bld_eq['logim'] = np.log(bld_eq['im_x']/9.81)
|
| 97 |
+
for m in ['muds1_g','muds2_g','muds3_g','muds4_g']:
|
| 98 |
+
bld_eq[m] = np.log(bld_eq[m])
|
| 99 |
+
|
| 100 |
+
for i in [1,2,3,4]:
|
| 101 |
+
bld_eq[f'prob_ds{i}'] = norm.cdf(bld_eq['logim'],bld_eq[f'muds{i}_g'],bld_eq[f'sigmads{i}'])
|
| 102 |
+
bld_eq[['prob_ds0','prob_ds5']] = [1,0]
|
| 103 |
+
for i in [1,2,3,4,5]:
|
| 104 |
+
bld_eq[f'ds_{i}'] = np.abs(bld_eq[f'prob_ds{i-1}'] - bld_eq[f'prob_ds{i}'])
|
| 105 |
+
df_ds = bld_eq[['ds_1','ds_2','ds_3','ds_4','ds_5']]
|
| 106 |
+
bld_eq['eq_ds'] = df_ds.idxmax(axis='columns').str.extract(r'ds_([0-9]+)').astype('int')
|
| 107 |
+
|
| 108 |
+
# Create a simplified building-hazard relation
|
| 109 |
+
bld_hazard = bld_eq[['bldid','occupancy','eq_ds']]
|
| 110 |
+
bld_hazard = bld_hazard.rename(columns={'eq_ds':'ds'})
|
| 111 |
+
|
| 112 |
+
ds_str = {1: 'No Damage',2:'Low',3:'Medium',4:'High',5:'Collapsed'}
|
| 113 |
+
|
| 114 |
+
elif hazard_type == HAZARD_FLOOD:
|
| 115 |
+
bld_flood = gdf_building_intensity.merge(df_flood, on='expstr', how='left')
|
| 116 |
+
x = np.array([0,0.5,1,1.5,2,3,4,5,6])
|
| 117 |
+
y = bld_flood[['hw0','hw0_5','hw1','hw1_5','hw2','hw3','hw4','hw5','hw6']].to_numpy()
|
| 118 |
+
xnew = bld_flood['im'].to_numpy()
|
| 119 |
+
flood_mapping = interp1d(x,y,axis=1,kind='linear',bounds_error=False, fill_value=(0,1))
|
| 120 |
+
# TODO: find another way for vectorized interpolate
|
| 121 |
+
bld_flood['fl_prob'] = np.diag(flood_mapping(xnew))
|
| 122 |
+
bld_flood['fl_ds'] = 0
|
| 123 |
+
bld_flood.loc[bld_flood['fl_prob'] > threshold_flood,'fl_ds'] = 1
|
| 124 |
+
|
| 125 |
+
# Create a simplified building-hazard relation
|
| 126 |
+
bld_hazard = bld_flood[['bldid','occupancy','fl_ds']]
|
| 127 |
+
bld_hazard = bld_hazard.rename(columns={'fl_ds':'ds'})
|
| 128 |
+
|
| 129 |
+
ds_str = {0: 'No Damage',1:'Flooded'}
|
| 130 |
+
# %%
|
| 131 |
+
bld_hazard['occupancy'] = pd.Categorical(bld_hazard['occupancy'])
|
| 132 |
+
for key, value in ds_str.items():
|
| 133 |
+
bld_hazard.loc[bld_hazard['ds'] == key,'damage_level'] = value
|
| 134 |
+
bld_hazard['damage_level'] = pd.Categorical(bld_hazard['damage_level'], list(ds_str.values()))
|
| 135 |
+
|
| 136 |
+
#%% Find the damage state of the building that the household is in
|
| 137 |
+
df_household_bld = df_household.merge(bld_hazard[['bldid','ds']], on='bldid', how='left',validate='many_to_one')
|
| 138 |
+
|
| 139 |
+
#%% find the damage state of the hospital that the household is associated with
|
| 140 |
+
df_hospitals = df_household.merge(bld_hazard[['bldid','damage_level', 'ds']],
|
| 141 |
+
how='left', left_on='commfacid', right_on='bldid', suffixes=['','_comm'],
|
| 142 |
+
validate='many_to_one')
|
| 143 |
+
|
| 144 |
+
#%%
|
| 145 |
+
df_individual_occupancy = df_individual.merge(bld_hazard[['bldid','occupancy','damage_level', 'ds']],
|
| 146 |
+
how='inner',left_on='indivfacid',right_on='bldid',
|
| 147 |
+
suffixes=['_l','_r'],validate='many_to_one')
|
| 148 |
+
|
| 149 |
+
#%%
|
| 150 |
+
df_workers = df_individual_occupancy.query('occupancy in ["Com","ResCom","Ind"]')
|
| 151 |
+
|
| 152 |
+
#%%
|
| 153 |
+
df_students = df_individual_occupancy.query('occupancy in ["Edu"]')
|
| 154 |
+
|
| 155 |
+
#%%
|
| 156 |
+
df_indiv_hosp = df_individual.merge(df_hospitals[['hhid','ds','bldid']],
|
| 157 |
+
how='left', on='hhid', validate='many_to_one')
|
| 158 |
+
#%%
|
| 159 |
+
|
| 160 |
+
# get the ds of household that individual lives in
|
| 161 |
+
df_indiv_household = df_individual[['hhid','individ']].merge(df_household_bld[['hhid','ds']])
|
| 162 |
+
|
| 163 |
+
df_displaced_indiv = df_indiv_hosp.rename(columns={'ds':'ds_hospital'})\
|
| 164 |
+
.merge(df_workers[['individ','ds']].rename(columns={'ds':'ds_workplace'}),on='individ', how='left')\
|
| 165 |
+
.merge(df_students[['individ','ds']].rename(columns={'ds':'ds_school'}), on='individ', how='left')\
|
| 166 |
+
.merge(df_indiv_household[['individ','ds']].rename(columns={'ds':'ds_household'}), on='individ',how='left')
|
| 167 |
+
|
| 168 |
+
# %%
|
| 169 |
+
#%%
|
| 170 |
+
if hazard_type == HAZARD_EARTHQUAKE:
|
| 171 |
+
# Effect of policies on thresholds
|
| 172 |
+
# First get the global threshold
|
| 173 |
+
thresholds = {f'metric{id}': threshold for id in range(8)}
|
| 174 |
+
# Policy-1: Loans for reconstruction for minor to moderate damages
|
| 175 |
+
# Changes: Damage state thresholds for “displacement”
|
| 176 |
+
# Increase thresholds from “slight to moderate” as fewer people will be displaced.
|
| 177 |
+
if 21 in policies and thresholds['metric7'] == DS_NO:
|
| 178 |
+
thresholds['metric7'] = DS_SLIGHT
|
| 179 |
+
|
| 180 |
+
# Policy-3: Cat-bond agreement for education and health facilities
|
| 181 |
+
# Changes: Damage state thresholds for “loss of access to hospitals” and “loss of access to schools”
|
| 182 |
+
# Increase thresholds from “slight to moderate” as fewer people will be displaced.
|
| 183 |
+
if 23 in policies and thresholds['metric3'] == DS_NO:
|
| 184 |
+
thresholds['metric3'] = DS_SLIGHT
|
| 185 |
+
if 23 in policies and thresholds['metric2'] == DS_NO:
|
| 186 |
+
thresholds['metric2'] = DS_SLIGHT
|
| 187 |
+
|
| 188 |
+
# Policy-2: Knowledge sharing about DRR in public and private schools
|
| 189 |
+
# Changes: Damage state thresholds for “loss of school access”
|
| 190 |
+
# Increase thresholds loss of school access to beyond current scale. So that the impact will be downgraded to “0”.
|
| 191 |
+
if 22 in policies:
|
| 192 |
+
thresholds['metric2'] = DS_COLLAPSED
|
| 193 |
+
elif hazard_type == HAZARD_FLOOD:
|
| 194 |
+
# For flood, there are only two states: 0 or 1.
|
| 195 |
+
# So threshold is set to 0.
|
| 196 |
+
thresholds = {f'metric{id}': 0 for id in range(8)}
|
| 197 |
+
|
| 198 |
+
#%% metric 1 number of unemployed workers in each building
|
| 199 |
+
print(df_workers[df_workers['bldid'] == 28])
|
| 200 |
+
df_workers_per_building = df_workers[df_workers['ds'] > thresholds['metric1']].groupby('bldid',as_index=False).agg({'individ':'count'})
|
| 201 |
+
|
| 202 |
+
df_metric1 = bld_hazard.merge(df_workers_per_building,how='left',left_on='bldid',right_on = 'bldid')[['bldid','individ']]
|
| 203 |
+
print(df_metric1[df_metric1['bldid'] == 28])
|
| 204 |
+
df_metric1.rename(columns={'individ':'metric1'}, inplace=True)
|
| 205 |
+
df_metric1['metric1'] = df_metric1['metric1'].fillna(0).astype(int)
|
| 206 |
+
|
| 207 |
+
#%% metric 2 number of students in each building with no access to schools
|
| 208 |
+
df_students_per_building = df_students[df_students['ds'] > thresholds['metric2']].groupby('bldid',as_index=False).agg({'individ':'count'})
|
| 209 |
+
df_metric2 = bld_hazard.merge(df_students_per_building,how='left',left_on='bldid',right_on = 'bldid')[['bldid','individ']]
|
| 210 |
+
df_metric2.rename(columns={'individ':'metric2'}, inplace=True)
|
| 211 |
+
df_metric2['metric2'] = df_metric2['metric2'].fillna(0).astype(int)
|
| 212 |
+
|
| 213 |
+
#%% metric 3 number of households in each building with no access to hospitals
|
| 214 |
+
df_hospitals_per_household = df_hospitals[df_hospitals['ds'] > thresholds['metric3']].groupby('bldid',as_index=False).agg({'hhid':'count'})
|
| 215 |
+
df_metric3 = bld_hazard.merge(df_hospitals_per_household,how='left',left_on='bldid',right_on='bldid')[['bldid','hhid']]
|
| 216 |
+
df_metric3.rename(columns={'hhid':'metric3'}, inplace=True)
|
| 217 |
+
df_metric3['metric3'] = df_metric3['metric3'].fillna(0).astype(int)
|
| 218 |
+
|
| 219 |
+
#%% metric 4 number of individuals in each building with no access to hospitals
|
| 220 |
+
df_hospitals_per_individual = df_hospitals[df_hospitals['ds'] > thresholds['metric4']].groupby('bldid',as_index=False).agg({'nind':'sum'})
|
| 221 |
+
df_metric4 = bld_hazard.merge(df_hospitals_per_individual,how='left',left_on='bldid',right_on='bldid')[['bldid','nind']]
|
| 222 |
+
df_metric4.rename(columns={'nind':'metric4'}, inplace=True)
|
| 223 |
+
df_metric4['metric4'] = df_metric4['metric4'].fillna(0).astype(int)
|
| 224 |
+
|
| 225 |
+
#%% metric 5 number of damaged households in each building
|
| 226 |
+
df_homeless_households = df_household_bld[df_household_bld['ds'] > thresholds['metric5']].groupby('bldid',as_index=False).agg({'hhid':'count'})
|
| 227 |
+
df_metric5 = bld_hazard.merge(df_homeless_households,how='left',left_on='bldid',right_on='bldid')[['bldid','hhid']]
|
| 228 |
+
df_metric5.rename(columns={'hhid':'metric5'}, inplace=True)
|
| 229 |
+
df_metric5['metric5'] = df_metric5['metric5'].fillna(0).astype(int)
|
| 230 |
+
|
| 231 |
+
#%% metric 6 number of homeless individuals in each building
|
| 232 |
+
df_homeless_individuals = df_household_bld[df_household_bld['ds'] > thresholds['metric6']].groupby('bldid',as_index=False).agg({'nind':'sum'})
|
| 233 |
+
df_metric6 = bld_hazard.merge(df_homeless_individuals,how='left',left_on='bldid',right_on='bldid')[['bldid','nind']]
|
| 234 |
+
df_metric6.rename(columns={'nind':'metric6'}, inplace=True)
|
| 235 |
+
df_metric6['metric6'] = df_metric6['metric6'].fillna(0).astype(int)
|
| 236 |
+
|
| 237 |
+
#%% metric 7 the number of displaced individuals in each building
|
| 238 |
+
# more info: an individual is displaced if at least of the conditions below hold
|
| 239 |
+
df_disp_per_bld = df_displaced_indiv[(df_displaced_indiv['ds_household'] > thresholds['metric6']) |
|
| 240 |
+
(df_displaced_indiv['ds_school'] > thresholds['metric7']) |
|
| 241 |
+
(df_displaced_indiv['ds_workplace'] > thresholds['metric7']) |
|
| 242 |
+
(df_displaced_indiv['ds_hospital'] > thresholds['metric7'])].groupby('bldid',as_index=False).agg({'individ':'count'})
|
| 243 |
+
df_metric7 = bld_hazard.merge(df_disp_per_bld,how='left',left_on='bldid',right_on='bldid')[['bldid','individ']]
|
| 244 |
+
df_metric7.rename(columns={'individ':'metric7'}, inplace=True)
|
| 245 |
+
df_metric7['metric7'] = df_metric7['metric7'].fillna(0).astype(int)
|
| 246 |
+
|
| 247 |
+
#%%
|
| 248 |
+
df_metrics = {'metric1': df_metric1,
|
| 249 |
+
'metric2': df_metric2,
|
| 250 |
+
'metric3': df_metric3,
|
| 251 |
+
'metric4': df_metric4,
|
| 252 |
+
'metric5': df_metric5,
|
| 253 |
+
'metric6': df_metric6,
|
| 254 |
+
'metric7': df_metric7}
|
| 255 |
+
|
| 256 |
+
#%%
|
| 257 |
+
number_of_workers = len(df_workers)
|
| 258 |
+
print('number of workers', number_of_workers)
|
| 259 |
+
|
| 260 |
+
number_of_students = len(df_students)
|
| 261 |
+
print('number of students', number_of_students)
|
| 262 |
+
|
| 263 |
+
number_of_households = len(df_household)
|
| 264 |
+
print('number of households', number_of_households)
|
| 265 |
+
|
| 266 |
+
number_of_individuals = len(df_individual)
|
| 267 |
+
print('number of individuals', number_of_individuals)
|
| 268 |
+
metrics = {"metric1": {"desc": "Number of workers unemployed", "value": 0, "max_value": number_of_workers},
|
| 269 |
+
"metric2": {"desc": "Number of children with no access to education", "value": 0, "max_value": number_of_students},
|
| 270 |
+
"metric3": {"desc": "Number of households with no access to hospital", "value": 0, "max_value": number_of_households},
|
| 271 |
+
"metric4": {"desc": "Number of individuals with no access to hospital", "value": 0, "max_value": number_of_individuals},
|
| 272 |
+
"metric5": {"desc": "Number of homeless households", "value": 0, "max_value": number_of_households},
|
| 273 |
+
"metric6": {"desc": "Number of homeless individuals", "value": 0, "max_value": number_of_individuals},
|
| 274 |
+
"metric7": {"desc": "Population displacement", "value": 0, "max_value": number_of_individuals},}
|
| 275 |
+
metrics["metric1"]["value"] = int(df_metric1['metric1'].sum())
|
| 276 |
+
metrics["metric2"]["value"] = int(df_metric2['metric2'].sum())
|
| 277 |
+
metrics["metric3"]["value"] = int(df_metric3['metric3'].sum())
|
| 278 |
+
metrics["metric4"]["value"] = int(df_metric4['metric4'].sum())
|
| 279 |
+
metrics["metric5"]["value"] = int(df_metric5['metric5'].sum())
|
| 280 |
+
metrics["metric6"]["value"] = int(df_metric6['metric6'].sum())
|
| 281 |
+
metrics["metric7"]["value"] = int(df_metric7['metric7'].sum())
|
| 282 |
+
|
| 283 |
+
result = {"metrics": metrics}
|
| 284 |
+
|
| 285 |
+
return result, df_metrics
|
| 286 |
+
|
src/gui/sandbox.py
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import solara
|
| 2 |
+
import ipyleaflet
|
| 3 |
+
from matplotlib.figure import Figure
|
| 4 |
+
import matplotlib.pyplot as plt
|
| 5 |
+
import math
|
| 6 |
+
import numpy as np
|
| 7 |
+
|
| 8 |
+
plt.switch_backend("agg")
|
| 9 |
+
|
| 10 |
+
@solara.component
|
| 11 |
+
def DialWidget(desc, value, max_value=10000):
|
| 12 |
+
if max_value == 0:
|
| 13 |
+
max_value = 10000
|
| 14 |
+
fig = Figure(tight_layout=True,dpi=30,frameon=False)
|
| 15 |
+
fig.set_size_inches(1.5,1)
|
| 16 |
+
ax = fig.subplots()
|
| 17 |
+
ax.axis('equal')
|
| 18 |
+
ax.axis('off')
|
| 19 |
+
|
| 20 |
+
ax.set_xticks([])
|
| 21 |
+
ax.set_yticks([])
|
| 22 |
+
|
| 23 |
+
t = np.linspace(0, math.pi, 100)
|
| 24 |
+
|
| 25 |
+
cos = np.cos(t)
|
| 26 |
+
sin = np.sin(t)
|
| 27 |
+
|
| 28 |
+
ax.plot(cos,sin, linewidth=2)
|
| 29 |
+
value_t = math.pi * (1 - (value / max_value))
|
| 30 |
+
|
| 31 |
+
fill_color = 'red'
|
| 32 |
+
if value_t > math.pi / 2 :
|
| 33 |
+
x1 = np.linspace(-1,np.cos(value_t),100)
|
| 34 |
+
y1 = np.sqrt(1 - x1**2)
|
| 35 |
+
ax.fill_between(x1,y1,color=fill_color)
|
| 36 |
+
x1 = np.linspace(np.cos(value_t),0,100)
|
| 37 |
+
y1 = np.tan(value_t) * x1
|
| 38 |
+
ax.fill_between(x1,y1,color=fill_color)
|
| 39 |
+
else:
|
| 40 |
+
x = np.linspace(-1, np.cos(value_t),100)
|
| 41 |
+
y1 = np.sqrt(1-x**2)
|
| 42 |
+
y2a = np.zeros(100)
|
| 43 |
+
y2b = np.tan(value_t) * x
|
| 44 |
+
y2 = np.maximum(y2a,y2b)
|
| 45 |
+
ax.fill_between(x,y1,y2,color=fill_color)
|
| 46 |
+
|
| 47 |
+
#ax.plot([0,0.9*np.cos(value_t)],[0, 0.9*np.sin(value_t)], linewidth=10)
|
| 48 |
+
#ax.scatter([0],[0],color='black',s=50)
|
| 49 |
+
#ax.text(-1.1,0,0,fontdict={'fontsize':14},verticalalignment="center",
|
| 50 |
+
# horizontalalignment="right",color="black")
|
| 51 |
+
#ax.text(1.1,0,max_value,fontdict={'fontsize':14},verticalalignment="center",
|
| 52 |
+
# horizontalalignment="left",color="black")
|
| 53 |
+
#ax.plot([-0.9,-1.1],[0,0],color="black")
|
| 54 |
+
#ax.plot([0.9,1.1],[0,0],color="black")
|
| 55 |
+
#ax.plot([0,0],[0.9,1.1],color="black")
|
| 56 |
+
horizontalalignment = "right" if value_t > math.pi/2 else "left"
|
| 57 |
+
#ax.text(1.1*np.cos(value_t),1.1*np.sin(value_t),value,fontdict={'fontsize':20},verticalalignment="center",
|
| 58 |
+
# horizontalalignment=horizontalalignment,color="black")
|
| 59 |
+
ax.text(0,0.5,value,fontdict={'fontsize':20},verticalalignment="center",
|
| 60 |
+
horizontalalignment="center",color="black")
|
| 61 |
+
ax.set_xlim(-1.1,1.1)
|
| 62 |
+
ax.set_ylim(-0.2,1.2)
|
| 63 |
+
#ax.text(0, -0.1, desc,fontdict={'fontsize':20},verticalalignment="center",
|
| 64 |
+
# horizontalalignment='center',color="white")
|
| 65 |
+
solara.FigureMatplotlib(fig)
|
| 66 |
+
|
| 67 |
+
metrics_template = {"metric1": {"desc": "Number of workers unemployed", "value": 0, "max_value": 0},
|
| 68 |
+
"metric2": {"desc": "Number of children with no access to education", "value": 0, "max_value": 0},
|
| 69 |
+
"metric3": {"desc": "Number of households with no access to hospital", "value": 0, "max_value": 0},
|
| 70 |
+
"metric4": {"desc": "Number of individuals with no access to hospital", "value": 0, "max_value": 0},
|
| 71 |
+
"metric5": {"desc": "Number of homeless households", "value": 0, "max_value": 0},
|
| 72 |
+
"metric6": {"desc": "Number of homeless individuals", "value": 0, "max_value": 0},
|
| 73 |
+
"metric7": {"desc": "Population displacement", "value": 0, "max_value": 0},}
|
| 74 |
+
|
| 75 |
+
metrics = solara.reactive(metrics_template)
|
| 76 |
+
|
| 77 |
+
def generate_metrics():
|
| 78 |
+
|
| 79 |
+
new_metrics = metrics_template.copy()
|
| 80 |
+
for m in new_metrics.keys():
|
| 81 |
+
max_value = np.random.randint(500, 10001)
|
| 82 |
+
value = int(np.random.random() * max_value)
|
| 83 |
+
new_metrics[m]["max_value"] = max_value
|
| 84 |
+
new_metrics[m]["value"] = value
|
| 85 |
+
|
| 86 |
+
metrics.set(new_metrics)
|
| 87 |
+
|
| 88 |
+
@solara.component
|
| 89 |
+
def Page():
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
|
| 93 |
+
|
| 94 |
+
solara.Button(label="Generate", on_click=generate_metrics)
|
| 95 |
+
|
| 96 |
+
for name, metric in metrics.value.items():
|
| 97 |
+
DialWidget(name, metric["value"], max_value=metric["max_value"])
|
| 98 |
+
|
| 99 |
+
|
| 100 |
+
Page()
|
src/tests/nairobi_business_buildings.geojson
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
src/tests/nairobi_business_household.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
src/tests/nairobi_business_individual.json
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:f46b176200fbf857d92ad7c9ec2ad65aab08fe5a09386245f1e8534f823a156f
|
| 3 |
+
size 53053675
|
src/tests/nairobi_business_landuse.geojson
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
src/tests/nairobi_flood_depth_50yr.geojson
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:447f10956468e02f7688923cc4094609c749dc039c10ec76984e377c803616b0
|
| 3 |
+
size 14778140
|
src/tests/nairobi_flood_vulnerability.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|