hkayabilisim commited on
Commit
4408461
·
1 Parent(s): 768d678

Added two policies

Browse files
README.md CHANGED
@@ -1,4 +1,16 @@
1
- # Release Notes
 
 
 
 
 
 
 
2
 
3
- ## v0.0.1
4
- *
 
 
 
 
 
 
1
+ ---
2
+ title: App Engine
3
+ emoji: 🏆
4
+ colorFrom: purple
5
+ colorTo: blue
6
+ sdk: docker
7
+ pinned: false
8
+ ---
9
 
10
+ ## What is New?
11
+
12
+ ### Excel support:
13
+ You can upload Excel files containing your tabular data such as individual, household, fragility or vulnerability data. However, processing excel files is very slower than processing JSON files so I definitely suggest working with JSON files. You can convert your Excel files via panda framework. The platform also does not try to convert the coordinates even if there is any in the Excel file because there is no way to know which columns represent the coordinates or coordinate reference systems without metadata. So Excel spreadsheets should be used to provide non-geo related tabular data. So please use them only for data not containing any geo-specific information.
14
+
15
+ ### Dar Es Salaam Study
16
+ [Sample Dataset](https://drive.google.com/file/d/1BGPZQ2IKJHY9ExOCCHcNNrCTioYZ8D1y/view?usp=sharing) now contains some visioning scenario for Dar Es Salaam case.
pyproject.toml CHANGED
@@ -17,7 +17,8 @@ dependencies = [
17
  "psycopg2-binary",
18
  "scipy",
19
  "pandas",
20
- "networkx"
 
21
  ]
22
 
23
  [tool.hatch.version]
 
17
  "psycopg2-binary",
18
  "scipy",
19
  "pandas",
20
+ "networkx",
21
+ "openpyxl"
22
  ]
23
 
24
  [tool.hatch.version]
tomorrowcities/backend/engine.py CHANGED
@@ -1,13 +1,7 @@
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
@@ -36,7 +30,7 @@ def compute_power_infra(nodes,edges,intensity,fragility):
36
  for _, edge in edges.iterrows():
37
  G_power.add_edge(*(edge.from_node, edge.to_node))
38
 
39
- nodes = geopandas.sjoin_nearest(nodes,intensity,
40
  how='left', rsuffix='intensity',distance_col='distance')
41
 
42
  nodes = nodes.merge(eq_vuln, how='left',left_on='eq_vuln',right_on='vuln_string')
@@ -55,14 +49,14 @@ def compute_power_infra(nodes,edges,intensity,fragility):
55
  for i in [1,2,3,4,5]:
56
  nodes[f'ds_{i}'] = np.abs(nodes[f'prob_ds{i-1}'] - nodes[f'prob_ds{i}'])
57
  df_ds = nodes[['ds_1','ds_2','ds_3','ds_4','ds_5']]
58
- nodes['eq_ds'] = df_ds.idxmax(axis='columns').str.extract(r'ds_([0-9]+)').astype('int')
59
 
60
  # Damage State Codes
61
- DS_NO = 1
62
- DS_SLIGHT = 2
63
- DS_MODERATE = 3
64
- DS_EXTENSIVE = 4
65
- DS_COMPLETE = 5
66
 
67
  # If a damage state is above this threshold (excluding),
68
  # we consider the associated node as dead.
@@ -131,8 +125,7 @@ def compute_power_infra(nodes,edges,intensity,fragility):
131
 
132
  return nodes['eq_ds'], nodes['is_damaged'], nodes['is_operational']
133
 
134
-
135
- def compute(gdf_buildings, df_household, df_individual,gdf_intensity, df_hazard, hazard_type):
136
 
