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.
- erad/__init__.py +1 -1
- erad/constants.py +20 -68
- erad/cypher_queries/load_data_v1.cypher +212 -0
- erad/data/World_Earthquakes_1960_2016.csv +23410 -0
- erad/db/__init__.py +0 -0
- erad/db/assets/__init__.py +0 -0
- erad/db/assets/critical_infras.py +171 -0
- erad/db/assets/distribution_lines.py +101 -0
- erad/db/credential_model.py +20 -0
- erad/db/disaster_input_model.py +23 -0
- erad/db/inject_earthquake.py +52 -0
- erad/db/inject_flooding.py +53 -0
- erad/db/neo4j_.py +162 -0
- erad/db/utils.py +14 -0
- erad/exceptions.py +68 -0
- erad/metrics/__init__.py +0 -0
- erad/metrics/check_microgrid.py +208 -0
- erad/metrics/metric.py +178 -0
- erad/programs/__init__.py +0 -0
- erad/programs/backup.py +62 -0
- erad/programs/microgrid.py +45 -0
- erad/scenarios/__init__.py +0 -0
- erad/scenarios/abstract_scenario.py +103 -0
- erad/scenarios/common.py +93 -0
- erad/scenarios/earthquake_scenario.py +161 -0
- erad/scenarios/fire_scenario.py +160 -0
- erad/scenarios/flood_scenario.py +494 -0
- erad/scenarios/flows.csv +671 -0
- erad/scenarios/utilities.py +76 -0
- erad/scenarios/wind_scenario.py +89 -0
- erad/utils/__init__.py +0 -0
- erad/utils/ditto_utils.py +252 -0
- erad/utils/hifld_utils.py +147 -0
- erad/utils/opendss_utils.py +357 -0
- erad/utils/overpass.py +76 -0
- erad/utils/util.py +178 -0
- erad/visualization/__init__.py +0 -0
- erad/visualization/plot_graph.py +218 -0
- {nrel_erad-0.1.0.dist-info → nrel_erad-1.0.0.dist-info}/METADATA +39 -29
- nrel_erad-1.0.0.dist-info/RECORD +42 -0
- {nrel_erad-0.1.0.dist-info → nrel_erad-1.0.0.dist-info}/WHEEL +1 -2
- {nrel_erad-0.1.0.dist-info → nrel_erad-1.0.0.dist-info}/licenses/LICENSE.txt +28 -28
- erad/default_fragility_curves/__init__.py +0 -15
- erad/default_fragility_curves/default_fire_boundary_dist.py +0 -94
- erad/default_fragility_curves/default_flood_depth.py +0 -108
- erad/default_fragility_curves/default_flood_velocity.py +0 -101
- erad/default_fragility_curves/default_fragility_curves.py +0 -23
- erad/default_fragility_curves/default_peak_ground_acceleration.py +0 -163
- erad/default_fragility_curves/default_peak_ground_velocity.py +0 -94
- erad/default_fragility_curves/default_wind_speed.py +0 -94
- erad/enums.py +0 -40
- erad/gdm_mapping.py +0 -83
- erad/models/__init__.py +0 -1
- erad/models/asset.py +0 -287
- erad/models/asset_mapping.py +0 -20
- erad/models/fragility_curve.py +0 -116
- erad/models/hazard/__init__.py +0 -5
- erad/models/hazard/base_models.py +0 -12
- erad/models/hazard/common.py +0 -26
- erad/models/hazard/earthquake.py +0 -93
- erad/models/hazard/flood.py +0 -83
- erad/models/hazard/wild_fire.py +0 -121
- erad/models/hazard/wind.py +0 -143
- erad/models/probability.py +0 -73
- erad/probability_builder.py +0 -35
- erad/quantities.py +0 -25
- erad/runner.py +0 -122
- erad/systems/__init__.py +0 -2
- erad/systems/asset_system.py +0 -414
- erad/systems/hazard_system.py +0 -122
- nrel_erad-0.1.0.dist-info/RECORD +0 -35
- nrel_erad-0.1.0.dist-info/top_level.txt +0 -1
@@ -0,0 +1,76 @@
|
|
1
|
+
from shapely.geometry import MultiPolygon, Point, LineString
|
2
|
+
from shapely.ops import nearest_points
|
3
|
+
import matplotlib.pyplot as plt
|
4
|
+
import scipy.stats as stats
|
5
|
+
import geopy.distance
|
6
|
+
import numpy as np
|
7
|
+
import stateplane
|
8
|
+
|
9
|
+
|
10
|
+
class GeoUtilities:
|
11
|
+
|
12
|
+
@property
|
13
|
+
def identify_stateplane_projection(self) -> str:
|
14
|
+
""" Automatically identifies stateplane projection ID """
|
15
|
+
x = self.centroid.x
|
16
|
+
y = self.centroid.y
|
17
|
+
return stateplane.identify(x, y)
|
18
|
+
|
19
|
+
def in_polygon(self, point : Point) -> bool:
|
20
|
+
return self.multipolygon.contains(point)
|
21
|
+
|
22
|
+
def distance_from_boundary(self, point : Point) -> float:
|
23
|
+
""" Calculates distance of a point to polygon boundary. Correct calculations require conversion to cartesian coordinates"""
|
24
|
+
if self.multipolygon.contains(point):
|
25
|
+
p1, p2 = nearest_points(self.boundary, point)
|
26
|
+
else:
|
27
|
+
p1, p2 = nearest_points(self.multipolygon, point)
|
28
|
+
coords_1 = (p1.y, p1.x)
|
29
|
+
coords_2 = (p2.y, p2.x)
|
30
|
+
return geopy.distance.geodesic(coords_1, coords_2).km
|
31
|
+
|
32
|
+
def distance_from_centroid(self, point : Point):
|
33
|
+
""" Calculates distance of a point to polygon centroid. Correct calculations require conversion to cartesian coordinates """
|
34
|
+
coords_1 = (self.centroid.y, self.centroid.x)
|
35
|
+
coords_2 = (point.y, point.x)
|
36
|
+
return geopy.distance.geodesic(coords_1, coords_2).km
|
37
|
+
|
38
|
+
|
39
|
+
|
40
|
+
class ProbabilityFunctionBuilder:
|
41
|
+
"""Class containing utility fuctions for sceario definations."""
|
42
|
+
|
43
|
+
|
44
|
+
def __init__(self, dist, params):
|
45
|
+
"""Constructor for BaseScenario class.
|
46
|
+
|
47
|
+
Args:
|
48
|
+
dist (str): Name of teh distribution. Should follow Scipy naming convention
|
49
|
+
params (list): A list of parameters for the chosen distribution function. See Scipy.stats documentation
|
50
|
+
"""
|
51
|
+
|
52
|
+
self.dist = getattr(stats, dist)
|
53
|
+
self.params = params
|
54
|
+
return
|
55
|
+
|
56
|
+
def sample(self):
|
57
|
+
"""Sample the distribution """
|
58
|
+
return self.dist.rvs(*self.params, size=1)[0]
|
59
|
+
|
60
|
+
def plot_cdf(self, x:np.linspace, ax =None, label="") -> None:
|
61
|
+
"""Plot the cumalative distribution fuction"""
|
62
|
+
cdf = self.dist.cdf
|
63
|
+
if ax is None:
|
64
|
+
plt.plot(x,cdf(x, *self.params), label=label)
|
65
|
+
else:
|
66
|
+
ax.plot(x,cdf(x, *self.params), label=label)
|
67
|
+
|
68
|
+
|
69
|
+
def probability(self, value: float) -> float:
|
70
|
+
"""Calculates survival probability of a given asset.
|
71
|
+
|
72
|
+
Args:
|
73
|
+
value (float): value for vetor of interest. Will change with scenarions
|
74
|
+
"""
|
75
|
+
cdf = self.dist.cdf
|
76
|
+
return cdf(value, *self.params)
|
@@ -0,0 +1,89 @@
|
|
1
|
+
from erad.constants import FIRE_HISTORIC_GEODATAFRAME_PATH, DATA_FOLDER
|
2
|
+
from shapely.geometry import MultiPolygon, Point, LineString
|
3
|
+
from erad.scenarios.utilities import ProbabilityFunctionBuilder
|
4
|
+
from erad.scenarios.abstract_scenario import BaseScenario
|
5
|
+
from erad.exceptions import FeatureNotImplementedError
|
6
|
+
from erad.scenarios.utilities import GeoUtilities
|
7
|
+
import matplotlib.pyplot as plt
|
8
|
+
from datetime import datetime
|
9
|
+
import geopandas as gpd
|
10
|
+
import numpy as np
|
11
|
+
import random
|
12
|
+
import pyproj
|
13
|
+
import os
|
14
|
+
|
15
|
+
from erad.scenarios.common import AssetTypes
|
16
|
+
from erad.scenarios.utilities import ProbabilityFunctionBuilder
|
17
|
+
|
18
|
+
|
19
|
+
class WindScenario(BaseScenario, GeoUtilities):
|
20
|
+
"""Base class for FireScenario. Extends BaseScenario and GeoUtilities
|
21
|
+
|
22
|
+
Attributes:
|
23
|
+
multipolygon (MultiPolygon): MultiPolygon enclosing wild fire regions
|
24
|
+
probability_model (dict): Dictionary mapping asset types to probability funcitons
|
25
|
+
timestamp (datetime): Scenario occurance time
|
26
|
+
"""
|
27
|
+
|
28
|
+
fragility_curves = {
|
29
|
+
#Extending energy system modelling to include extreme weather risks and application to hurricane events in Puerto Rico
|
30
|
+
AssetTypes.substation.name : ProbabilityFunctionBuilder("lognorm", [0.8, 10, 5]),
|
31
|
+
AssetTypes.solar_panels.name : ProbabilityFunctionBuilder("lognorm", [0.8, 10, 5]),
|
32
|
+
AssetTypes.buried_lines.name : ProbabilityFunctionBuilder("lognorm", [0.8, 10, 5]),
|
33
|
+
AssetTypes.wind_turbines.name : ProbabilityFunctionBuilder("norm", [0.8, 10, 5]),
|
34
|
+
#AssetTypes.battery_storage.name : ProbabilityFunctionBuilder("lognorm", [0.8, 10, 5]),
|
35
|
+
#AssetTypes.transmission_poles.name : ProbabilityFunctionBuilder("lognorm", [0.8, 10, 5]),
|
36
|
+
AssetTypes.distribution_poles.name : ProbabilityFunctionBuilder("lognorm", [0.8, 10, 5]),
|
37
|
+
# AssetTypes.transmission_overhead_lines.name : ProbabilityFunctionBuilder("lognorm", [0.8, 10, 5]),
|
38
|
+
AssetTypes.distribution_overhead_lines.name : ProbabilityFunctionBuilder("beta", [0.8, 10, 5]),
|
39
|
+
}
|
40
|
+
|
41
|
+
def __init__(self, multipolygon : MultiPolygon , probability_model : dict, timestamp : datetime) -> None:
|
42
|
+
"""Constructor for FireScenario.
|
43
|
+
|
44
|
+
Args:
|
45
|
+
multipolygon (MultiPolygon): MultiPolygon enclosing wild fire regions
|
46
|
+
probability_model (dict): Dictionary mapping asset types to probability funcitons
|
47
|
+
timestamp (datetime): Scenario occurance time
|
48
|
+
"""
|
49
|
+
|
50
|
+
super(WindScenario, self).__init__(multipolygon, probability_model, timestamp)
|
51
|
+
return
|
52
|
+
|
53
|
+
@property
|
54
|
+
def area(self) -> float:
|
55
|
+
"""Method to calculate area of affected region."""
|
56
|
+
geod = pyproj.Geod(ellps="WGS84")
|
57
|
+
area = abs(geod.geometry_area_perimeter(self.polygon)[0])
|
58
|
+
return area
|
59
|
+
|
60
|
+
@property
|
61
|
+
def polygon(self) -> MultiPolygon:
|
62
|
+
"""Method to return polygon for the affected region."""
|
63
|
+
return self.multipolygon
|
64
|
+
|
65
|
+
@property
|
66
|
+
def boundary(self) -> LineString:
|
67
|
+
"""Method to return boundary for the affected region."""
|
68
|
+
return self.multipolygon.boundary
|
69
|
+
|
70
|
+
@property
|
71
|
+
def centroid(self) -> Point:
|
72
|
+
"""Method to return the centroid of the affected region."""
|
73
|
+
return self.polygon.centroid
|
74
|
+
|
75
|
+
def increment_time(self):
|
76
|
+
"""Method to increment simulation time for time evolviong scenarios."""
|
77
|
+
raise FeatureNotImplementedError()
|
78
|
+
|
79
|
+
def calculate_survival_probability(self, assets : dict, timestamp : datetime, plot: bool) -> dict:
|
80
|
+
"""Method to calculate survival probaility of asset types.
|
81
|
+
|
82
|
+
Args:
|
83
|
+
assets (dict): The dictionary of all assets and their corresponding asset types
|
84
|
+
plot (bool): Set to true to plot the fire survival model
|
85
|
+
"""
|
86
|
+
return assets
|
87
|
+
|
88
|
+
|
89
|
+
|
erad/utils/__init__.py
ADDED
File without changes
|
@@ -0,0 +1,252 @@
|
|
1
|
+
""" Utility functions for dealing with SMART DS dataset.
|
2
|
+
|
3
|
+
Examples:
|
4
|
+
|
5
|
+
>>> from erad import ditto_utils
|
6
|
+
>>> ditto_utils.download_smartds_data('P4R', '.')
|
7
|
+
|
8
|
+
"""
|
9
|
+
|
10
|
+
# standard libraries
|
11
|
+
from pathlib import Path
|
12
|
+
import shutil
|
13
|
+
import logging
|
14
|
+
from typing import List
|
15
|
+
|
16
|
+
# third-party libraries
|
17
|
+
import boto3
|
18
|
+
from botocore import UNSIGNED
|
19
|
+
from botocore.config import Config
|
20
|
+
from ditto.store import Store
|
21
|
+
from ditto.readers.opendss.read import Reader
|
22
|
+
from ditto.network.network import Network
|
23
|
+
from ditto.models.power_source import PowerSource
|
24
|
+
import networkx as nx
|
25
|
+
from networkx.readwrite import json_graph
|
26
|
+
|
27
|
+
|
28
|
+
# internal libraries
|
29
|
+
from erad.constants import SMARTDS_VALID_AREAS, SMARTDS_VALID_YEARS
|
30
|
+
from erad.exceptions import SMARTDSInvalidInput, DittoException
|
31
|
+
from erad.utils.util import timeit, write_file, path_validation
|
32
|
+
from erad.utils.util import read_file, setup_logging
|
33
|
+
|
34
|
+
|
35
|
+
logger = logging.getLogger(__name__)
|
36
|
+
|
37
|
+
|
38
|
+
@timeit
|
39
|
+
def download_aws_dir(
|
40
|
+
bucket: str, path: str, target: str, unsigned=True, **kwargs
|
41
|
+
) -> None:
|
42
|
+
"""Utility function download data from AWS S3 directory.
|
43
|
+
|
44
|
+
Args:
|
45
|
+
bucket (str): Name of the bucket.
|
46
|
+
path (str): S3 bucket prefix
|
47
|
+
target (str): Path for downloading the data
|
48
|
+
unsigned (bool): Indicate whether to use credential or not
|
49
|
+
kwargs (dict): Keyword arguments accepted by `boto3.client`
|
50
|
+
"""
|
51
|
+
|
52
|
+
target = Path(target)
|
53
|
+
if unsigned:
|
54
|
+
client = boto3.client("s3", config=Config(signature_version=UNSIGNED))
|
55
|
+
else:
|
56
|
+
if kwargs:
|
57
|
+
client = boto3.client("s3", **kwargs)
|
58
|
+
else:
|
59
|
+
client = boto3.client("s3")
|
60
|
+
|
61
|
+
# Handle missing / at end of prefix
|
62
|
+
if not path.endswith("/"):
|
63
|
+
path += "/"
|
64
|
+
|
65
|
+
paginator = client.get_paginator("list_objects_v2")
|
66
|
+
for result in paginator.paginate(Bucket=bucket, Prefix=path):
|
67
|
+
|
68
|
+
# Download each file individually
|
69
|
+
for key in result["Contents"]:
|
70
|
+
|
71
|
+
# Calculate relative path
|
72
|
+
rel_path = key["Key"][len(path) :]
|
73
|
+
|
74
|
+
# Skip paths ending in /
|
75
|
+
if not key["Key"].endswith("/"):
|
76
|
+
local_file_path = target / rel_path
|
77
|
+
local_file_path.parent.mkdir(parents=True, exist_ok=True)
|
78
|
+
client.download_file(bucket, key["Key"], str(local_file_path))
|
79
|
+
|
80
|
+
|
81
|
+
@timeit
|
82
|
+
def download_smartds_data(
|
83
|
+
smartds_region: str,
|
84
|
+
output_path: str = "./smart_ds_downloads",
|
85
|
+
year: int = 2018,
|
86
|
+
area: str = "SFO",
|
87
|
+
s3_bucket_name: str = "oedi-data-lake",
|
88
|
+
folder_name: str = "opendss_no_loadshapes",
|
89
|
+
cache_folder: str = "cache",
|
90
|
+
) -> str:
|
91
|
+
"""Utility function to download SMARTDS data from AWS S3 bucket.
|
92
|
+
|
93
|
+
Args:
|
94
|
+
smartds_region (str): SMARTDS region name
|
95
|
+
output_path (str): Path for downloaded data
|
96
|
+
year (int): Valid year input for downloading the data
|
97
|
+
area (str): Valid SMARTDS area
|
98
|
+
s3_bucket_name (str): S3 bucket name storing the SMARTDS data
|
99
|
+
folder_name (str): S3 bucket folder to download
|
100
|
+
cache_folder (str): Folder path for caching the results
|
101
|
+
|
102
|
+
Raises:
|
103
|
+
SMARTDSInvalidInput: Raises this error if year and/or area
|
104
|
+
provided is not valid.
|
105
|
+
|
106
|
+
Returns:
|
107
|
+
str: Folder path containing downloaded data.
|
108
|
+
"""
|
109
|
+
if year not in SMARTDS_VALID_YEARS or area not in SMARTDS_VALID_AREAS:
|
110
|
+
raise SMARTDSInvalidInput(
|
111
|
+
f"Not valid input! year= {year} area={area}, \
|
112
|
+
valid_years={SMARTDS_VALID_YEARS}, valid_areas={SMARTDS_VALID_AREAS}"
|
113
|
+
)
|
114
|
+
|
115
|
+
output_path = Path(output_path)
|
116
|
+
cache_folder = Path(cache_folder)
|
117
|
+
|
118
|
+
output_path.mkdir(exist_ok=True)
|
119
|
+
cache_folder.mkdir(exist_ok=True)
|
120
|
+
|
121
|
+
cache_key = (
|
122
|
+
f"{smartds_region}__{year}__{area}__{s3_bucket_name}_{folder_name}"
|
123
|
+
)
|
124
|
+
cache_data_folder = cache_folder / cache_key
|
125
|
+
output_folder = output_path / cache_key
|
126
|
+
|
127
|
+
if cache_data_folder.exists():
|
128
|
+
logger.info(f"Cache hit for {cache_data_folder}")
|
129
|
+
shutil.copytree(cache_data_folder, output_folder, dirs_exist_ok=True)
|
130
|
+
|
131
|
+
else:
|
132
|
+
logger.info(
|
133
|
+
f"Cache missed reaching to AWS for downloading the data ..."
|
134
|
+
)
|
135
|
+
output_folder.mkdir(exist_ok=True)
|
136
|
+
prefix = f"SMART-DS/v1.0/{year}/{area}/{smartds_region}/scenarios/base_timeseries/{folder_name}/"
|
137
|
+
download_aws_dir(s3_bucket_name, prefix, output_folder)
|
138
|
+
shutil.copytree(output_folder, cache_data_folder, dirs_exist_ok=False)
|
139
|
+
|
140
|
+
logger.info(f"Check the folder {output_folder} for downloaded data")
|
141
|
+
return output_folder
|
142
|
+
|
143
|
+
|
144
|
+
@timeit
|
145
|
+
def _create_networkx_from_ditto(
|
146
|
+
output_path: str, file_name: str, **kwargs
|
147
|
+
) -> List:
|
148
|
+
"""Creates networkx graph from OpenDSS model using Ditto.
|
149
|
+
|
150
|
+
Args:
|
151
|
+
output_path (str): Path to store the networkx
|
152
|
+
data in json file format
|
153
|
+
file_name (str): JSON file name used to export
|
154
|
+
the network
|
155
|
+
kwargs (dict): Keyword arguments accepted
|
156
|
+
by Ditto
|
157
|
+
|
158
|
+
Raises:
|
159
|
+
DittoException: Raises if multiple sources are found.
|
160
|
+
|
161
|
+
|
162
|
+
Returns:
|
163
|
+
List: Pair of networkx graph and
|
164
|
+
path containing JSON file
|
165
|
+
"""
|
166
|
+
file_name = Path(file_name).stem
|
167
|
+
logger.debug(
|
168
|
+
"Attempting to create NetworkX representation from OpenDSS \
|
169
|
+
files using DiTTo"
|
170
|
+
)
|
171
|
+
|
172
|
+
path_validation(output_path)
|
173
|
+
|
174
|
+
store = Store()
|
175
|
+
reader = Reader(
|
176
|
+
master_file=kwargs["master_file"],
|
177
|
+
buscoordinates_file=kwargs["buscoordinates_file"],
|
178
|
+
coordinates_delimiter=kwargs["coordinates_delimiter"],
|
179
|
+
)
|
180
|
+
reader.parse(store)
|
181
|
+
|
182
|
+
all_sources = []
|
183
|
+
for i in store.models:
|
184
|
+
if isinstance(i, PowerSource) and i.connecting_element is not None:
|
185
|
+
all_sources.append(i)
|
186
|
+
elif isinstance(i, PowerSource):
|
187
|
+
print(
|
188
|
+
"Warning - a PowerSource element has a None connecting element"
|
189
|
+
)
|
190
|
+
|
191
|
+
if len(all_sources) > 1:
|
192
|
+
raise DittoException(
|
193
|
+
f"This feeder has lots of sources {len(all_sources)}"
|
194
|
+
)
|
195
|
+
|
196
|
+
ditto_graph = Network()
|
197
|
+
ditto_graph.build(store, all_sources[0].connecting_element)
|
198
|
+
ditto_graph.set_attributes(store)
|
199
|
+
|
200
|
+
data = dict(ditto_graph.graph.nodes.data())
|
201
|
+
data_new = {}
|
202
|
+
for node, node_data in data.items():
|
203
|
+
try:
|
204
|
+
data_new[node] = node_data["positions"][0]._trait_values
|
205
|
+
except Exception as e:
|
206
|
+
connecting_node = node_data["connecting_element"]
|
207
|
+
data_new[node] = data[connecting_node]["positions"][0]._trait_values
|
208
|
+
|
209
|
+
adj_file = file_name + ".adjlist"
|
210
|
+
nx.write_adjlist(ditto_graph.graph, output_path / adj_file)
|
211
|
+
g = nx.read_adjlist(output_path / adj_file)
|
212
|
+
nx.set_node_attributes(g, data_new)
|
213
|
+
|
214
|
+
data = json_graph.adjacency_data(g)
|
215
|
+
json_file = file_name + ".json"
|
216
|
+
output_file = output_path / json_file
|
217
|
+
write_file(data, output_file)
|
218
|
+
|
219
|
+
logger.debug(
|
220
|
+
f"Successfully created json file representing the network \
|
221
|
+
check the file {output_file}"
|
222
|
+
)
|
223
|
+
|
224
|
+
return (g, output_file)
|
225
|
+
|
226
|
+
|
227
|
+
def create_networkx_from_ditto(
|
228
|
+
output_path: str, file_name: str, **kwargs
|
229
|
+
) -> None:
|
230
|
+
"""Creates networkx graph from OpenDSS model using Ditto.
|
231
|
+
|
232
|
+
Args:
|
233
|
+
output_path (str): Path to store the networkx
|
234
|
+
data in json file format
|
235
|
+
file_name (str): JSON file name used to export
|
236
|
+
the network
|
237
|
+
kwargs (dict): Keyword arguments accepted
|
238
|
+
by Ditto
|
239
|
+
"""
|
240
|
+
try:
|
241
|
+
output_path = Path(output_path)
|
242
|
+
return _create_networkx_from_ditto(output_path, file_name, **kwargs)
|
243
|
+
finally:
|
244
|
+
for file_path in output_path.iterdir():
|
245
|
+
if file_path.suffix == ".adjlist":
|
246
|
+
file_path.unlink(missing_ok=True)
|
247
|
+
|
248
|
+
|
249
|
+
def create_networkx_from_json(json_file_path: str):
|
250
|
+
"""Returns networkx graph from JSON file."""
|
251
|
+
content = read_file(json_file_path)
|
252
|
+
return json_graph.adjacency_graph(content)
|
@@ -0,0 +1,147 @@
|
|
1
|
+
""" Module for parsing Homeland infrastructure foundation level-data.
|
2
|
+
|
3
|
+
Idea is to take the bounding box and find the subset of
|
4
|
+
infrastructure in that region.
|
5
|
+
"""
|
6
|
+
# standard imports
|
7
|
+
from pathlib import Path
|
8
|
+
import math
|
9
|
+
from typing import Union, List
|
10
|
+
|
11
|
+
# third-party imports
|
12
|
+
import pandas as pd
|
13
|
+
import stateplane
|
14
|
+
|
15
|
+
# internal imports
|
16
|
+
from erad.utils.util import path_validation
|
17
|
+
|
18
|
+
|
19
|
+
def get_subset_of_hifld_data(
|
20
|
+
csv_file: str,
|
21
|
+
bounds: List,
|
22
|
+
output_folder: str,
|
23
|
+
logitude_column_name: str = "X",
|
24
|
+
latitude_column_name: str = "Y",
|
25
|
+
columns_to_keep: List[str] = ["X", "Y"],
|
26
|
+
name_of_csv_file: Union[str, None] = None,
|
27
|
+
) -> None:
|
28
|
+
"""Extracts a subset of HIFLD data set.
|
29
|
+
|
30
|
+
Args:
|
31
|
+
csv_file (str): Path to HIFLD data csv file
|
32
|
+
bounds (List): Bounding box coordinates
|
33
|
+
output_folder (str): Path to output folder
|
34
|
+
logitude_column_name (str): Expects column with name 'X'
|
35
|
+
latitude_column_name (str): Expects column with name 'Y'
|
36
|
+
columns_to_keep (List): List of column names to keep
|
37
|
+
by default keeps all of them
|
38
|
+
name_of_csv_file (Union[str, None]): Name of csv file to export
|
39
|
+
filtered set
|
40
|
+
"""
|
41
|
+
|
42
|
+
# Unpacking the bounds data
|
43
|
+
longitude_min, latitude_min, longitude_max, latitude_max = bounds
|
44
|
+
|
45
|
+
# Do a path validation
|
46
|
+
csv_file = Path(csv_file)
|
47
|
+
output_folder = Path(output_folder)
|
48
|
+
path_validation(csv_file, check_for_file=True, check_for_file_type=".csv")
|
49
|
+
path_validation(output_folder)
|
50
|
+
|
51
|
+
# Reading the hifld csv data
|
52
|
+
df = pd.read_csv(csv_file)
|
53
|
+
|
54
|
+
# filtering for bounds
|
55
|
+
df_filtered = df[
|
56
|
+
(df[logitude_column_name] >= longitude_min)
|
57
|
+
& (df[logitude_column_name] <= longitude_max)
|
58
|
+
& (df[latitude_column_name] >= latitude_min)
|
59
|
+
& (df[latitude_column_name] <= latitude_max)
|
60
|
+
]
|
61
|
+
|
62
|
+
# Keep only the limited columns
|
63
|
+
df_subset = df_filtered[columns_to_keep]
|
64
|
+
|
65
|
+
# export the subset
|
66
|
+
file_name = name_of_csv_file if name_of_csv_file else csv_file.name
|
67
|
+
df_subset.to_csv(output_folder / file_name)
|
68
|
+
|
69
|
+
|
70
|
+
def get_relationship_between_hifld_infrastructures(
|
71
|
+
hifld_data_csv: str,
|
72
|
+
unique_id_column: str,
|
73
|
+
load_csv: str,
|
74
|
+
bus_csv: str,
|
75
|
+
output_csv_path: str,
|
76
|
+
distance_threshold: float = 2000.0,
|
77
|
+
):
|
78
|
+
"""Creates a relationship between consumers and HIFLD infrastructures.
|
79
|
+
|
80
|
+
Args:
|
81
|
+
hifld_data_csv (str): Path to filtered HIFLD data csv file
|
82
|
+
unique_id_column (List): Column name used as identifier
|
83
|
+
for critical infrastructures
|
84
|
+
load_csv (str): Path to load csv file
|
85
|
+
bus_csv (str): Path to bus csv file
|
86
|
+
output_csv_path (str): output csv path for storing relationship csv
|
87
|
+
distance_threshold (float): Distance threshold used for mapping
|
88
|
+
customer to critical infrastructure
|
89
|
+
"""
|
90
|
+
hifld_data_csv = Path(hifld_data_csv)
|
91
|
+
bus_csv = Path(bus_csv)
|
92
|
+
load_csv = Path(load_csv)
|
93
|
+
output_csv_path = Path(output_csv_path)
|
94
|
+
|
95
|
+
path_validation(
|
96
|
+
hifld_data_csv, check_for_file=True, check_for_file_type=".csv"
|
97
|
+
)
|
98
|
+
path_validation(bus_csv, check_for_file=True, check_for_file_type=".csv")
|
99
|
+
path_validation(load_csv, check_for_file=True, check_for_file_type=".csv")
|
100
|
+
path_validation(output_csv_path.parents[0])
|
101
|
+
|
102
|
+
hifld_data_df = pd.read_csv(hifld_data_csv)
|
103
|
+
load_df = pd.read_csv(load_csv)
|
104
|
+
bus_df = pd.read_csv(bus_csv)
|
105
|
+
|
106
|
+
merged_data = pd.merge(
|
107
|
+
load_df, bus_df, how="left", left_on="source", right_on="name"
|
108
|
+
).to_dict(orient="records")
|
109
|
+
|
110
|
+
# Container for storing shelter relationships
|
111
|
+
_relationship = []
|
112
|
+
for _record in hifld_data_df.to_dict(orient="records"):
|
113
|
+
_lon, _lat = _record["LONGITUDE"], _record["LATITUDE"]
|
114
|
+
|
115
|
+
# convert into state plane coordinates
|
116
|
+
_lon_translated, _lat_translated = stateplane.from_lonlat(_lon, _lat)
|
117
|
+
|
118
|
+
# Loop through all the loads
|
119
|
+
for load_record in merged_data:
|
120
|
+
|
121
|
+
load_lon, load_lat = (
|
122
|
+
load_record["longitude"],
|
123
|
+
load_record["latitude"],
|
124
|
+
)
|
125
|
+
|
126
|
+
# convert into state plane coordinates
|
127
|
+
load_lon_translated, load_lat_translated = stateplane.from_lonlat(
|
128
|
+
load_lon, load_lat
|
129
|
+
)
|
130
|
+
|
131
|
+
# computes distance
|
132
|
+
distance = math.sqrt(
|
133
|
+
(_lat_translated - load_lat_translated) ** 2
|
134
|
+
+ (_lon_translated - load_lon_translated) ** 2
|
135
|
+
)
|
136
|
+
|
137
|
+
if distance < distance_threshold:
|
138
|
+
_relationship.append(
|
139
|
+
{
|
140
|
+
unique_id_column: _record[unique_id_column],
|
141
|
+
"load_name": load_record["name_x"],
|
142
|
+
"distance": distance,
|
143
|
+
}
|
144
|
+
)
|
145
|
+
|
146
|
+
df = pd.DataFrame(_relationship)
|
147
|
+
df.to_csv(output_csv_path)
|