NREL-erad 0.0.0a0__py3-none-any.whl → 0.1.1__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.
- erad/__init__.py +1 -0
- erad/constants.py +80 -11
- erad/default_fragility_curves/__init__.py +15 -0
- erad/default_fragility_curves/default_fire_boundary_dist.py +94 -0
- erad/default_fragility_curves/default_flood_depth.py +108 -0
- erad/default_fragility_curves/default_flood_velocity.py +101 -0
- erad/default_fragility_curves/default_fragility_curves.py +23 -0
- erad/default_fragility_curves/default_peak_ground_acceleration.py +163 -0
- erad/default_fragility_curves/default_peak_ground_velocity.py +94 -0
- erad/default_fragility_curves/default_wind_speed.py +94 -0
- erad/enums.py +40 -0
- erad/gdm_mapping.py +83 -0
- erad/models/__init__.py +1 -0
- erad/models/asset.py +300 -0
- erad/models/asset_mapping.py +20 -0
- erad/models/edit_store.py +22 -0
- erad/models/fragility_curve.py +116 -0
- erad/models/hazard/__init__.py +5 -0
- erad/models/hazard/base_models.py +12 -0
- erad/models/hazard/common.py +26 -0
- erad/models/hazard/earthquake.py +93 -0
- erad/models/hazard/flood.py +83 -0
- erad/models/hazard/wild_fire.py +121 -0
- erad/models/hazard/wind.py +143 -0
- erad/models/probability.py +76 -0
- erad/probability_builder.py +38 -0
- erad/quantities.py +31 -0
- erad/runner.py +125 -0
- erad/systems/__init__.py +2 -0
- erad/systems/asset_system.py +462 -0
- erad/systems/hazard_system.py +122 -0
- nrel_erad-0.1.1.dist-info/METADATA +61 -0
- nrel_erad-0.1.1.dist-info/RECORD +36 -0
- {NREL_erad-0.0.0a0.dist-info → nrel_erad-0.1.1.dist-info}/WHEEL +1 -1
- NREL_erad-0.0.0a0.dist-info/METADATA +0 -61
- NREL_erad-0.0.0a0.dist-info/RECORD +0 -42
- erad/cypher_queries/load_data_v1.cypher +0 -212
- erad/data/World_Earthquakes_1960_2016.csv +0 -23410
- erad/db/__init__.py +0 -0
- erad/db/assets/__init__.py +0 -0
- erad/db/assets/critical_infras.py +0 -171
- erad/db/assets/distribution_lines.py +0 -101
- erad/db/credential_model.py +0 -20
- erad/db/disaster_input_model.py +0 -23
- erad/db/inject_earthquake.py +0 -52
- erad/db/inject_flooding.py +0 -53
- erad/db/neo4j_.py +0 -162
- erad/db/utils.py +0 -14
- erad/exceptions.py +0 -68
- erad/metrics/__init__.py +0 -0
- erad/metrics/check_microgrid.py +0 -208
- erad/metrics/metric.py +0 -178
- erad/programs/__init__.py +0 -0
- erad/programs/backup.py +0 -62
- erad/programs/microgrid.py +0 -45
- erad/scenarios/__init__.py +0 -0
- erad/scenarios/abstract_scenario.py +0 -103
- erad/scenarios/common.py +0 -93
- erad/scenarios/earthquake_scenario.py +0 -161
- erad/scenarios/fire_scenario.py +0 -160
- erad/scenarios/flood_scenario.py +0 -494
- erad/scenarios/utilities.py +0 -76
- erad/scenarios/wind_scenario.py +0 -89
- erad/utils/__init__.py +0 -0
- erad/utils/ditto_utils.py +0 -252
- erad/utils/hifld_utils.py +0 -147
- erad/utils/opendss_utils.py +0 -357
- erad/utils/overpass.py +0 -76
- erad/utils/util.py +0 -178
- erad/visualization/__init__.py +0 -0
- erad/visualization/plot_graph.py +0 -218
- {NREL_erad-0.0.0a0.dist-info → nrel_erad-0.1.1.dist-info/licenses}/LICENSE.txt +0 -0
- {NREL_erad-0.0.0a0.dist-info → nrel_erad-0.1.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,116 @@
|
|
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)
|
@@ -0,0 +1,5 @@
|
|
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
|
@@ -0,0 +1,12 @@
|
|
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")
|
@@ -0,0 +1,26 @@
|
|
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"
|
@@ -0,0 +1,93 @@
|
|
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
|
@@ -0,0 +1,83 @@
|
|
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)
|
@@ -0,0 +1,121 @@
|
|
1
|
+
from datetime import datetime
|
2
|
+
import sqlite3
|
3
|
+
import os
|
4
|
+
|
5
|
+
from shapely.geometry import MultiPolygon, Polygon, Point
|
6
|
+
from pydantic import field_serializer, field_validator
|
7
|
+
import plotly.graph_objects as go
|
8
|
+
from gdm.quantities import Angle
|
9
|
+
from infrasys import Component
|
10
|
+
from shapely import wkb
|
11
|
+
import pandas as pd
|
12
|
+
|
13
|
+
from erad.models.hazard.common import ERAD_DB, HISTROIC_FIRE_TABLE
|
14
|
+
from erad.models.hazard.base_models import BaseDisasterModel
|
15
|
+
from erad.quantities import Speed
|
16
|
+
|
17
|
+
|
18
|
+
class FireModelArea(Component):
|
19
|
+
name: str = ""
|
20
|
+
affected_area: Polygon
|
21
|
+
wind_speed: Speed
|
22
|
+
wind_direction: Angle
|
23
|
+
|
24
|
+
@field_validator("affected_area", mode="before")
|
25
|
+
def deserialize_polygon(cls, value):
|
26
|
+
if isinstance(value, dict) and value.get("type") == "Polygon":
|
27
|
+
points = [Point(c) for c in value["coordinates"]]
|
28
|
+
return Polygon(points)
|
29
|
+
return value
|
30
|
+
|
31
|
+
@field_serializer("affected_area")
|
32
|
+
def serialize_polygon(self, poly: Polygon, _info):
|
33
|
+
return {"type": "Polygon", "coordinates": list(poly.exterior.coords)}
|
34
|
+
|
35
|
+
@classmethod
|
36
|
+
def example(cls) -> "FireModelArea":
|
37
|
+
return FireModelArea(
|
38
|
+
affected_area=Polygon(
|
39
|
+
[
|
40
|
+
(-120.93036, 36.60144),
|
41
|
+
(-120.91072, 36.60206),
|
42
|
+
(-120.91127, 36.5712),
|
43
|
+
(-120.93405, 36.58100),
|
44
|
+
]
|
45
|
+
),
|
46
|
+
wind_speed=Speed(50, "miles/hour"),
|
47
|
+
wind_direction=Angle(45, "deg"),
|
48
|
+
)
|
49
|
+
|
50
|
+
|
51
|
+
class FireModel(BaseDisasterModel):
|
52
|
+
timestamp: datetime
|
53
|
+
affected_areas: list[FireModelArea]
|
54
|
+
|
55
|
+
@classmethod
|
56
|
+
def example(cls) -> "FireModel":
|
57
|
+
return FireModel(
|
58
|
+
name="fire 1",
|
59
|
+
timestamp=datetime.now(),
|
60
|
+
affected_areas=[FireModelArea.example()],
|
61
|
+
)
|
62
|
+
|
63
|
+
@classmethod
|
64
|
+
def from_wildfire_name(cls, wildfire_name: str) -> "FireModel":
|
65
|
+
assert os.path.exists(ERAD_DB), f"The data file {ERAD_DB} not found"
|
66
|
+
conn = sqlite3.connect(ERAD_DB)
|
67
|
+
fire_data = pd.read_sql(
|
68
|
+
f"SELECT * FROM {HISTROIC_FIRE_TABLE} WHERE firename = '{wildfire_name}';", conn
|
69
|
+
)
|
70
|
+
if fire_data.empty:
|
71
|
+
raise ValueError(
|
72
|
+
f"Fire '{wildfire_name}' not found in table '{HISTROIC_FIRE_TABLE}' in the database"
|
73
|
+
)
|
74
|
+
conn.close()
|
75
|
+
fire_data["discoverydatetime"] = pd.to_datetime(fire_data["discoverydatetime"])
|
76
|
+
geometry: MultiPolygon = [wkb.loads(g) for g in fire_data.GEOMETRY][0]
|
77
|
+
areas = []
|
78
|
+
for i, poly in enumerate(geometry.geoms):
|
79
|
+
areas.append(
|
80
|
+
FireModelArea(
|
81
|
+
affected_area=poly,
|
82
|
+
wind_speed=Speed(-999, "miles/hour"),
|
83
|
+
wind_direction=Angle(0, "deg"),
|
84
|
+
)
|
85
|
+
)
|
86
|
+
return cls(
|
87
|
+
name=wildfire_name,
|
88
|
+
timestamp=fire_data["discoverydatetime"]
|
89
|
+
.values[0]
|
90
|
+
.astype("datetime64[ms]")
|
91
|
+
.astype(datetime),
|
92
|
+
affected_areas=areas,
|
93
|
+
)
|
94
|
+
|
95
|
+
def plot(
|
96
|
+
self,
|
97
|
+
time_index: int = 0,
|
98
|
+
figure: go.Figure = go.Figure(),
|
99
|
+
map_obj: type[go.Scattergeo | go.Scattermap] = go.Scattermap,
|
100
|
+
) -> int:
|
101
|
+
for area in self.affected_areas:
|
102
|
+
lon, lat = area.affected_area.exterior.xy # returns x and y sequences
|
103
|
+
figure.add_trace(
|
104
|
+
map_obj(
|
105
|
+
lon=lon.tolist(),
|
106
|
+
lat=lat.tolist(),
|
107
|
+
mode="markers+lines+text",
|
108
|
+
fill="toself",
|
109
|
+
marker={
|
110
|
+
"size": 10,
|
111
|
+
"color": [area.wind_speed.magnitude],
|
112
|
+
},
|
113
|
+
hovertemplate=f"""
|
114
|
+
<br> <b>Wind speed:</b> {area.wind_speed}
|
115
|
+
<br> <b>Wind direction:</b> {area.wind_direction}
|
116
|
+
""",
|
117
|
+
hoverinfo="text",
|
118
|
+
visible=(time_index == 0),
|
119
|
+
)
|
120
|
+
)
|
121
|
+
return len(self.affected_areas)
|
@@ -0,0 +1,143 @@
|
|
1
|
+
from datetime import datetime
|
2
|
+
import sqlite3
|
3
|
+
import os
|
4
|
+
|
5
|
+
from pydantic import field_serializer, field_validator
|
6
|
+
from infrasys.quantities import Distance
|
7
|
+
from shapely.geometry import Point
|
8
|
+
import plotly.graph_objects as go
|
9
|
+
import geopandas as gpd
|
10
|
+
import pandas as pd
|
11
|
+
|
12
|
+
from erad.models.hazard.common import ERAD_DB, HISTROIC_HURRICANE_TABLE
|
13
|
+
from erad.models.hazard.base_models import BaseDisasterModel
|
14
|
+
from erad.quantities import Speed, Pressure
|
15
|
+
|
16
|
+
|
17
|
+
class WindModel(BaseDisasterModel):
|
18
|
+
timestamp: datetime
|
19
|
+
center: Point
|
20
|
+
max_wind_speed: Speed
|
21
|
+
radius_of_max_wind: Distance
|
22
|
+
radius_of_closest_isobar: Distance
|
23
|
+
air_pressure: Pressure
|
24
|
+
|
25
|
+
@field_validator("center", mode="before")
|
26
|
+
def deserialize_point(cls, value):
|
27
|
+
if isinstance(value, dict) and value.get("type") == "Point":
|
28
|
+
coords = value["coordinates"]
|
29
|
+
return Point(coords)
|
30
|
+
return value
|
31
|
+
|
32
|
+
@field_serializer("center")
|
33
|
+
def serialize_location(self, point: Point, _info):
|
34
|
+
return {"type": "Point", "coordinates": (point.x, point.y)}
|
35
|
+
|
36
|
+
@classmethod
|
37
|
+
def example(cls) -> "WindModel":
|
38
|
+
return WindModel(
|
39
|
+
name="hurricane 1",
|
40
|
+
timestamp=datetime.now(),
|
41
|
+
center=Point(-121.93036, 36.60144),
|
42
|
+
max_wind_speed=Speed(50, "miles/hour"),
|
43
|
+
air_pressure=Pressure(1013.25, "hPa"),
|
44
|
+
radius_of_max_wind=Distance(50, "miles"),
|
45
|
+
radius_of_closest_isobar=Distance(300, "miles"),
|
46
|
+
)
|
47
|
+
|
48
|
+
@classmethod
|
49
|
+
def from_hurricane_sid(cls, hurricane_sid: str) -> list["WindModel"]:
|
50
|
+
assert os.path.exists(ERAD_DB), f"The data file {ERAD_DB} not found"
|
51
|
+
conn = sqlite3.connect(ERAD_DB)
|
52
|
+
hurricane_data = pd.read_sql(
|
53
|
+
f"SELECT * FROM {HISTROIC_HURRICANE_TABLE} WHERE `SID ` = '{hurricane_sid}';", conn
|
54
|
+
)
|
55
|
+
cols = [
|
56
|
+
"LAT (degrees_north)",
|
57
|
+
"LON (degrees_east)",
|
58
|
+
"USA_WIND (kts)",
|
59
|
+
"USA_ROCI (nmile)",
|
60
|
+
"USA_RMW (nmile)",
|
61
|
+
"USA_POCI (mb)",
|
62
|
+
"ISO_TIME ",
|
63
|
+
]
|
64
|
+
hurricane_data = hurricane_data[cols]
|
65
|
+
for col in cols:
|
66
|
+
hurricane_data = hurricane_data[hurricane_data[col] != " "]
|
67
|
+
if hurricane_data.empty:
|
68
|
+
raise ValueError(
|
69
|
+
f"Hurricane '{hurricane_sid}' not found in column 'SID', table '{HISTROIC_HURRICANE_TABLE}' in the database"
|
70
|
+
)
|
71
|
+
conn.close()
|
72
|
+
geometry = [
|
73
|
+
Point(lat, lon)
|
74
|
+
for lat, lon in zip(
|
75
|
+
hurricane_data["LAT (degrees_north)"], hurricane_data["LON (degrees_east)"]
|
76
|
+
)
|
77
|
+
]
|
78
|
+
hurricane_data["ISO_TIME "] = pd.to_datetime(hurricane_data["ISO_TIME "])
|
79
|
+
hurricane_data = gpd.GeoDataFrame(hurricane_data, geometry=geometry)
|
80
|
+
hurricane_data.set_crs("epsg:4326")
|
81
|
+
track = []
|
82
|
+
for idx, row in hurricane_data.iterrows():
|
83
|
+
track.append(
|
84
|
+
WindModel(
|
85
|
+
name=hurricane_sid,
|
86
|
+
timestamp=row["ISO_TIME "],
|
87
|
+
center=row["geometry"],
|
88
|
+
max_wind_speed=Speed(float(row["USA_WIND (kts)"]), "knots"),
|
89
|
+
radius_of_max_wind=Distance(float(row["USA_RMW (nmile)"]), "nautical_mile"),
|
90
|
+
radius_of_closest_isobar=Distance(
|
91
|
+
float(row["USA_ROCI (nmile)"]), "nautical_mile"
|
92
|
+
),
|
93
|
+
air_pressure=Pressure(float(row["USA_POCI (mb)"]), "millibar"),
|
94
|
+
)
|
95
|
+
)
|
96
|
+
|
97
|
+
return track
|
98
|
+
|
99
|
+
def plot(
|
100
|
+
self,
|
101
|
+
time_index: int = 0,
|
102
|
+
figure: go.Figure = go.Figure(),
|
103
|
+
map_obj: type[go.Scattergeo | go.Scattermap] = go.Scattermap,
|
104
|
+
) -> int:
|
105
|
+
figure.add_trace(
|
106
|
+
map_obj(
|
107
|
+
lat=[self.center.x],
|
108
|
+
lon=[self.center.y],
|
109
|
+
mode="markers",
|
110
|
+
marker=dict(
|
111
|
+
size=[self.radius_of_closest_isobar.magnitude / 5],
|
112
|
+
color="lightblue",
|
113
|
+
opacity=0.4,
|
114
|
+
),
|
115
|
+
name="Radius of closest isobar", # Name for the legend
|
116
|
+
visible=(time_index == 0),
|
117
|
+
)
|
118
|
+
)
|
119
|
+
|
120
|
+
figure.add_trace(
|
121
|
+
map_obj(
|
122
|
+
lat=[self.center.x],
|
123
|
+
lon=[self.center.y],
|
124
|
+
mode="markers",
|
125
|
+
marker=dict(
|
126
|
+
size=[self.radius_of_max_wind.magnitude / 5],
|
127
|
+
color=[self.max_wind_speed.magnitude],
|
128
|
+
showscale=False,
|
129
|
+
opacity=0.4,
|
130
|
+
),
|
131
|
+
visible=(time_index == 0),
|
132
|
+
hovertext=[
|
133
|
+
f"""
|
134
|
+
<br> <b>Max wind speed:</b> {self.max_wind_speed}
|
135
|
+
<br> <b>Radius of max wind speed:</b> {self.radius_of_max_wind}
|
136
|
+
<br> <b>Radius of closest isobar:</b> {self.radius_of_closest_isobar}
|
137
|
+
<br> <b>Air pressure:</b> {self.air_pressure}
|
138
|
+
"""
|
139
|
+
],
|
140
|
+
name="Radius of max wind", # Name for the legend
|
141
|
+
)
|
142
|
+
)
|
143
|
+
return 2
|
@@ -0,0 +1,76 @@
|
|
1
|
+
from typing import Annotated
|
2
|
+
|
3
|
+
from gdm.quantities import Distance
|
4
|
+
from infrasys import Component
|
5
|
+
from pydantic import Field
|
6
|
+
from abc import ABC
|
7
|
+
|
8
|
+
from erad.quantities import Speed, Acceleration, Temperature
|
9
|
+
|
10
|
+
|
11
|
+
class BaseProbabilityModel(Component, ABC):
|
12
|
+
name: str = ""
|
13
|
+
survival_probability: Annotated[
|
14
|
+
float,
|
15
|
+
Field(1.0, ge=0, le=1, description="Asset survival probability"),
|
16
|
+
]
|
17
|
+
|
18
|
+
|
19
|
+
class SpeedProbability(BaseProbabilityModel):
|
20
|
+
speed: Annotated[
|
21
|
+
Speed,
|
22
|
+
Field(
|
23
|
+
...,
|
24
|
+
description="Represents the speed of a scenario parameter experienced by the asset e.g speed of wind",
|
25
|
+
),
|
26
|
+
]
|
27
|
+
|
28
|
+
@classmethod
|
29
|
+
def example(cls) -> "SpeedProbability":
|
30
|
+
return SpeedProbability(
|
31
|
+
speed=Speed(50, "m/s"),
|
32
|
+
survival_probability=1.0,
|
33
|
+
)
|
34
|
+
|
35
|
+
|
36
|
+
class TemperatureProbability(BaseProbabilityModel):
|
37
|
+
temperature: Annotated[
|
38
|
+
Temperature,
|
39
|
+
Field(..., description="Temperature of the asset"),
|
40
|
+
]
|
41
|
+
|
42
|
+
@classmethod
|
43
|
+
def example(cls) -> "TemperatureProbability":
|
44
|
+
return TemperatureProbability(
|
45
|
+
temperature=Temperature(0, "degC"),
|
46
|
+
survival_probability=1.0,
|
47
|
+
)
|
48
|
+
|
49
|
+
|
50
|
+
class DistanceProbability(BaseProbabilityModel):
|
51
|
+
distance: Annotated[
|
52
|
+
Distance,
|
53
|
+
Field(
|
54
|
+
Distance(-9999, "m"),
|
55
|
+
description="Distance of asset from the source / boundary of a disaster event",
|
56
|
+
),
|
57
|
+
]
|
58
|
+
|
59
|
+
@classmethod
|
60
|
+
def example(cls) -> "DistanceProbability":
|
61
|
+
return DistanceProbability(
|
62
|
+
distance=Distance(0, "m"),
|
63
|
+
survival_probability=1.0,
|
64
|
+
)
|
65
|
+
|
66
|
+
|
67
|
+
class AccelerationProbability(BaseProbabilityModel):
|
68
|
+
name: str = ""
|
69
|
+
acceleration: Acceleration = Acceleration(0, "m/s**2")
|
70
|
+
|
71
|
+
@classmethod
|
72
|
+
def example(cls) -> "AccelerationProbability":
|
73
|
+
return AccelerationProbability(
|
74
|
+
acceleration=Acceleration(0, "m/s**2"),
|
75
|
+
survival_probability=1.0,
|
76
|
+
)
|