137
  column_names = {'zoneID':'zoneid','bldID':'bldid','nHouse':'nhouse',
138
  'specialFac':'specialfac','expStr':'expstr','repValue':'repvalue',
@@ -140,30 +133,28 @@ def compute(gdf_buildings, df_household, df_individual,gdf_intensity, df_hazard,
140
  'CommFacID':'commfacid','indivId':'individ','eduAttStat':'eduattstat',
141
  'indivFacID':'indivfacid','VALUE':'im'}
142
 
143
- print(hazard_type, df_hazard.head())
 
144
  gdf_buildings = gdf_buildings.rename(columns=column_names)
145
  df_household = df_household.rename(columns=column_names)
146
  df_individual = df_individual.rename(columns=column_names)
147
  gdf_intensity = gdf_intensity.rename(columns=column_names)
148
 
149
  # Damage States
150
- DS_NO = 1
151
- DS_SLIGHT = 2
152
- DS_MODERATE = 3
153
- DS_EXTENSIZE = 4
154
- DS_COLLAPSED = 5
155
 
156
  # Hazard Types
157
  HAZARD_EARTHQUAKE = "earthquake"
158
  HAZARD_FLOOD = "flood"
159
  HAZARD_DEBRIS = "debris"
160
 
161
- policies = []
162
- threshold = 1
163
  threshold_flood = 0.2
164
  threshold_flood_distance = 10
165
  epsg = 3857
166
- #epsg = 21037 # Arc 1960 / UTM zone 37S
167
 
168
  # Replace strange TypeX LRS with RCi
169
  print('deneme',df_hazard.columns)
@@ -172,42 +163,40 @@ def compute(gdf_buildings, df_household, df_individual,gdf_intensity, df_hazard,
172
  number_of_unique_buildings = len(pd.unique(gdf_buildings['bldid']))
173
  print('number of unique building', number_of_unique_buildings)
174
  print('number of records in building layer ', len(gdf_buildings['bldid']))
175
-
176
  # Convert both to the same target coordinate system
177
  gdf_buildings = gdf_buildings.set_crs("EPSG:4326",allow_override=True)
178
  gdf_intensity = gdf_intensity.set_crs("EPSG:4326",allow_override=True)
179
-
180
  gdf_buildings = gdf_buildings.to_crs(f"EPSG:{epsg}")
181
  gdf_intensity = gdf_intensity.to_crs(f"EPSG:{epsg}")
182
 
183
- #%%
184
- gdf_building_intensity = geopandas.sjoin_nearest(gdf_buildings,gdf_intensity,
185
  how='left', rsuffix='intensity',distance_col='distance')
186
- #%%
187
  gdf_building_intensity = gdf_building_intensity.drop_duplicates(subset=['bldid'], keep='first')
188
- # %%
 
 
189
  # TODO: Check if the logic makes sense
190
  if hazard_type == HAZARD_FLOOD:
191
  away_from_flood = gdf_building_intensity['distance'] > threshold_flood_distance
192
  print('threshold_flood_distance',threshold_flood_distance)
193
  print('number of distant buildings', len(gdf_building_intensity.loc[away_from_flood, 'im']))
194
  gdf_building_intensity.loc[away_from_flood, 'im'] = 0
195
- # %%
196
  gdf_building_intensity[['material','code_level','storeys','occupancy']] = \
197
  gdf_building_intensity['expstr'].str.split('+',expand=True)
198
  gdf_building_intensity['height'] = gdf_building_intensity['storeys'].str.extract(r'([0-9]+)s').astype('int')
199
- # %%
200
  lr = (gdf_building_intensity['height'] <= 4)
201
  mr = (gdf_building_intensity['height'] >= 5) & (gdf_building_intensity['height'] <= 8)
202
  hr = (gdf_building_intensity['height'] >= 9)
203
  gdf_building_intensity.loc[lr, 'height_level'] = 'LR'
204
  gdf_building_intensity.loc[mr, 'height_level'] = 'MR'
205
  gdf_building_intensity.loc[hr, 'height_level'] = 'HR'
206
- # %% Earthquake uses simplified taxonomy
207
  gdf_building_intensity['vulnstreq'] = \
208
  gdf_building_intensity[['material','code_level','height_level']] \
209
  .agg('+'.join,axis=1)
210
- # %%
211
  if hazard_type == HAZARD_EARTHQUAKE:
212
  bld_eq = gdf_building_intensity.merge(df_hazard, left_on='vulnstreq',right_on='expstr', how='left')
213
  nulls = bld_eq['muds1_g'].isna()
@@ -223,13 +212,18 @@ def compute(gdf_buildings, df_household, df_individual,gdf_intensity, df_hazard,
223
  for i in [1,2,3,4,5]:
224
  bld_eq[f'ds_{i}'] = np.abs(bld_eq[f'prob_ds{i-1}'] - bld_eq[f'prob_ds{i}'])
225
  df_ds = bld_eq[['ds_1','ds_2','ds_3','ds_4','ds_5']]
226
- bld_eq['eq_ds'] = df_ds.idxmax(axis='columns').str.extract(r'ds_([0-9]+)').astype('int')
227
 
 
 
 
 
 
228
  # Create a simplified building-hazard relation
229
- bld_hazard = bld_eq[['bldid','occupancy','eq_ds']]
230
  bld_hazard = bld_hazard.rename(columns={'eq_ds':'ds'})
231
 
232
- ds_str = {1: 'No Damage',2:'Low',3:'Medium',4:'High',5:'Collapsed'}
233
 
234
  elif hazard_type == HAZARD_FLOOD:
235
  bld_flood = gdf_building_intensity.merge(df_hazard, on='expstr', how='left')
@@ -241,128 +235,146 @@ def compute(gdf_buildings, df_household, df_individual,gdf_intensity, df_hazard,
241
  bld_flood['fl_prob'] = np.diag(flood_mapping(xnew))
242
  bld_flood['fl_ds'] = 0
243
  bld_flood.loc[bld_flood['fl_prob'] > threshold_flood,'fl_ds'] = 1
244
-
 
 
 
 
245
  # Create a simplified building-hazard relation
246
- bld_hazard = bld_flood[['bldid','occupancy','fl_ds']]
247
  bld_hazard = bld_hazard.rename(columns={'fl_ds':'ds'})
248
 
249
  ds_str = {0: 'No Damage',1:'Flooded'}
250
- # %%
251
- bld_hazard['occupancy'] = pd.Categorical(bld_hazard['occupancy'])
252
- for key, value in ds_str.items():
253
- bld_hazard.loc[bld_hazard['ds'] == key,'damage_level'] = value
254
- bld_hazard['damage_level'] = pd.Categorical(bld_hazard['damage_level'], list(ds_str.values()))
 
 
 
 
 
255
 
256
- #%% Find the damage state of the building that the household is in
257
  df_household_bld = df_household.merge(bld_hazard[['bldid','ds']], on='bldid', how='left',validate='many_to_one')
258
 
259
- #%% find the damage state of the hospital that the household is associated with
260
- df_hospitals = df_household.merge(bld_hazard[['bldid','damage_level', 'ds']],
261
  how='left', left_on='commfacid', right_on='bldid', suffixes=['','_comm'],
262
  validate='many_to_one')
263
 
264
- #%%
265
- df_individual_occupancy = df_individual.merge(bld_hazard[['bldid','occupancy','damage_level', 'ds']],
266
  how='inner',left_on='indivfacid',right_on='bldid',
267
  suffixes=['_l','_r'],validate='many_to_one')
268
 
269
- #%%
270
  df_workers = df_individual_occupancy.query('occupancy in ["Com","ResCom","Ind"]')
271
 
272
- #%%
273
  df_students = df_individual_occupancy.query('occupancy in ["Edu"]')
274
 
275
- #%%
276
- df_indiv_hosp = df_individual.merge(df_hospitals[['hhid','ds','bldid']],
277
- how='left', on='hhid', validate='many_to_one')
278
- #%%
279
 
280
  # get the ds of household that individual lives in
281
  df_indiv_household = df_individual[['hhid','individ']].merge(df_household_bld[['hhid','ds']])
282
 
 
283
  df_displaced_indiv = df_indiv_hosp.rename(columns={'ds':'ds_hospital'})\
284
  .merge(df_workers[['individ','ds']].rename(columns={'ds':'ds_workplace'}),on='individ', how='left')\
285
  .merge(df_students[['individ','ds']].rename(columns={'ds':'ds_school'}), on='individ', how='left')\
286
- .merge(df_indiv_household[['individ','ds']].rename(columns={'ds':'ds_household'}), on='individ',how='left')
 
 
 
 
 
 
 
 
 
 
 
 
287
 
288
- # %%
289
- #%%
290
  if hazard_type == HAZARD_EARTHQUAKE:
291
- # Effect of policies on thresholds
292
- # First get the global threshold
293
- thresholds = {f'metric{id}': threshold for id in range(8)}
294
- # Policy-1: Loans for reconstruction for minor to moderate damages
295
- # Changes: Damage state thresholds for “displacement”
296
- # Increase thresholds from “slight to moderate” as fewer people will be displaced.
297
- if 21 in policies and thresholds['metric7'] == DS_NO:
298
- thresholds['metric7'] = DS_SLIGHT
299
-
300
- # Policy-3: Cat-bond agreement for education and health facilities
301
- # Changes: Damage state thresholds for “loss of access to hospitals” and “loss of access to schools”
302
- # Increase thresholds from “slight to moderate” as fewer people will be displaced.
303
- if 23 in policies and thresholds['metric3'] == DS_NO:
304
- thresholds['metric3'] = DS_SLIGHT
305
- if 23 in policies and thresholds['metric2'] == DS_NO:
306
- thresholds['metric2'] = DS_SLIGHT
307
-
308
- # Policy-2: Knowledge sharing about DRR in public and private schools
309
- # Changes: Damage state thresholds for “loss of school access”
310
- # Increase thresholds loss of school access to beyond current scale. So that the impact will be downgraded to “0”.
311
- if 22 in policies:
312
- thresholds['metric2'] = DS_COLLAPSED
313
- elif hazard_type == HAZARD_FLOOD:
314
- # For flood, there are only two states: 0 or 1.
315
- # So threshold is set to 0.
316
- thresholds = {f'metric{id}': 0 for id in range(8)}
317
 
318
- #%% metric 1 number of unemployed workers in each building
319
- df_workers_per_building = df_workers[df_workers['ds'] > thresholds['metric1']].groupby('bldid',as_index=False).agg({'individ':'count'})
320
-
321
  df_metric1 = bld_hazard.merge(df_workers_per_building,how='left',left_on='bldid',right_on = 'bldid')[['bldid','individ']]
322
  df_metric1.rename(columns={'individ':'metric1'}, inplace=True)
323
  df_metric1['metric1'] = df_metric1['metric1'].fillna(0).astype(int)
324
 
325
- #%% metric 2 number of students in each building with no access to schools
326
- df_students_per_building = df_students[df_students['ds'] > thresholds['metric2']].groupby('bldid',as_index=False).agg({'individ':'count'})
 
 
 
327
  df_metric2 = bld_hazard.merge(df_students_per_building,how='left',left_on='bldid',right_on = 'bldid')[['bldid','individ']]
328
  df_metric2.rename(columns={'individ':'metric2'}, inplace=True)
329
  df_metric2['metric2'] = df_metric2['metric2'].fillna(0).astype(int)
330
 
331
- #%% metric 3 number of households in each building with no access to hospitals
332
- df_hospitals_per_household = df_hospitals[df_hospitals['ds'] > thresholds['metric3']].groupby('bldid',as_index=False).agg({'hhid':'count'})
 
 
333
  df_metric3 = bld_hazard.merge(df_hospitals_per_household,how='left',left_on='bldid',right_on='bldid')[['bldid','hhid']]
334
  df_metric3.rename(columns={'hhid':'metric3'}, inplace=True)
335
  df_metric3['metric3'] = df_metric3['metric3'].fillna(0).astype(int)
336
 
337
- #%% metric 4 number of individuals in each building with no access to hospitals
338
- df_hospitals_per_individual = df_hospitals[df_hospitals['ds'] > thresholds['metric4']].groupby('bldid',as_index=False).agg({'nind':'sum'})
 
 
339
  df_metric4 = bld_hazard.merge(df_hospitals_per_individual,how='left',left_on='bldid',right_on='bldid')[['bldid','nind']]
340
  df_metric4.rename(columns={'nind':'metric4'}, inplace=True)
341
  df_metric4['metric4'] = df_metric4['metric4'].fillna(0).astype(int)
342
 
343
- #%% metric 5 number of damaged households in each building
344
- df_homeless_households = df_household_bld[df_household_bld['ds'] > thresholds['metric5']].groupby('bldid',as_index=False).agg({'hhid':'count'})
 
 
345
  df_metric5 = bld_hazard.merge(df_homeless_households,how='left',left_on='bldid',right_on='bldid')[['bldid','hhid']]
346
  df_metric5.rename(columns={'hhid':'metric5'}, inplace=True)
347
  df_metric5['metric5'] = df_metric5['metric5'].fillna(0).astype(int)
348
 
349
- #%% metric 6 number of homeless individuals in each building
350
- df_homeless_individuals = df_household_bld[df_household_bld['ds'] > thresholds['metric6']].groupby('bldid',as_index=False).agg({'nind':'sum'})
 
 
351
  df_metric6 = bld_hazard.merge(df_homeless_individuals,how='left',left_on='bldid',right_on='bldid')[['bldid','nind']]
352
  df_metric6.rename(columns={'nind':'metric6'}, inplace=True)
353
  df_metric6['metric6'] = df_metric6['metric6'].fillna(0).astype(int)
354
 
355
- #%% metric 7 the number of displaced individuals in each building
356
  # more info: an individual is displaced if at least of the conditions below hold
357
  df_disp_per_bld = df_displaced_indiv[(df_displaced_indiv['ds_household'] > thresholds['metric6']) |
358
- (df_displaced_indiv['ds_school'] > thresholds['metric7']) |
359
- (df_displaced_indiv['ds_workplace'] > thresholds['metric7']) |
360
- (df_displaced_indiv['ds_hospital'] > thresholds['metric7'])].groupby('bldid',as_index=False).agg({'individ':'count'})
 
 
 
361
  df_metric7 = bld_hazard.merge(df_disp_per_bld,how='left',left_on='bldid',right_on='bldid')[['bldid','individ']]
362
  df_metric7.rename(columns={'individ':'metric7'}, inplace=True)
363
  df_metric7['metric7'] = df_metric7['metric7'].fillna(0).astype(int)
364
 
365
- #%%
 
366
  df_metrics = {'metric1': df_metric1,
367
  'metric2': df_metric2,
368
  'metric3': df_metric3,
@@ -371,7 +383,7 @@ def compute(gdf_buildings, df_household, df_individual,gdf_intensity, df_hazard,
371
  'metric6': df_metric6,
372
  'metric7': df_metric7}
373
 
374
- #%%
375
  number_of_workers = len(df_workers)
376
  print('number of workers', number_of_workers)
377
 
@@ -387,7 +399,7 @@ def compute(gdf_buildings, df_household, df_individual,gdf_intensity, df_hazard,
387
  "metric2": {"desc": "Number of children with no access to education", "value": 0, "max_value": number_of_students},
388
  "metric3": {"desc": "Number of households with no access to hospital", "value": 0, "max_value": number_of_households},
389
  "metric4": {"desc": "Number of individuals with no access to hospital", "value": 0, "max_value": number_of_individuals},
390
- "metric5": {"desc": "Number of homeless households", "value": 0, "max_value": number_of_households},
391
  "metric6": {"desc": "Number of homeless individuals", "value": 0, "max_value": number_of_individuals},
392
  "metric7": {"desc": "Population displacement", "value": 0, "max_value": number_of_individuals},}
393
  metrics["metric1"]["value"] = int(df_metric1['metric1'].sum())
@@ -398,5 +410,4 @@ def compute(gdf_buildings, df_household, df_individual,gdf_intensity, df_hazard,
398
  metrics["metric6"]["value"] = int(df_metric6['metric6'].sum())
399
  metrics["metric7"]["value"] = int(df_metric7['metric7'].sum())
400
 
401
- return metrics, df_metrics, bld_hazard
402
-
 
 
 
 
 
 
 
1
  import os
2
+ os.environ['USE_PYGEOS'] = '0'
3
  import pandas as pd
4
+ import geopandas as gpd
 
5
  import numpy as np
6
  from scipy.stats import norm
7
  from scipy.interpolate import interp1d
 
30
  for _, edge in edges.iterrows():
31
  G_power.add_edge(*(edge.from_node, edge.to_node))
32
 
33
+ nodes = gpd.sjoin_nearest(nodes,intensity,
34
  how='left', rsuffix='intensity',distance_col='distance')
35
 
36
  nodes = nodes.merge(eq_vuln, how='left',left_on='eq_vuln',right_on='vuln_string')
 
49
  for i in [1,2,3,4,5]:
50
  nodes[f'ds_{i}'] = np.abs(nodes[f'prob_ds{i-1}'] - nodes[f'prob_ds{i}'])
51
  df_ds = nodes[['ds_1','ds_2','ds_3','ds_4','ds_5']]
52
+ nodes['eq_ds'] = df_ds.idxmax(axis='columns').str.extract(r'ds_([0-9]+)').astype('int') - 1
53
 
54
  # Damage State Codes
55
+ DS_NO = 0
56
+ DS_SLIGHT = 1
57
+ DS_MODERATE = 2
58
+ DS_EXTENSIVE = 3
59
+ DS_COMPLETE = 4
60
 
61
  # If a damage state is above this threshold (excluding),
62
  # we consider the associated node as dead.
 
125
 
126
  return nodes['eq_ds'], nodes['is_damaged'], nodes['is_operational']
127
 
128
+ def compute(gdf_landuse, gdf_buildings, df_household, df_individual,gdf_intensity, df_hazard, hazard_type, policies=[]):
 
129
 
130
  column_names = {'zoneID':'zoneid','bldID':'bldid','nHouse':'nhouse',
131
  'specialFac':'specialfac','expStr':'expstr','repValue':'repvalue',
 
133
  'CommFacID':'commfacid','indivId':'individ','eduAttStat':'eduattstat',
134
  'indivFacID':'indivfacid','VALUE':'im'}
135
 
136
+
137
+ gdf_landuse = gdf_landuse.rename(columns=column_names)
138
  gdf_buildings = gdf_buildings.rename(columns=column_names)
139
  df_household = df_household.rename(columns=column_names)
140
  df_individual = df_individual.rename(columns=column_names)
141
  gdf_intensity = gdf_intensity.rename(columns=column_names)
142
 
143
  # Damage States
144
+ DS_NO = 0
145
+ DS_SLIGHT = 1
146
+ DS_MODERATE = 2
147
+ DS_EXTENSIZE = 3
148
+ DS_COLLAPSED = 4
149
 
150
  # Hazard Types
151
  HAZARD_EARTHQUAKE = "earthquake"
152
  HAZARD_FLOOD = "flood"
153
  HAZARD_DEBRIS = "debris"
154
 
 
 
155
  threshold_flood = 0.2
156
  threshold_flood_distance = 10
157
  epsg = 3857
 
158
 
159
  # Replace strange TypeX LRS with RCi
160
  print('deneme',df_hazard.columns)
 
163
  number_of_unique_buildings = len(pd.unique(gdf_buildings['bldid']))
164
  print('number of unique building', number_of_unique_buildings)
165
  print('number of records in building layer ', len(gdf_buildings['bldid']))
166
+
167
  # Convert both to the same target coordinate system
168
  gdf_buildings = gdf_buildings.set_crs("EPSG:4326",allow_override=True)
169
  gdf_intensity = gdf_intensity.set_crs("EPSG:4326",allow_override=True)
170
+
171
  gdf_buildings = gdf_buildings.to_crs(f"EPSG:{epsg}")
172
  gdf_intensity = gdf_intensity.to_crs(f"EPSG:{epsg}")
173
 
174
+ gdf_building_intensity = gpd.sjoin_nearest(gdf_buildings,gdf_intensity,
 
175
  how='left', rsuffix='intensity',distance_col='distance')
 
176
  gdf_building_intensity = gdf_building_intensity.drop_duplicates(subset=['bldid'], keep='first')
177
+
178
+ gdf_building_intensity = gdf_building_intensity.merge(gdf_landuse[['zoneid','avgincome']],on='zoneid',how='left')
179
+
180
  # TODO: Check if the logic makes sense
181
  if hazard_type == HAZARD_FLOOD:
182
  away_from_flood = gdf_building_intensity['distance'] > threshold_flood_distance
183
  print('threshold_flood_distance',threshold_flood_distance)
184
  print('number of distant buildings', len(gdf_building_intensity.loc[away_from_flood, 'im']))
185
  gdf_building_intensity.loc[away_from_flood, 'im'] = 0
 
186
  gdf_building_intensity[['material','code_level','storeys','occupancy']] = \
187
  gdf_building_intensity['expstr'].str.split('+',expand=True)
188
  gdf_building_intensity['height'] = gdf_building_intensity['storeys'].str.extract(r'([0-9]+)s').astype('int')
 
189
  lr = (gdf_building_intensity['height'] <= 4)
190
  mr = (gdf_building_intensity['height'] >= 5) & (gdf_building_intensity['height'] <= 8)
191
  hr = (gdf_building_intensity['height'] >= 9)
192
  gdf_building_intensity.loc[lr, 'height_level'] = 'LR'
193
  gdf_building_intensity.loc[mr, 'height_level'] = 'MR'
194
  gdf_building_intensity.loc[hr, 'height_level'] = 'HR'
195
+ # Earthquake uses simplified taxonomy
196
  gdf_building_intensity['vulnstreq'] = \
197
  gdf_building_intensity[['material','code_level','height_level']] \
198
  .agg('+'.join,axis=1)
199
+ #
200
  if hazard_type == HAZARD_EARTHQUAKE:
201
  bld_eq = gdf_building_intensity.merge(df_hazard, left_on='vulnstreq',right_on='expstr', how='left')
202
  nulls = bld_eq['muds1_g'].isna()
 
212
  for i in [1,2,3,4,5]:
213
  bld_eq[f'ds_{i}'] = np.abs(bld_eq[f'prob_ds{i-1}'] - bld_eq[f'prob_ds{i}'])
214
  df_ds = bld_eq[['ds_1','ds_2','ds_3','ds_4','ds_5']]
215
+ bld_eq['eq_ds'] = df_ds.idxmax(axis='columns').str.extract(r'ds_([0-9]+)').astype('int') - 1
216
 
217
+ if 1 in policies:
218
+ bld_eq.loc[bld_eq['occupancy'] == 'Res', 'eq_ds'] = 0
219
+ if 2 in policies:
220
+ bld_eq.loc[bld_eq['avgincome'] == 'lowIncomeA', 'eq_ds'] = 0
221
+ bld_eq.loc[bld_eq['avgincome'] == 'lowIncomeB', 'eq_ds'] = 0
222
  # Create a simplified building-hazard relation
223
+ bld_hazard = bld_eq[['bldid','eq_ds']]
224
  bld_hazard = bld_hazard.rename(columns={'eq_ds':'ds'})
225
 
226
+ ds_str = {0: 'No Damage',1:'Low',2:'Medium',3:'High',4:'Collapsed'}
227
 
228
  elif hazard_type == HAZARD_FLOOD:
229
  bld_flood = gdf_building_intensity.merge(df_hazard, on='expstr', how='left')
 
235
  bld_flood['fl_prob'] = np.diag(flood_mapping(xnew))
236
  bld_flood['fl_ds'] = 0
237
  bld_flood.loc[bld_flood['fl_prob'] > threshold_flood,'fl_ds'] = 1
238
+ if 1 in policies:
239
+ bld_flood.loc[bld_flood['occupancy'] == 'Res', 'fl_ds'] = 0
240
+ if 2 in policies:
241
+ bld_flood.loc[bld_flood['avgincome'] == 'lowIncomeA', 'fl_ds'] = 0
242
+ bld_flood.loc[bld_flood['avgincome'] == 'lowIncomeB', 'fl_ds'] = 0
243
  # Create a simplified building-hazard relation
244
+ bld_hazard = bld_flood[['bldid','fl_ds']]
245
  bld_hazard = bld_hazard.rename(columns={'fl_ds':'ds'})
246
 
247
  ds_str = {0: 'No Damage',1:'Flooded'}
248
+
249
+ return bld_hazard
250
+
251
+
252
+ def calculate_metrics(gdf_buildings, df_household, df_individual, hazard_type, policies=[]):
253
+ # only use necessary columns
254
+ bld_hazard = gdf_buildings[['bldid','ds','expstr']]
255
+ bld_hazard[['material','code_level','storeys','occupancy']] = \
256
+ bld_hazard['expstr'].str.split('+',expand=True).copy()
257
+ #bld_hazard['occupancy'] = bld_hazard['occupancy'].astype('category')
258
 
259
+ # Find the damage state of the building that the household is in
260
  df_household_bld = df_household.merge(bld_hazard[['bldid','ds']], on='bldid', how='left',validate='many_to_one')
261
 
262
+ # Find the damage state of the hospital that the household is associated with
263
+ df_hospitals = df_household.merge(bld_hazard[['bldid', 'ds']],
264
  how='left', left_on='commfacid', right_on='bldid', suffixes=['','_comm'],
265
  validate='many_to_one')
266
 
267
+ # Find the occupancy of facility that the individual is associated
268
+ df_individual_occupancy = df_individual.merge(bld_hazard[['bldid','occupancy','ds']],
269
  how='inner',left_on='indivfacid',right_on='bldid',
270
  suffixes=['_l','_r'],validate='many_to_one')
271
 
272
+ # Filtering working places
273
  df_workers = df_individual_occupancy.query('occupancy in ["Com","ResCom","Ind"]')
274
 
275
+ # Filtering schools
276
  df_students = df_individual_occupancy.query('occupancy in ["Edu"]')
277
 
278
+ # connect individuals to damage state of associated hospitals
279
+ df_indiv_hosp = df_individual.merge(df_hospitals[['hhid','ds']],
280
+ how='left', on='hhid', validate='many_to_one')
 
281
 
282
  # get the ds of household that individual lives in
283
  df_indiv_household = df_individual[['hhid','individ']].merge(df_household_bld[['hhid','ds']])
284
 
285
+ # Collect all damage states in a single table
286
  df_displaced_indiv = df_indiv_hosp.rename(columns={'ds':'ds_hospital'})\
287
  .merge(df_workers[['individ','ds']].rename(columns={'ds':'ds_workplace'}),on='individ', how='left')\
288
  .merge(df_students[['individ','ds']].rename(columns={'ds':'ds_school'}), on='individ', how='left')\
289
+ .merge(df_indiv_household[['individ','ds']].rename(columns={'ds':'ds_household'}), on='individ',how='left')\
290
+ .merge(df_household[['hhid','bldid']],on='hhid',how='left')
291
+
292
+ DS_NO = 0
293
+ DS_SLIGHT = 1
294
+ DS_MODERATE = 2
295
+ DS_EXTENSIZE = 3
296
+ DS_COLLAPSED = 4
297
+
298
+ # Hazard Types
299
+ HAZARD_EARTHQUAKE = "earthquake"
300
+ HAZARD_FLOOD = "flood"
301
+ HAZARD_DEBRIS = "debris"
302
 
 
 
303
  if hazard_type == HAZARD_EARTHQUAKE:
304
+ # Effect of policies on thresholds
305
+ # First get the global threshold
306
+ thresholds = {f'metric{id}': DS_SLIGHT for id in range(8)}
307
+ else:
308
+ # Default thresholds for flood and debris
309
+ # For flood, there are only two states: 0 or 1.
310
+ # So threshold is set to 0.
311
+ thresholds = {f'metric{id}': DS_NO for id in range(8)}
312
+
313
+ # metric 1 number of unemployed workers in each building
314
+ df_workers_per_building = df_workers[df_workers['ds'] > thresholds['metric1']][['individ','hhid','ds']].merge(
315
+ df_household[['hhid','bldid']],on='hhid',how='left').groupby(
316
+ 'bldid',as_index=False).agg({'individ':'count'})
 
 
 
 
 
 
 
 
 
 
 
 
 
317
 
 
 
 
318
  df_metric1 = bld_hazard.merge(df_workers_per_building,how='left',left_on='bldid',right_on = 'bldid')[['bldid','individ']]
319
  df_metric1.rename(columns={'individ':'metric1'}, inplace=True)
320
  df_metric1['metric1'] = df_metric1['metric1'].fillna(0).astype(int)
321
 
322
+ # metric 2 number of students in each building with no access to schools
323
+ df_students_per_building = df_students[df_students['ds'] > thresholds['metric2']][['individ','hhid','ds']].merge(
324
+ df_household[['hhid','bldid']],on='hhid',how='left').groupby(
325
+ 'bldid',as_index=False).agg({'individ':'count'})
326
+
327
  df_metric2 = bld_hazard.merge(df_students_per_building,how='left',left_on='bldid',right_on = 'bldid')[['bldid','individ']]
328
  df_metric2.rename(columns={'individ':'metric2'}, inplace=True)
329
  df_metric2['metric2'] = df_metric2['metric2'].fillna(0).astype(int)
330
 
331
+ # metric 3 number of households in each building with no access to hospitals
332
+ df_hospitals_per_household = df_hospitals[df_hospitals['ds'] > thresholds['metric3']].groupby(
333
+ 'bldid',as_index=False).agg({'hhid':'count'})
334
+
335
  df_metric3 = bld_hazard.merge(df_hospitals_per_household,how='left',left_on='bldid',right_on='bldid')[['bldid','hhid']]
336
  df_metric3.rename(columns={'hhid':'metric3'}, inplace=True)
337
  df_metric3['metric3'] = df_metric3['metric3'].fillna(0).astype(int)
338
 
339
+ # metric 4 number of individuals in each building with no access to hospitals
340
+ df_hospitals_per_individual = df_hospitals[df_hospitals['ds'] > thresholds['metric4']].groupby(
341
+ 'bldid',as_index=False).agg({'nind':'sum'})
342
+
343
  df_metric4 = bld_hazard.merge(df_hospitals_per_individual,how='left',left_on='bldid',right_on='bldid')[['bldid','nind']]
344
  df_metric4.rename(columns={'nind':'metric4'}, inplace=True)
345
  df_metric4['metric4'] = df_metric4['metric4'].fillna(0).astype(int)
346
 
347
+ # metric 5 number of damaged households in each building
348
+ df_homeless_households = df_household_bld[df_household_bld['ds'] > thresholds['metric5']].groupby(
349
+ 'bldid',as_index=False).agg({'hhid':'count'})
350
+
351
  df_metric5 = bld_hazard.merge(df_homeless_households,how='left',left_on='bldid',right_on='bldid')[['bldid','hhid']]
352
  df_metric5.rename(columns={'hhid':'metric5'}, inplace=True)
353
  df_metric5['metric5'] = df_metric5['metric5'].fillna(0).astype(int)
354
 
355
+ # metric 6 number of homeless individuals in each building
356
+ df_homeless_individuals = df_household_bld[df_household_bld['ds'] > thresholds['metric6']].groupby(
357
+ 'bldid',as_index=False).agg({'nind':'sum'})
358
+
359
  df_metric6 = bld_hazard.merge(df_homeless_individuals,how='left',left_on='bldid',right_on='bldid')[['bldid','nind']]
360
  df_metric6.rename(columns={'nind':'metric6'}, inplace=True)
361
  df_metric6['metric6'] = df_metric6['metric6'].fillna(0).astype(int)
362
 
363
+ # metric 7 the number of displaced individuals in each building
364
  # more info: an individual is displaced if at least of the conditions below hold
365
  df_disp_per_bld = df_displaced_indiv[(df_displaced_indiv['ds_household'] > thresholds['metric6']) |
366
+ (df_displaced_indiv['ds_school'] > thresholds['metric2']) |
367
+ (df_displaced_indiv['ds_workplace'] > thresholds['metric1']) |
368
+ (df_displaced_indiv['ds_hospital'] > thresholds['metric4'])]\
369
+ .groupby('bldid',as_index=False)\
370
+ .agg({'individ':'count'})
371
+
372
  df_metric7 = bld_hazard.merge(df_disp_per_bld,how='left',left_on='bldid',right_on='bldid')[['bldid','individ']]
373
  df_metric7.rename(columns={'individ':'metric7'}, inplace=True)
374
  df_metric7['metric7'] = df_metric7['metric7'].fillna(0).astype(int)
375
 
376
+
377
+
378
  df_metrics = {'metric1': df_metric1,
379
  'metric2': df_metric2,
380
  'metric3': df_metric3,
 
383
  'metric6': df_metric6,
384
  'metric7': df_metric7}
385
 
386
+
387
  number_of_workers = len(df_workers)
388
  print('number of workers', number_of_workers)
389
 
 
399
  "metric2": {"desc": "Number of children with no access to education", "value": 0, "max_value": number_of_students},
400
  "metric3": {"desc": "Number of households with no access to hospital", "value": 0, "max_value": number_of_households},
401
  "metric4": {"desc": "Number of individuals with no access to hospital", "value": 0, "max_value": number_of_individuals},
402
+ "metric5": {"desc": "Number of households displaced", "value": 0, "max_value": number_of_households},
403
  "metric6": {"desc": "Number of homeless individuals", "value": 0, "max_value": number_of_individuals},
404
  "metric7": {"desc": "Population displacement", "value": 0, "max_value": number_of_individuals},}
405
  metrics["metric1"]["value"] = int(df_metric1['metric1'].sum())
 
410
  metrics["metric6"]["value"] = int(df_metric6['metric6'].sum())
411
  metrics["metric7"]["value"] = int(df_metric7['metric7'].sum())
412
 
413
+ return metrics, df_metrics
 
tomorrowcities/content/articles/metrics.md ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ author: huseyin.kaya
3
+ title: Metrics
4
+ description: A brief introduction to metrics
5
+ image: https://images.unsplash.com/photo-1429041966141-44d228a42775?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=2500&q=80
6
+ thumbnail: https://images.unsplash.com/photo-1429041966141-44d228a42775?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=350&q=80
7
+ alt: "Metrics"
8
+ createdAt: 2023-10-10
9
+ duration: 6 min read
10
+ category:
11
+ - general
12
+ ---
13
+
14
+ ## Metrics
15
+ There are seven fundamental impact metrics displayed in the web application. For each them there is an associcated threhold but for the sake of simplicity, it is not explicitly stated.
16
+
17
+ ### Metric 1: Number of workers unemployed
18
+ It denotes the number of invidivuals who last their jobs either due to a severe damage at the workplace
19
+ or lost access to a functional workplace. When the metrics is displayed on a building-level, it is the sum of
20
+ such individuals living in that building.
21
+
22
+ ### Metric 2: Number of children with no access to education
23
+ Similar to the first metrics but individual here refers to a child who is associated with a school.
24
+ The metric becomes active if the school is damaged or not accessible.
25
+
26
+ ### Metric 3: Number of households with no access to hospital
27
+ The number of households who lost its access to its associated hospital.
28
+ Either hospital is damaged or the path between the household and the hospital
29
+ is broken.
30
+
31
+ ### Metric 4: Number of individuals with no access to hospital
32
+ It is derived from metric 3 by counting the individuals in the corresponding households.
33
+
34
+ ### Metric 5: Number of households displaced
35
+ It is direct result of building damage state. If a building is damaged, then the households in it are also
36
+ damaged.
37
+
38
+ ### Metric 6: Number of homeless individuals
39
+ It is derived from metric 5.
40
+
41
+ ### Metric 7: Population displacement
42
+ An individual is assumed to be displaced if the associated household is damaged, or he/she lost access to workplace, schoold or hospital.
tomorrowcities/content/articles/policies.md ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ author: huseyin.kaya
3
+ title: Policies in a nutshell
4
+ description: A brief introduction to policies
5
+ image: https://images.unsplash.com/photo-1429041966141-44d228a42775?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=2500&q=80
6
+ thumbnail: https://images.unsplash.com/photo-1429041966141-44d228a42775?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=350&q=80
7
+ alt: "Policies"
8
+ createdAt: 2023-10-10
9
+ duration: 6 min read
10
+ category:
11
+ - general
12
+ ---
13
+
14
+ ### Policy 1: Land and tenure security program
15
+ When this policy applied, the code levels of the residential buildings are set to higher standards
16
+ which effectively reduces the damage states during hazard simulations. Since this policy only changes
17
+ the code levels, the computing engine is required to run again to see its effect.
18
+
19
+ ### Policy 2: State-led upgrading/retrofitting of low-income/informal housing
20
+ When applied, this policy increase the code level of the buildings located in low income A and B zones.
21
+ This effectively reduces the damage states, and hence several impact metrics, starting from building-level
22
+ metrics.
tomorrowcities/content/articles/welcome.md CHANGED
@@ -14,6 +14,17 @@ category:
14
  ## Tomorrow's Cities Decision Support Environment (TCDSE)
15
  TCDSE is a web application designed to conduct computational tasks to generate information needed for decision mechanisms in designing future cities. The web application, which will be referred as TCDSE for short, contains a computational engine capable of executing several hazard scenarios on different exposure datasets and infrastructures.
16
 
 
 
 
 
 
 
 
 
 
 
 
17
  ## Features
18
  General capabilities/features of the web application can be summarized as follows:
19
 
 
14
  ## Tomorrow's Cities Decision Support Environment (TCDSE)
15
  TCDSE is a web application designed to conduct computational tasks to generate information needed for decision mechanisms in designing future cities. The web application, which will be referred as TCDSE for short, contains a computational engine capable of executing several hazard scenarios on different exposure datasets and infrastructures.
16
 
17
+ ## What is New?
18
+
19
+ ### Policy Support
20
+ Two new policies are added to the computing engine. More info is [here](/docs/policies)
21
+
22
+ ### Excel support:
23
+ You can upload Excel files containing your tabular data such as individual, household, fragility or vulnerability data. However, processing excel files is very slower than processing JSON files so I definitely suggest working with JSON files. You can convert your Excel files via panda framework. The platform also does not try to convert the coordinates even if there is any in the Excel file because there is no way to know which columns represent the coordinates or coordinate reference systems without metadata. So Excel spreadsheets should be used to provide non-geo related tabular data. So please use them only for data not containing any geo-specific information.
24
+
25
+ ### Dar Es Salaam Study
26
+ [Sample Dataset](https://drive.google.com/file/d/1BGPZQ2IKJHY9ExOCCHcNNrCTioYZ8D1y/view?usp=sharing) now contains some visioning scenario for Dar Es Salaam case.
27
+
28
  ## Features
29
  General capabilities/features of the web application can be summarized as follows:
30
 
tomorrowcities/pages/engine.py CHANGED
@@ -10,10 +10,10 @@ from typing import Tuple, Optional
10
  import ipyleaflet
11
  from ipyleaflet import AwesomeIcon, Marker
12
  import numpy as np
13
- import sys
14
-
15
- from ..backend.engine import compute, compute_power_infra
16
 
 
17
 
18
  layers = solara.reactive({
19
  'layers' : {
@@ -23,6 +23,7 @@ layers = solara.reactive({
23
  'force_render': solara.reactive(False),
24
  'visible': solara.reactive(False),
25
  'extra_cols': {'ds': 0, 'metric1': 0, 'metric2': 0, 'metric3': 0,'metric4': 0, 'metric5': 0,'metric6': 0,'metric7': 0},
 
26
  'cols': set(['residents', 'fptarea', 'repvalue', 'nhouse', 'zoneid', 'expstr', 'bldid', 'geometry', 'specialfac'])},
27
  'landuse': {
28
  'df': solara.reactive(None),
@@ -30,6 +31,7 @@ layers = solara.reactive({
30
  'force_render': solara.reactive(False),
31
  'visible': solara.reactive(False),
32
  'extra_cols': {},
 
33
  'cols': set(['geometry', 'zoneid', 'luf', 'population', 'densitycap', 'floorarat', 'setback', 'avgincome'])},
34
  'household': {
35
  'df': solara.reactive(None),
@@ -37,6 +39,7 @@ layers = solara.reactive({
37
  'force_render': solara.reactive(False),
38
  'visible': solara.reactive(False),
39
  'extra_cols': {},
 
40
  'cols':set(['hhid', 'nind', 'income', 'bldid', 'commfacid'])},
41
  'individual': {
42
  'df': solara.reactive(None),
@@ -44,6 +47,7 @@ layers = solara.reactive({
44
  'force_render': solara.reactive(False),
45
  'visible': solara.reactive(False),
46
  'extra_cols': {},
 
47
  'cols': set(['individ', 'hhid', 'gender', 'age', 'eduattstat', 'head', 'indivfacid'])},
48
  'intensity': {
49
  'df': solara.reactive(None),
@@ -51,6 +55,7 @@ layers = solara.reactive({
51
  'force_render': solara.reactive(False),
52
  'visible': solara.reactive(False),
53
  'extra_cols': {},
 
54
  'cols': set(['geometry','im'])},
55
  'fragility': {
56
  'df': solara.reactive(None),
@@ -58,6 +63,7 @@ layers = solara.reactive({
58
  'force_render': solara.reactive(False),
59
  'visible': solara.reactive(False),
60
  'extra_cols': {},
 
61
  'cols': set(['expstr','muds1_g','muds2_g','muds3_g','muds4_g','sigmads1','sigmads2','sigmads3','sigmads4'])},
62
  'vulnerability': {
63
  'df': solara.reactive(None),
@@ -65,6 +71,7 @@ layers = solara.reactive({
65
  'force_render': solara.reactive(False),
66
  'visible': solara.reactive(False),
67
  'extra_cols': {},
 
68
  'cols': set(['expstr', 'hw0', 'hw0_5', 'hw1', 'hw1_5', 'hw2', 'hw3', 'hw4', 'hw5','hw6'])},
69
  'power nodes': {
70
  'df': solara.reactive(None),
@@ -72,6 +79,9 @@ layers = solara.reactive({
72
  'force_render': solara.reactive(False),
73
  'visible': solara.reactive(False),
74
  'extra_cols': {'ds': 0, 'is_damaged': False, 'is_operational': True},
 
 
 
75
  'cols': set(['geometry', 'fltytype', 'strctype', 'utilfcltyc', 'indpnode', 'guid',
76
  'node_id', 'x_coord', 'y_coord', 'pwr_plant', 'serv_area', 'n_bldgs',
77
  'income', 'eq_vuln'])},
@@ -81,6 +91,8 @@ layers = solara.reactive({
81
  'force_render': solara.reactive(False),
82
  'visible': solara.reactive(False),
83
  'extra_cols': {},
 
 
84
  'cols': set(['from_node', 'direction', 'pipetype', 'edge_id', 'guid', 'capacity',
85
  'geometry', 'to_node', 'length'])},
86
  'power fragility': {
@@ -89,6 +101,8 @@ layers = solara.reactive({
89
  'force_render': solara.reactive(False),
90
  'visible': solara.reactive(False),
91
  'extra_cols': {},
 
 
92
  'cols': set(['vuln_string', 'med_slight', 'med_moderate', 'med_extensive', 'med_complete',
93
  'beta_slight', 'beta_moderate', 'beta_extensive', 'beta_complete', 'description'])}
94
  },
@@ -96,12 +110,16 @@ layers = solara.reactive({
96
  'selected_layer' : solara.reactive(None),
97
  'render_count': solara.reactive(0),
98
  'bounds': solara.reactive(None),
 
 
 
 
99
  'metrics': {
100
  "metric1": {"desc": "Number of workers unemployed", "value": 0, "max_value": 100},
101
  "metric2": {"desc": "Number of children with no access to education", "value": 0, "max_value": 100},
102
  "metric3": {"desc": "Number of households with no access to hospital", "value": 0, "max_value": 100},
103
  "metric4": {"desc": "Number of individuals with no access to hospital", "value": 0, "max_value": 100},
104
- "metric5": {"desc": "Number of homeless households", "value": 0, "max_value": 100},
105
  "metric6": {"desc": "Number of homeless individuals", "value": 0, "max_value": 100},
106
  "metric7": {"desc": "Population displacement", "value": 0, "max_value":100},}})
107
 
@@ -200,21 +218,35 @@ def MetricWidget(name, description, value, max_value, render_count):
200
  solara.FigureEcharts(option=options, attributes={ "style": "height: 100px; width: 100px" })
201
 
202
 
203
- def import_data(data: Optional[bytes]):
204
- json_string = data.decode('utf-8')
205
- json_data = json.loads(json_string)
206
- if "features" in json_data.keys():
207
- df = gpd.GeoDataFrame.from_features(json_data['features'])
208
  else:
209
- df = pd.read_json(json_string)
 
 
 
 
 
210
 
211
  df.columns = df.columns.str.lower()
212
 
 
213
  name = None
214
  for layer_name, layer in layers.value['layers'].items():
215
  if layer['cols'] == set(df.columns):
216
  name = layer_name
217
  break
 
 
 
 
 
 
 
 
218
 
219
  # Inject columns
220
  if name is not None:
@@ -232,7 +264,7 @@ def FileDropZone():
232
  def load():
233
  if fileinfo is not None:
234
  print('processing file')
235
- name, df = import_data(fileinfo['data'])
236
  if name is not None and df is not None:
237
  layers.value['layers'][name]['df'].set(df)
238
  layers.value['selected_layer'].set(name)
@@ -278,7 +310,7 @@ def FileDropZone():
278
 
279
  @solara.component
280
  def LayerDisplayer():
281
-
282
  nonempty_layers = {name: layer for name, layer in layers.value['layers'].items() if layer['df'].value is not None}
283
  nonempty_layer_names = list(nonempty_layers.keys())
284
  selected = layers.value['selected_layer'].value
@@ -290,9 +322,13 @@ def LayerDisplayer():
290
  if selected is None and len(nonempty_layer_names) > 0:
291
  set_selected(nonempty_layer_names[0])
292
  if selected is not None:
293
- DataframeDisplayer(nonempty_layers[selected]['df'].value,
294
- layers.value['render_count'].value,
295
- layers.value['bounds'].value)
 
 
 
 
296
 
297
  @solara.component
298
  def MetricPanel():
@@ -310,6 +346,8 @@ def MetricPanel():
310
  filtered_metrics[name],
311
  metric['max_value'],
312
  layers.value['render_count'].value)
 
 
313
 
314
 
315
  @solara.component
@@ -329,9 +367,6 @@ def MapViewer():
329
  zoom, set_zoom = solara.use_state(default_zoom)
330
  #center, set_center = solara.use_state(default_center)
331
 
332
- def set_bounds(bounds):
333
- layers.value['bounds'].set(bounds)
334
-
335
  base_map = ipyleaflet.basemaps["Stamen"]["Watercolor"]
336
  base_layer = ipyleaflet.TileLayer.element(url=base_map.build_url())
337
  map_layers = [base_layer]
@@ -350,7 +385,7 @@ def MapViewer():
350
  ipyleaflet.Map.element(
351
  zoom=zoom,
352
  on_zoom=set_zoom,
353
- on_bounds=set_bounds,
354
  center=layers.value['center'].value,
355
  on_center=layers.value['center'].set,
356
  scroll_wheel_zoom=True,
@@ -361,21 +396,11 @@ def MapViewer():
361
  keyboard=True if random.random() > 0.5 else False,
362
  layers=map_layers
363
  )
364
-
365
-
366
- @solara.component
367
- def DataframeDisplayer(df, render_count, bounds):
368
- df, set_df = solara.use_state_or_update(df)
369
- if "geometry" in df.columns:
370
- ((ymin,xmin),(ymax,xmax)) = bounds
371
- solara.DataFrame(df.cx[xmin:xmax,ymin:ymax].drop(columns='geometry'))
372
- else:
373
- solara.DataFrame(df)
374
-
375
  @solara.component
376
  def ExecutePanel():
377
- infra, set_infra = solara.use_state(["power"])
378
- hazard, set_hazard = solara.use_state("earthquake")
379
 
380
 
381
  execute_counter, set_execute_counter = solara.use_state(0)
@@ -394,12 +419,12 @@ def ExecutePanel():
394
  if "power" in infra:
395
  missing += list(set(["power edges","power nodes","intensity","power fragility"]) - existing_layers)
396
  if "building" in infra:
397
- missing += list(set(["building","household","individual","intensity","fragility"]) - existing_layers)
398
  elif hazard == "flood":
399
  if "power" in infra:
400
  missing += list(set(["power edges","power nodes","intensity","power vulnerability"]) - existing_layers)
401
  if "building" in infra:
402
- missing += list(set(["building","household","individual","intensity","vulnerability"]) - existing_layers)
403
 
404
  if infra == []:
405
  missing += ['You should select power and/or building']
@@ -429,6 +454,7 @@ def ExecutePanel():
429
  return nodes
430
 
431
  def execute_building():
 
432
  buildings = layers.value['layers']['building']['df'].value
433
  household = layers.value['layers']['household']['df'].value
434
  individual = layers.value['layers']['individual']['df'].value
@@ -436,20 +462,27 @@ def ExecutePanel():
436
 
437
  fragility = layers.value['layers']['fragility']['df'].value
438
  vulnerability = layers.value['layers']['vulnerability']['df'].value
439
- computed_metrics, df_metrics, df_bld_hazard = compute(
 
 
 
 
 
440
  buildings,
441
  household,
442
  individual,
443
  intensity,
444
  fragility if hazard == "earthquake" else vulnerability,
445
- hazard)
446
-
 
 
447
  print(computed_metrics)
448
  for metric in df_metrics.keys():
449
  buildings[metric] = list(df_metrics[metric][metric])
450
  layers.value['metrics'][metric]['value'] = computed_metrics[metric]['value']
451
  layers.value['metrics'][metric]['max_value'] = computed_metrics[metric]['max_value']
452
- buildings['ds'] = list(df_bld_hazard['ds'])
453
  return buildings
454
 
455
  if execute_counter > 0 :
@@ -477,10 +510,12 @@ def ExecutePanel():
477
  result = solara.use_thread(execute_engine, dependencies=[execute_counter])
478
 
479
  with solara.Row(justify="center"):
480
- solara.ToggleButtonsMultiple(value=infra, on_value=set_infra, values=["power","building"])
481
  with solara.Row(justify="center"):
482
  solara.ToggleButtonsSingle(value=hazard, on_value=set_hazard, values=["earthquake","flood"])
483
 
 
 
484
  solara.Button("Calculate", on_click=on_click, outlined=True,
485
  disabled=execute_btn_disabled)
486
  # The statements in this block are passed several times during thread execution
@@ -499,13 +534,22 @@ def ExecutePanel():
499
  set_execute_btn_disabled(False)
500
  solara.ProgressLinear(value=False)
501
 
 
 
 
 
 
 
 
 
 
 
502
 
503
  @solara.component
504
  def WebApp():
505
 
506
  with solara.Columns([30,60]):
507
  with solara.Column():
508
-
509
  solara.Markdown('[Download Sample Dataset](https://drive.google.com/file/d/1BGPZQ2IKJHY9ExOCCHcNNrCTioYZ8D1y/view?usp=sharing)')
510
  FileDropZone()
511
  ExecutePanel()
 
10
  import ipyleaflet
11
  from ipyleaflet import AwesomeIcon, Marker
12
  import numpy as np
13
+ import logging, sys
14
+ #logging.basicConfig(stream=sys.stderr, level=logging.INFO)
 
15
 
16
+ from ..backend.engine import compute, compute_power_infra, calculate_metrics
17
 
18
  layers = solara.reactive({
19
  'layers' : {
 
23
  'force_render': solara.reactive(False),
24
  'visible': solara.reactive(False),
25
  'extra_cols': {'ds': 0, 'metric1': 0, 'metric2': 0, 'metric3': 0,'metric4': 0, 'metric5': 0,'metric6': 0,'metric7': 0},
26
+ 'cols_required': set(['residents', 'fptarea', 'repvalue', 'nhouse', 'zoneid', 'expstr', 'bldid', 'geometry', 'specialfac']),
27
  'cols': set(['residents', 'fptarea', 'repvalue', 'nhouse', 'zoneid', 'expstr', 'bldid', 'geometry', 'specialfac'])},
28
  'landuse': {
29
  'df': solara.reactive(None),
 
31
  'force_render': solara.reactive(False),
32
  'visible': solara.reactive(False),
33
  'extra_cols': {},
34
+ 'cols_required': set(['geometry', 'zoneid', 'luf', 'population', 'densitycap', 'avgincome']),
35
  'cols': set(['geometry', 'zoneid', 'luf', 'population', 'densitycap', 'floorarat', 'setback', 'avgincome'])},
36
  'household': {
37
  'df': solara.reactive(None),
 
39
  'force_render': solara.reactive(False),
40
  'visible': solara.reactive(False),
41
  'extra_cols': {},
42
+ 'cols_required':set(['hhid', 'nind', 'income', 'bldid', 'commfacid']),
43
  'cols':set(['hhid', 'nind', 'income', 'bldid', 'commfacid'])},
44
  'individual': {
45
  'df': solara.reactive(None),
 
47
  'force_render': solara.reactive(False),
48
  'visible': solara.reactive(False),
49
  'extra_cols': {},
50
+ 'cols_required': set(['individ', 'hhid', 'gender', 'age', 'eduattstat', 'head', 'indivfacid']),
51
  'cols': set(['individ', 'hhid', 'gender', 'age', 'eduattstat', 'head', 'indivfacid'])},
52
  'intensity': {
53
  'df': solara.reactive(None),
 
55
  'force_render': solara.reactive(False),
56
  'visible': solara.reactive(False),
57
  'extra_cols': {},
58
+ 'cols_required': set(['geometry','im']),
59
  'cols': set(['geometry','im'])},
60
  'fragility': {
61
  'df': solara.reactive(None),
 
63
  'force_render': solara.reactive(False),
64
  'visible': solara.reactive(False),
65
  'extra_cols': {},
66
+ 'cols_required': set(['expstr','muds1_g','muds2_g','muds3_g','muds4_g','sigmads1','sigmads2','sigmads3','sigmads4']),
67
  'cols': set(['expstr','muds1_g','muds2_g','muds3_g','muds4_g','sigmads1','sigmads2','sigmads3','sigmads4'])},
68
  'vulnerability': {
69
  'df': solara.reactive(None),
 
71
  'force_render': solara.reactive(False),
72
  'visible': solara.reactive(False),
73
  'extra_cols': {},
74
+ 'cols_required': set(['expstr', 'hw0', 'hw0_5', 'hw1', 'hw1_5', 'hw2', 'hw3', 'hw4', 'hw5','hw6']),
75
  'cols': set(['expstr', 'hw0', 'hw0_5', 'hw1', 'hw1_5', 'hw2', 'hw3', 'hw4', 'hw5','hw6'])},
76
  'power nodes': {
77
  'df': solara.reactive(None),
 
79
  'force_render': solara.reactive(False),
80
  'visible': solara.reactive(False),
81
  'extra_cols': {'ds': 0, 'is_damaged': False, 'is_operational': True},
82
+ 'cols_required': set(['geometry', 'fltytype', 'strctype', 'utilfcltyc', 'indpnode', 'guid',
83
+ 'node_id', 'x_coord', 'y_coord', 'pwr_plant', 'serv_area', 'n_bldgs',
84
+ 'income', 'eq_vuln']),
85
  'cols': set(['geometry', 'fltytype', 'strctype', 'utilfcltyc', 'indpnode', 'guid',
86
  'node_id', 'x_coord', 'y_coord', 'pwr_plant', 'serv_area', 'n_bldgs',
87
  'income', 'eq_vuln'])},
 
91
  'force_render': solara.reactive(False),
92
  'visible': solara.reactive(False),
93
  'extra_cols': {},
94
+ 'cols_required': set(['from_node', 'direction', 'pipetype', 'edge_id', 'guid', 'capacity',
95
+ 'geometry', 'to_node', 'length']),
96
  'cols': set(['from_node', 'direction', 'pipetype', 'edge_id', 'guid', 'capacity',
97
  'geometry', 'to_node', 'length'])},
98
  'power fragility': {
 
101
  'force_render': solara.reactive(False),
102
  'visible': solara.reactive(False),
103
  'extra_cols': {},
104
+ 'cols_required': set(['vuln_string', 'med_slight', 'med_moderate', 'med_extensive', 'med_complete',
105
+ 'beta_slight', 'beta_moderate', 'beta_extensive', 'beta_complete']),
106
  'cols': set(['vuln_string', 'med_slight', 'med_moderate', 'med_extensive', 'med_complete',
107
  'beta_slight', 'beta_moderate', 'beta_extensive', 'beta_complete', 'description'])}
108
  },
 
110
  'selected_layer' : solara.reactive(None),
111
  'render_count': solara.reactive(0),
112
  'bounds': solara.reactive(None),
113
+ 'policies': {
114
+ '1': {'id':1, 'label': 'Policy 1', 'description': 'Land and tenure security program', 'applied': solara.reactive(False)},
115
+ '2': {'id':2, 'label': 'Policy 2', 'description': 'State-led upgrading/retrofitting of low-income/informal housing', 'applied': solara.reactive(False)},
116
+ },
117
  'metrics': {
118
  "metric1": {"desc": "Number of workers unemployed", "value": 0, "max_value": 100},
119
  "metric2": {"desc": "Number of children with no access to education", "value": 0, "max_value": 100},
120
  "metric3": {"desc": "Number of households with no access to hospital", "value": 0, "max_value": 100},
121
  "metric4": {"desc": "Number of individuals with no access to hospital", "value": 0, "max_value": 100},
122
+ "metric5": {"desc": "Number of households displaced", "value": 0, "max_value": 100},
123
  "metric6": {"desc": "Number of homeless individuals", "value": 0, "max_value": 100},
124
  "metric7": {"desc": "Population displacement", "value": 0, "max_value":100},}})
125
 
 
218
  solara.FigureEcharts(option=options, attributes={ "style": "height: 100px; width: 100px" })
219
 
220
 
221
+ def import_data(fileinfo: solara.components.file_drop.FileInfo):
222
+ data = fileinfo['data']
223
+ extension = fileinfo['name'].split('.')[-1]
224
+ if extension == 'xlsx':
225
+ df = pd.read_excel(data)
226
  else:
227
+ json_string = data.decode('utf-8')
228
+ json_data = json.loads(json_string)
229
+ if "features" in json_data.keys():
230
+ df = gpd.GeoDataFrame.from_features(json_data['features'])
231
+ else:
232
+ df = pd.read_json(json_string)
233
 
234
  df.columns = df.columns.str.lower()
235
 
236
+ # in the first pass, look for exact column match
237
  name = None
238
  for layer_name, layer in layers.value['layers'].items():
239
  if layer['cols'] == set(df.columns):
240
  name = layer_name
241
  break
242
+ # if not, check only the required columns
243
+ if name is None:
244
+ for layer_name, layer in layers.value['layers'].items():
245
+ if layer['cols_required'].issubset(set(df.columns)):
246
+ name = layer_name
247
+ logging.debug('There are extra columns', set(df.columns) - layer['cols_required'])
248
+ break
249
+
250
 
251
  # Inject columns
252
  if name is not None:
 
264
  def load():
265
  if fileinfo is not None:
266
  print('processing file')
267
+ name, df = import_data(fileinfo)
268
  if name is not None and df is not None:
269
  layers.value['layers'][name]['df'].set(df)
270
  layers.value['selected_layer'].set(name)
 
310
 
311
  @solara.component
312
  def LayerDisplayer():
313
+ print(f'{layers.value["bounds"].value}')
314
  nonempty_layers = {name: layer for name, layer in layers.value['layers'].items() if layer['df'].value is not None}
315
  nonempty_layer_names = list(nonempty_layers.keys())
316
  selected = layers.value['selected_layer'].value
 
322
  if selected is None and len(nonempty_layer_names) > 0:
323
  set_selected(nonempty_layer_names[0])
324
  if selected is not None:
325
+ df = nonempty_layers[selected]['df'].value
326
+ if "geometry" in df.columns:
327
+ ((ymin,xmin),(ymax,xmax)) = layers.value['bounds'].value
328
+ solara.DataFrame(df.cx[xmin:xmax,ymin:ymax].drop(columns='geometry'))
329
+ else:
330
+ solara.DataFrame(df)
331
+
332
 
333
  @solara.component
334
  def MetricPanel():
 
346
  filtered_metrics[name],
347
  metric['max_value'],
348
  layers.value['render_count'].value)
349
+ with solara.Link("/docs/metrics"):
350
+ solara.Button(icon_name="mdi-help-circle-outline", icon=True)
351
 
352
 
353
  @solara.component
 
367
  zoom, set_zoom = solara.use_state(default_zoom)
368
  #center, set_center = solara.use_state(default_center)
369
 
 
 
 
370
  base_map = ipyleaflet.basemaps["Stamen"]["Watercolor"]
371
  base_layer = ipyleaflet.TileLayer.element(url=base_map.build_url())
372
  map_layers = [base_layer]
 
385
  ipyleaflet.Map.element(
386
  zoom=zoom,
387
  on_zoom=set_zoom,
388
+ on_bounds=layers.value['bounds'].set,
389
  center=layers.value['center'].value,
390
  on_center=layers.value['center'].set,
391
  scroll_wheel_zoom=True,
 
396
  keyboard=True if random.random() > 0.5 else False,
397
  layers=map_layers
398
  )
399
+
 
 
 
 
 
 
 
 
 
 
400
  @solara.component
401
  def ExecutePanel():
402
+ infra, set_infra = solara.use_state(["building"])
403
+ hazard, set_hazard = solara.use_state("flood")
404
 
405
 
406
  execute_counter, set_execute_counter = solara.use_state(0)
 
419
  if "power" in infra:
420
  missing += list(set(["power edges","power nodes","intensity","power fragility"]) - existing_layers)
421
  if "building" in infra:
422
+ missing += list(set(["landuse","building","household","individual","intensity","fragility"]) - existing_layers)
423
  elif hazard == "flood":
424
  if "power" in infra:
425
  missing += list(set(["power edges","power nodes","intensity","power vulnerability"]) - existing_layers)
426
  if "building" in infra:
427
+ missing += list(set(["landuse","building","household","individual","intensity","vulnerability"]) - existing_layers)
428
 
429
  if infra == []:
430
  missing += ['You should select power and/or building']
 
454
  return nodes
455
 
456
  def execute_building():
457
+ landuse = layers.value['layers']['landuse']['df'].value
458
  buildings = layers.value['layers']['building']['df'].value
459
  household = layers.value['layers']['household']['df'].value
460
  individual = layers.value['layers']['individual']['df'].value
 
462
 
463
  fragility = layers.value['layers']['fragility']['df'].value
464
  vulnerability = layers.value['layers']['vulnerability']['df'].value
465
+
466
+ policies = [p['id'] for id, p in layers.value['policies'].items() if p['applied'].value]
467
+
468
+ print('policies',policies)
469
+ df_bld_hazard = compute(
470
+ landuse,
471
  buildings,
472
  household,
473
  individual,
474
  intensity,
475
  fragility if hazard == "earthquake" else vulnerability,
476
+ hazard,policies=policies)
477
+ buildings['ds'] = list(df_bld_hazard['ds'])
478
+ computed_metrics, df_metrics = calculate_metrics(buildings, household, individual, hazard, policies=policies)
479
+
480
  print(computed_metrics)
481
  for metric in df_metrics.keys():
482
  buildings[metric] = list(df_metrics[metric][metric])
483
  layers.value['metrics'][metric]['value'] = computed_metrics[metric]['value']
484
  layers.value['metrics'][metric]['max_value'] = computed_metrics[metric]['max_value']
485
+
486
  return buildings
487
 
488
  if execute_counter > 0 :
 
510
  result = solara.use_thread(execute_engine, dependencies=[execute_counter])
511
 
512
  with solara.Row(justify="center"):
513
+ solara.ToggleButtonsMultiple(value=infra, on_value=set_infra, values=["building","power"])
514
  with solara.Row(justify="center"):
515
  solara.ToggleButtonsSingle(value=hazard, on_value=set_hazard, values=["earthquake","flood"])
516
 
517
+ PolicyPanel()
518
+ solara.ProgressLinear(value=False)
519
  solara.Button("Calculate", on_click=on_click, outlined=True,
520
  disabled=execute_btn_disabled)
521
  # The statements in this block are passed several times during thread execution
 
534
  set_execute_btn_disabled(False)
535
  solara.ProgressLinear(value=False)
536
 
537
+ @solara.component
538
+ def PolicyPanel():
539
+ with solara.Row():
540
+ for policy_key, policy in layers.value['policies'].items():
541
+ with solara.Tooltip(tooltip=policy['description']):
542
+ solara.Checkbox(label=policy['label'],
543
+ value=policy['applied'])
544
+ with solara.Link("/docs/policies"):
545
+ solara.Button(icon_name="mdi-help-circle-outline", icon=True)
546
+
547
 
548
  @solara.component
549
  def WebApp():
550
 
551
  with solara.Columns([30,60]):
552
  with solara.Column():
 
553
  solara.Markdown('[Download Sample Dataset](https://drive.google.com/file/d/1BGPZQ2IKJHY9ExOCCHcNNrCTioYZ8D1y/view?usp=sharing)')
554
  FileDropZone()
555
  ExecutePanel()