NREL-erad 0.1.0__py3-none-any.whl → 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. erad/__init__.py +1 -1
  2. erad/constants.py +20 -68
  3. erad/cypher_queries/load_data_v1.cypher +212 -0
  4. erad/data/World_Earthquakes_1960_2016.csv +23410 -0
  5. erad/db/__init__.py +0 -0
  6. erad/db/assets/__init__.py +0 -0
  7. erad/db/assets/critical_infras.py +171 -0
  8. erad/db/assets/distribution_lines.py +101 -0
  9. erad/db/credential_model.py +20 -0
  10. erad/db/disaster_input_model.py +23 -0
  11. erad/db/inject_earthquake.py +52 -0
  12. erad/db/inject_flooding.py +53 -0
  13. erad/db/neo4j_.py +162 -0
  14. erad/db/utils.py +14 -0
  15. erad/exceptions.py +68 -0
  16. erad/metrics/__init__.py +0 -0
  17. erad/metrics/check_microgrid.py +208 -0
  18. erad/metrics/metric.py +178 -0
  19. erad/programs/__init__.py +0 -0
  20. erad/programs/backup.py +62 -0
  21. erad/programs/microgrid.py +45 -0
  22. erad/scenarios/__init__.py +0 -0
  23. erad/scenarios/abstract_scenario.py +103 -0
  24. erad/scenarios/common.py +93 -0
  25. erad/scenarios/earthquake_scenario.py +161 -0
  26. erad/scenarios/fire_scenario.py +160 -0
  27. erad/scenarios/flood_scenario.py +494 -0
  28. erad/scenarios/flows.csv +671 -0
  29. erad/scenarios/utilities.py +76 -0
  30. erad/scenarios/wind_scenario.py +89 -0
  31. erad/utils/__init__.py +0 -0
  32. erad/utils/ditto_utils.py +252 -0
  33. erad/utils/hifld_utils.py +147 -0
  34. erad/utils/opendss_utils.py +357 -0
  35. erad/utils/overpass.py +76 -0
  36. erad/utils/util.py +178 -0
  37. erad/visualization/__init__.py +0 -0
  38. erad/visualization/plot_graph.py +218 -0
  39. {nrel_erad-0.1.0.dist-info → nrel_erad-1.0.0.dist-info}/METADATA +39 -29
  40. nrel_erad-1.0.0.dist-info/RECORD +42 -0
  41. {nrel_erad-0.1.0.dist-info → nrel_erad-1.0.0.dist-info}/WHEEL +1 -2
  42. {nrel_erad-0.1.0.dist-info → nrel_erad-1.0.0.dist-info}/licenses/LICENSE.txt +28 -28
  43. erad/default_fragility_curves/__init__.py +0 -15
  44. erad/default_fragility_curves/default_fire_boundary_dist.py +0 -94
  45. erad/default_fragility_curves/default_flood_depth.py +0 -108
  46. erad/default_fragility_curves/default_flood_velocity.py +0 -101
  47. erad/default_fragility_curves/default_fragility_curves.py +0 -23
  48. erad/default_fragility_curves/default_peak_ground_acceleration.py +0 -163
  49. erad/default_fragility_curves/default_peak_ground_velocity.py +0 -94
  50. erad/default_fragility_curves/default_wind_speed.py +0 -94
  51. erad/enums.py +0 -40
  52. erad/gdm_mapping.py +0 -83
  53. erad/models/__init__.py +0 -1
  54. erad/models/asset.py +0 -287
  55. erad/models/asset_mapping.py +0 -20
  56. erad/models/fragility_curve.py +0 -116
  57. erad/models/hazard/__init__.py +0 -5
  58. erad/models/hazard/base_models.py +0 -12
  59. erad/models/hazard/common.py +0 -26
  60. erad/models/hazard/earthquake.py +0 -93
  61. erad/models/hazard/flood.py +0 -83
  62. erad/models/hazard/wild_fire.py +0 -121
  63. erad/models/hazard/wind.py +0 -143
  64. erad/models/probability.py +0 -73
  65. erad/probability_builder.py +0 -35
  66. erad/quantities.py +0 -25
  67. erad/runner.py +0 -122
  68. erad/systems/__init__.py +0 -2
  69. erad/systems/asset_system.py +0 -414
  70. erad/systems/hazard_system.py +0 -122
  71. nrel_erad-0.1.0.dist-info/RECORD +0 -35
  72. nrel_erad-0.1.0.dist-info/top_level.txt +0 -1
