hkayabilisim commited on
Commit
124003a
·
1 Parent(s): 7c7f766

demo version

Browse files
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 :ref:`install` tomorrowcities package, you can import and use
8
- as follows:
 
9
 
10
  .. code-block:: python
11
 
12
  import tomorrowcities as tc
13
 
14
- dg = tc.DataGenerator(parameter_file='tests/Input_DistributionTables_20230614.xlsx',
15
- land_use_file='tests/polygonsTV50_v2b.zip')
 
 
 
 
 
 
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