erad/models/asset.py DELETED
@@ -1,287 +0,0 @@
1
- from datetime import datetime
2
- from uuid import UUID
3
- import math
4
-
5
- from pydantic import computed_field, Field
6
- from infrasys.quantities import Distance
7
- from geopy.distance import geodesic
8
- from shapely.geometry import Point
9
- from pyhigh import get_elevation
10
- from infrasys import Component
11
-
12
- from erad.quantities import Acceleration, Speed
13
- from erad.models.probability import (
14
- AccelerationProbability,
15
- DistanceProbability,
16
- SpeedProbability,
17
- )
18
- from erad.enums import AssetTypes
19
- import erad.models.hazard as hz
20
-
21
-
22
- class AssetState(Component):
23
- name: str = ""
24
- timestamp: datetime
25
- wind_speed: SpeedProbability | None = None
26
- flood_velocity: SpeedProbability | None = None
27
- flood_depth: DistanceProbability | None = None
28
- fire_boundary_dist: DistanceProbability | None = None
29
- peak_ground_velocity: SpeedProbability | None = None
30
- peak_ground_acceleration: AccelerationProbability | None = None
31
-
32
- def calculate_earthquake_vectors(
33
- self, asset_coordinate: Point, hazard_model: hz.EarthQuakeModel
34
- ):
35
- """
36
- Sources:
37
-
38
- Atkinson, G.M., & Wald, D.J. (2007). “Did You Feel It?” intensity data: A surprisingly good
39
- measure of earthquake ground motion. Seismological Research Letters, 78(3), 362–368.
40
- """
41
-
42
- epicenter_distance = Distance(
43
- geodesic(
44
- (hazard_model.origin.y, hazard_model.origin.x),
45
- (asset_coordinate.y, asset_coordinate.x),
46
- ).km,
47
- "km",
48
- )
49
-
50
- hypocentral_distance = (hazard_model.depth**2 + epicenter_distance**2) ** 0.5
51
-
52
- mmi = (
53
- 3.31
54
- + 1.28 * hazard_model.magnitude
55
- - 1.42 * math.log10(hypocentral_distance.to("km").magnitude)
56
- ) # Modified Mercalli Intensity
57
-
58
- log_pgv = (mmi - 3.78) / 1.47
59
- pgv = 10**log_pgv # cm/s
60
- self.peak_ground_velocity = SpeedProbability(
61
- speed=Speed(pgv, "centimeter/second"),
62
- survival_probability=1,
63
- )
64
-
65
- log_pga = (mmi - 1.78) / 3.70
66
- pga_per_g = 10**log_pga # % of g
67
- self.peak_ground_acceleration = AccelerationProbability(
68
- acceleration=Acceleration(pga_per_g / 100.0 * 9.80665, "meter/second**2"),
69
- survival_probability=1,
70
- )
71
-
72
- def calculate_fire_vectors(self, asset_coordinate: Point, hazard_model: hz.FireModel):
73
- """
74
- Sources:
75
-
76
- “Integrating Wildfire Risk Modeling with Electric Grid Asset Management” (EPRI)
77
- “Modeling and Mitigating Wildfire Risk to the Power Grid” (PNNL, 2021)
78
- “A Framework for Assessing Wildfire Ignition Risk on Distribution Systems”
79
- (IEEE Transactions on Power Systems, 2020)
80
- Improving Grid Safety and Resilience to Mitigate Ignition Incident and Fire Risks – EPRI
81
- Wildfire Risk Reduction Methods 2024 – EPRI
82
- """
83
-
84
- distances_to_wild_fire_boundaries = []
85
- for wild_fire_area in hazard_model.affected_areas:
86
- if wild_fire_area.affected_area.contains(asset_coordinate):
87
- distance = 0
88
- else:
89
- distance = wild_fire_area.affected_area.exterior.distance(asset_coordinate)
90
- distances_to_wild_fire_boundaries.append(distance)
91
- minimum_distance_km = min(distances_to_wild_fire_boundaries)
92
- self.fire_boundary_dist = DistanceProbability(
93
- distance=Distance(minimum_distance_km, "kilometer"),
94
- survival_probability=1,
95
- )
96
-
97
- # survival_probability = 1 - 0.95 * math.exp(-k * minimum_distance_km)
98
-
99
- def calculate_wind_vectors(self, asset_coordinate: Point, hazard_model: hz.WindModel):
100
- r = Distance(
101
- geodesic(
102
- (hazard_model.center.y, hazard_model.center.x),
103
- (asset_coordinate.y, asset_coordinate.x),
104
- ).km,
105
- "km",
106
- )
107
-
108
- r = r.to("nautical_mile")
109
- r_max = hazard_model.radius_of_closest_isobar.to("nautical_mile")
110
- r_v_max = hazard_model.radius_of_max_wind.to("nautical_mile")
111
- v_max = hazard_model.max_wind_speed.to("knot")
112
-
113
- k = 1.14
114
- b = 10
115
-
116
- a = math.log(b) / (r_max - r_v_max)
117
- m = (1 / r_v_max) * math.log(k / (k + 1))
118
-
119
- if r >= 0 and r < r_v_max:
120
- wind_speed = k * v_max * (1 - math.exp(-m * r))
121
- elif r >= r_v_max and r < r_max:
122
- wind_speed = v_max * math.exp(-a * (r - r_v_max))
123
- else:
124
- wind_speed = 0
125
-
126
- self.wind_speed = SpeedProbability(
127
- speed=wind_speed.to("miles/hour"),
128
- survival_probability=1,
129
- )
130
-
131
- def calculate_flood_vectors(
132
- self, asset_coordinate: Point, hazard_model: hz.FloodModel, asset_total_elevation: Distance
133
- ):
134
- for area in hazard_model.affected_areas:
135
- if area.affected_area.contains(asset_coordinate):
136
- self.flood_depth = DistanceProbability(
137
- distance=area.water_elevation - asset_total_elevation,
138
- survival_probability=1,
139
- )
140
- self.flood_velocity = SpeedProbability(
141
- speed=area.water_velocity,
142
- survival_probability=1,
143
- )
144
- else:
145
- self.flood_depth = DistanceProbability(
146
- distance=Distance(-9999, "meter"),
147
- survival_probability=1,
148
- )
149
- self.flood_velocity = SpeedProbability(
150
- speed=Speed(0, "meter / second"),
151
- survival_probability=1,
152
- )
153
-
154
- @computed_field
155
- @property
156
- def survival_probability(self) -> float:
157
- wind_survival_probability = self.wind_speed.survival_probability if self.wind_speed else 1
158
- water_flow_survival_probability = (
159
- self.flood_velocity.survival_probability if self.flood_velocity else 1
160
- )
161
- water_level_survivial_probability = (
162
- self.flood_depth.survival_probability if self.flood_depth else 1
163
- )
164
- fire_survival_probability = (
165
- self.fire_boundary_dist.survival_probability if self.fire_boundary_dist else 1
166
- )
167
- pgv_survival_probability = (
168
- self.peak_ground_velocity.survival_probability if self.peak_ground_velocity else 1
169
- )
170
- pga_survival_probability = (
171
- self.peak_ground_acceleration.survival_probability
172
- if self.peak_ground_acceleration
173
- else 1
174
- )
175
-
176
- return (
177
- wind_survival_probability
178
- * water_flow_survival_probability
179
- * water_level_survivial_probability
180
- * fire_survival_probability
181
- * pgv_survival_probability
182
- * pga_survival_probability
183
- )
184
-
185
- @classmethod
186
- def example(cls) -> "AssetState":
187
- return AssetState(
188
- timestamp=datetime.now(),
189
- wind_speed=SpeedProbability.example(),
190
- flood_velocity=SpeedProbability.example(),
191
- flood_depth=DistanceProbability.example(),
192
- fire_boundary_dist=DistanceProbability.example(),
193
- peak_ground_velocity=SpeedProbability.example(),
194
- peak_ground_acceleration=AccelerationProbability.example(),
195
- )
196
-
197
-
198
- class Asset(Component):
199
- distribution_asset: UUID = Field(..., description="UUID of the distribution asset")
200
- connections: list[UUID] = Field([], description="List of UUIDs of connected assets")
201
- devices: list[UUID] = Field(
202
- [], description="List of UUIDs of devices associated with the asset"
203
- )
204
- asset_type: AssetTypes
205
- height: Distance = Field(..., gt=0, description="Height of the asset")
206
- latitude: float = Field(..., ge=-90, le=90, description="Latitude in degrees")
207
- longitude: float = Field(..., ge=-180, le=180, description="Longitude in degrees")
208
- asset_state: list[AssetState]
209
-
210
- @computed_field
211
- @property
212
- def elevation(self) -> Distance:
213
- return Distance(get_elevation(lat=self.latitude, lon=self.longitude), "meter")
214
-
215
- def _get_asset_state_at_timestamp(self, timestamp: datetime) -> AssetState | None:
216
- for asset_state in self.asset_state:
217
- if asset_state.timestamp == timestamp:
218
- return asset_state
219
- return AssetState(
220
- timestamp=timestamp,
221
- )
222
-
223
- def update_survival_probability(
224
- self, time_stamp: datetime, hazard_model: hz.BaseDisasterModel, frag_curves
225
- ):
226
- asset_state = self._get_asset_state_at_timestamp(time_stamp)
227
- asset_location = Point(self.longitude, self.latitude)
228
-
229
- if isinstance(hazard_model, hz.EarthQuakeModel):
230
- asset_state.calculate_earthquake_vectors(asset_location, hazard_model)
231
- elif isinstance(hazard_model, hz.FireModel):
232
- asset_state.calculate_fire_vectors(asset_location, hazard_model)
233
- elif isinstance(hazard_model, hz.WindModel):
234
- asset_state.calculate_wind_vectors(asset_location, hazard_model)
235
- elif isinstance(hazard_model, hz.FloodModel):
236
- asset_state.calculate_flood_vectors(
237
- asset_location, hazard_model, self.elevation + self.height
238
- )
239
- else:
240
- raise (f"Unsupported hazard type {hazard_model.__class__.__name__}")
241
-
242
- self.calculate_probabilities(asset_state, frag_curves)
243
-
244
- if asset_state not in self.asset_state:
245
- self.asset_state.append(asset_state)
246
-
247
- def calculate_probabilities(self, asset_state: AssetState, frag_curves):
248
- fields = [
249
- model_field
250
- for model_field in list(asset_state.model_fields.keys())
251
- if model_field not in ["uuid", "name", "timestamp"]
252
- ]
253
- for field in fields:
254
- prob_model = getattr(asset_state, field)
255
- if prob_model and prob_model.survival_probability == 1:
256
- curve = self.get_vadid_curve(frag_curves, field)
257
- if curve is None:
258
- raise Exception(f"No fragility curve found for field - {field}")
259
- prob_inst = curve.prob_model
260
- quantity_name = [
261
- model_field
262
- for model_field in list(prob_model.model_fields.keys())
263
- if model_field not in ["uuid", "name", "survival_probability"]
264
- ][0]
265
- quantity = getattr(prob_model, quantity_name)
266
- prob_model.survival_probability = 1 - prob_inst.probability(quantity)
267
-
268
- def get_vadid_curve(self, frag_curves, field: SyntaxError) -> float:
269
- for frag_curve in frag_curves:
270
- if frag_curve.asset_state_param == field:
271
- for curve in frag_curve.curves:
272
- if curve.asset_type == self.asset_type:
273
- return curve.prob_function
274
-
275
- return None
276
-
277
- @classmethod
278
- def example(cls) -> "Asset":
279
- return Asset(
280
- name="Asset 1",
281
- asset_type=AssetTypes.distribution_poles,
282
- distribution_asset=UUID("123e4567-e89b-12d3-a456-426614174000"),
283
- height=Distance(100, "m"),
284
- latitude=37.7749,
285
- longitude=-122.4194,
286
- asset_state=[AssetState.example()],
287
- )
@@ -1,20 +0,0 @@
1
- from typing import Type, Callable
2
-
3
- from pydantic import BaseModel, ConfigDict
4
- from infrasys import Component
5
-
6
- from erad.enums import AssetTypes
7
-
8
-
9
- class BaseEradScenarioModel(BaseModel):
10
- model_config = ConfigDict(arbitrary_types_allowed=True, extra="forbid")
11
-
12
-
13
- class ComponentFilterModel(BaseEradScenarioModel):
14
- component_type: Type[Component]
15
- component_filter: Callable | None = None
16
-
17
-
18
- class AssetComponentMap(BaseEradScenarioModel):
19
- asset_type: AssetTypes
20
- filters: list[ComponentFilterModel] = []
@@ -1,116 +0,0 @@
1
- from functools import cached_property
2
- from typing import Literal
3
- from pathlib import Path
4
-
5
-
6
- from infrasys import Component, BaseQuantity
7
- from scipy.stats import _continuous_distns
8
- from pydantic import field_validator
9
- import plotly.express as px
10
- import numpy as np
11
-
12
- from erad.probability_builder import ProbabilityFunctionBuilder
13
- from erad.models.asset import AssetState
14
- from erad.enums import AssetTypes
15
- from erad.quantities import Speed
16
-
17
-
18
- FRAGILITY_CURVE_TYPES = [
19
- fc for fc in AssetState.model_fields if fc not in ["name", "uuid", "timestamp"]
20
- ]
21
- SUPPORTED_CONT_DIST = [name for name in dir(_continuous_distns) if not name.startswith("_")]
22
-
23
-
24
- class ProbabilityFunction(Component):
25
- name: str = ""
26
- distribution: Literal[*SUPPORTED_CONT_DIST]
27
- parameters: list[float | BaseQuantity]
28
-
29
- @field_validator("parameters")
30
- def validate_parameters(cls, value):
31
- if not any(isinstance(v, BaseQuantity) for v in value):
32
- raise ValueError("There should be atleast one BaseQuantity in the parameters")
33
-
34
- units = set([v.units for v in value if isinstance(v, BaseQuantity)])
35
- if not len(units) == 1:
36
- raise ValueError("All BaseQuantities should have the same units")
37
- return value
38
-
39
- @cached_property
40
- def prob_model(self) -> ProbabilityFunctionBuilder:
41
- return ProbabilityFunctionBuilder(self.distribution, self.parameters)
42
-
43
- @classmethod
44
- def example(cls) -> "ProbabilityFunction":
45
- return ProbabilityFunction(
46
- distribution="norm",
47
- parameters=[Speed(1.5, "m/s"), 2],
48
- )
49
-
50
-
51
- class FragilityCurve(Component):
52
- name: str = ""
53
- asset_type: AssetTypes
54
- prob_function: ProbabilityFunction
55
-
56
- @classmethod
57
- def example(cls) -> "FragilityCurve":
58
- return FragilityCurve(
59
- asset_type=AssetTypes.substation,
60
- prob_function=ProbabilityFunction.example(),
61
- )
62
-
63
-
64
- class HazardFragilityCurves(Component):
65
- name: str = "DEFAULT_CURVES"
66
- asset_state_param: Literal[*FRAGILITY_CURVE_TYPES]
67
- curves: list[FragilityCurve]
68
-
69
- @classmethod
70
- def example(cls) -> "HazardFragilityCurves":
71
- return HazardFragilityCurves(
72
- asset_state_param="peak_ground_acceleration",
73
- curves=[FragilityCurve.example()],
74
- )
75
-
76
- def plot(
77
- self, file_path: Path, x_min: float = 0, x_max: float = None, number_of_points: int = 100
78
- ):
79
- """Plot the fragility curves."""
80
- file_path = Path(file_path)
81
- assert file_path.suffix.lower() == ".html", "File path should be an HTML file"
82
-
83
- if not self.curves:
84
- raise ValueError("No curves to plot")
85
-
86
- quantities = [
87
- p for p in self.curves[0].prob_function.parameters if isinstance(p, BaseQuantity)
88
- ]
89
-
90
- x_label = self.asset_state_param.replace("_", " ").title()
91
- x = np.linspace(x_min, x_max, number_of_points)
92
-
93
- plot_data = {"x": [], "y": [], "Asset Type": []}
94
-
95
- for curve in self.curves:
96
- model_class = quantities[0].__class__
97
- units = quantities[0].units
98
- prob_model = curve.prob_function.prob_model
99
- y = prob_model.probability(model_class(x, units))
100
- label = curve.asset_type.name.replace("_", " ").title()
101
-
102
- plot_data["x"].extend(x)
103
- plot_data["y"].extend(y)
104
- plot_data["Asset Type"].extend([label] * len(x))
105
-
106
- fig = px.line(
107
- plot_data,
108
- x="x",
109
- y="y",
110
- color="Asset Type",
111
- labels={"x": f"{x_label} [{units}]", "y": "Probability of Failure"},
112
- title=f"Fragility Curves for {x_label}",
113
- )
114
-
115
- fig.show()
116
- fig.write_html(file_path)
@@ -1,5 +0,0 @@
1
- from erad.models.hazard.base_models import BaseDisasterModel
2
- from erad.models.hazard.earthquake import EarthQuakeModel
3
- from erad.models.hazard.wild_fire import FireModel, FireModelArea
4
- from erad.models.hazard.wind import WindModel
5
- from erad.models.hazard.flood import FloodModel, FloodModelArea
@@ -1,12 +0,0 @@
1
- import plotly.graph_objects as go
2
- from pydantic import ConfigDict
3
- from infrasys import Component
4
-
5
-
6
- class BaseDisasterModel(Component):
7
- model_config = ConfigDict(arbitrary_types_allowed=True, extra="forbid")
8
-
9
- def plot(
10
- self, time_index: int, figure: go.Figure, map_obj: go.Scattergeo | go.Scattermap
11
- ) -> int:
12
- raise NotImplementedError("This method should be implemented in the derived classes")
@@ -1,26 +0,0 @@
1
- from pathlib import Path
2
-
3
- from loguru import logger
4
- import requests
5
-
6
-
7
- TEST_PATH = Path(__file__).parents[4] / "tests"
8
- DATA_FOLDER_NAME = "data"
9
- DATA_FOLDER = TEST_PATH / DATA_FOLDER_NAME
10
- DB_FILENAME = "erad_data.sqlite"
11
- ERAD_DB = TEST_PATH / DATA_FOLDER_NAME / DB_FILENAME
12
- BUCKET_NAME = "erad_v2_dataset"
13
-
14
- if not ERAD_DB.exists():
15
- logger.info("Erad database not found. Downloading from Google Cloud.")
16
- ERAD_DB.parent.mkdir(parents=True, exist_ok=True)
17
- url = f"https://storage.googleapis.com/{BUCKET_NAME}/{DB_FILENAME}"
18
- response = requests.get(url)
19
-
20
- with open(ERAD_DB, "wb") as f:
21
- f.write(response.content)
22
- logger.info("Download complete...")
23
-
24
- HISTROIC_EARTHQUAKE_TABLE = "historic_earthquakes"
25
- HISTROIC_HURRICANE_TABLE = "historic_hurricanes"
26
- HISTROIC_FIRE_TABLE = "historic_fires"
@@ -1,93 +0,0 @@
1
- from datetime import datetime
2
- import sqlite3
3
- import os
4
-
5
- from pydantic import field_serializer, field_validator
6
- from gdm.quantities import Distance
7
- from shapely.geometry import Point
8
- import plotly.graph_objects as go
9
- import pandas as pd
10
-
11
- from erad.models.hazard.common import ERAD_DB, HISTROIC_EARTHQUAKE_TABLE
12
- from erad.models.hazard.base_models import BaseDisasterModel
13
-
14
-
15
- class EarthQuakeModel(BaseDisasterModel):
16
- timestamp: datetime
17
- origin: Point
18
- depth: Distance
19
- magnitude: float
20
-
21
- @field_validator("origin", mode="before")
22
- def deserialize_point(cls, value):
23
- if isinstance(value, dict) and value.get("type") == "Point":
24
- coords = value["coordinates"]
25
- return Point(coords)
26
- return value
27
-
28
- @field_serializer("origin")
29
- def serialize_location(self, point: Point, _info):
30
- return {"type": "Point", "coordinates": (point.x, point.y)}
31
-
32
- @classmethod
33
- def example(cls) -> "EarthQuakeModel":
34
- return EarthQuakeModel(
35
- name="earthquake 1",
36
- timestamp=datetime.now(),
37
- origin=Point(-120.93036, 36.60144),
38
- depth=Distance(300, "km"),
39
- magnitude=5.0,
40
- )
41
-
42
- @classmethod
43
- def from_earthquake_code(cls, earthquake_code: str) -> "EarthQuakeModel":
44
- assert os.path.exists(ERAD_DB), f"The data file {ERAD_DB} not found"
45
-
46
- conn = sqlite3.connect(ERAD_DB)
47
- earthquake_data = pd.read_sql(
48
- f"SELECT * FROM {HISTROIC_EARTHQUAKE_TABLE} WHERE ID = '{earthquake_code}'", conn
49
- )
50
- conn.close()
51
-
52
- assert not earthquake_data.empty, f"No earthquake {earthquake_code} found in the database"
53
-
54
- earthquake_data["Date"] = pd.to_datetime(earthquake_data["Date"])
55
- earthquake_data["Time"] = pd.to_datetime(earthquake_data["Time"])
56
- earthquake_data["DateTime"] = earthquake_data.apply(
57
- lambda row: datetime.combine(row["Date"].date(), row["Time"].time()), axis=1
58
- )
59
-
60
- long = earthquake_data.Longitude.values[0]
61
- lat = earthquake_data.Latitude.values[0]
62
-
63
- return cls(
64
- name=earthquake_code,
65
- timestamp=earthquake_data.DateTime.values[0].astype("datetime64[ms]").astype(datetime),
66
- origin=Point(long, lat),
67
- depth=Distance(earthquake_data.Depth.values[0], "km"),
68
- magnitude=earthquake_data.Magnitude.values[0],
69
- )
70
-
71
- def plot(
72
- self,
73
- time_index: int = 0,
74
- figure: go.Figure = go.Figure(),
75
- map_obj: type[go.Scattergeo | go.Scattermap] = go.Scattermap,
76
- ) -> int:
77
- figure.add_trace(
78
- map_obj(
79
- lat=[self.origin.y],
80
- lon=[self.origin.x],
81
- mode="markers",
82
- marker=dict(size=[self.magnitude * 10], color=[self.depth.magnitude], opacity=0.4),
83
- name="Earthquake",
84
- hovertext=[
85
- f"""
86
- <br> <b>Earthquake depth:</b> {self.depth}
87
- <br> <b>Earthquake magnitude:</b> {self.magnitude}
88
- """
89
- ],
90
- visible=(time_index == 0),
91
- )
92
- )
93
- return 1
@@ -1,83 +0,0 @@
1
- from datetime import datetime
2
-
3
- from pydantic import field_serializer, field_validator
4
- from shapely.geometry import Polygon, Point
5
- from gdm.quantities import Distance
6
- import plotly.graph_objects as go
7
- from infrasys import Component
8
-
9
- from erad.models.hazard.base_models import BaseDisasterModel
10
- from erad.quantities import Speed
11
-
12
-
13
- class FloodModelArea(Component):
14
- name: str = ""
15
- affected_area: Polygon
16
- water_velocity: Speed
17
- water_elevation: Distance
18
-
19
- @field_validator("affected_area", mode="before")
20
- def deserialize_polygon(cls, value):
21
- if isinstance(value, dict) and value.get("type") == "Polygon":
22
- points = [Point(c) for c in value["coordinates"]]
23
- return Polygon(points)
24
- return value
25
-
26
- @field_serializer("affected_area")
27
- def serialize_polygon(self, poly: Polygon, _info):
28
- return {"type": "Polygon", "coordinates": list(poly.exterior.coords)}
29
-
30
- @classmethod
31
- def example(cls) -> "FloodModelArea":
32
- return FloodModelArea(
33
- affected_area=Polygon(
34
- [
35
- (-120.93036, 36.60144),
36
- (-120.91072, 36.60206),
37
- (-120.91127, 36.5712),
38
- (-120.93405, 36.58100),
39
- ]
40
- ),
41
- water_velocity=Speed(50, "meter/second"),
42
- water_elevation=Distance(10, "feet"),
43
- )
44
-
45
-
46
- class FloodModel(BaseDisasterModel):
47
- timestamp: datetime
48
- affected_areas: list[FloodModelArea]
49
-
50
- @classmethod
51
- def example(cls) -> "FloodModel":
52
- return FloodModel(
53
- name="flood 1",
54
- timestamp=datetime.now(),
55
- affected_areas=[FloodModelArea.example()],
56
- )
57
-
58
- def plot(
59
- self,
60
- time_index: int = 0,
61
- figure: go.Figure = go.Figure(),
62
- map_obj: type[go.Scattergeo | go.Scattermap] = go.Scattermap,
63
- ) -> int:
64
- for area in self.affected_areas:
65
- lon, lat = area.affected_area.exterior.xy # returns x and y sequences
66
- figure.add_trace(
67
- map_obj(
68
- lon=lon.tolist(),
69
- lat=lat.tolist(),
70
- mode="markers+lines+text",
71
- fill="toself",
72
- marker={
73
- "size": 10,
74
- "color": [area.water_elevation.magnitude],
75
- },
76
- hovertemplate=f"""
77
- <br> <b>Water velocity:</b> {area.water_velocity}
78
- <br> <b>Water elevation:</b> {area.water_elevation}
79
- """,
80
- visible=(time_index == 0),
81
- )
82
- )
83
- return len(self.affected_areas)