Skip to content

Commit 30f12f9

Browse files
committed
Add transmission plots
1 parent dabce79 commit 30f12f9

File tree

4 files changed

+250
-140
lines changed

4 files changed

+250
-140
lines changed

switch_model/generators/core/build.py

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -893,18 +893,16 @@ def graph_buildout_per_tech(tools):
893893
# Set the y-axis to use percent
894894
ax.yaxis.set_major_formatter(tools.mplt.ticker.PercentFormatter(1.0))
895895
# Horizontal line at 100%
896-
<<<<<<< HEAD
897896
ax.axhline(y=1, linestyle="--", color="b")
898-
=======
899-
ax.axhline(y=1, linestyle="--", color='b')
900897

901-
@graph(
902-
"buildout_map",
903-
title="Map of online capacity per load zone."
904-
)
898+
899+
@graph("buildout_map", title="Map of online capacity per load zone.")
905900
def buildout_map(tools):
906-
buildout = tools.get_dataframe("gen_cap.csv").rename({"GenCapacity": "value"}, axis=1)
901+
buildout = tools.get_dataframe("gen_cap.csv").rename(
902+
{"GenCapacity": "value"}, axis=1
903+
)
907904
buildout = tools.transform.gen_type(buildout)
908-
buildout = buildout.groupby(["gen_type", "gen_load_zone"], as_index=False)["value"].sum()
909-
tools.graph_map_pychart(buildout)
910-
>>>>>>> 292648b0 (Make map of buildout)
905+
buildout = buildout.groupby(["gen_type", "gen_load_zone"], as_index=False)[
906+
"value"
907+
].sum()
908+
tools.maps.graph_pie_chart(buildout)

switch_model/generators/core/dispatch.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -947,4 +947,4 @@ def dispatch_map(tools):
947947
dispatch = dispatch.groupby(["gen_type", "gen_load_zone"], as_index=False)[
948948
"value"
949949
].sum()
950-
tools.graph_map_pychart(dispatch)
950+
tools.maps.graph_pie_chart(dispatch)

switch_model/tools/graph/main.py

Lines changed: 156 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,160 @@ def __exit__(self, exc_type, exc_val, exc_tb):
119119
os.chdir(Scenario.root_path)
120120

121121

122+
class GraphMapTools:
123+
def __init__(self, graph_tools):
124+
self._tools = graph_tools
125+
self._wecc_states = None
126+
self._center_points = None
127+
self._figure_size = (11, 12)
128+
self._shapely = None
129+
self._geopandas = None
130+
131+
def _load_maps(self):
132+
"""
133+
Plots the WECC states as a background on the provided axes
134+
and returns a dataframe with a mapping of load zones to center points.
135+
"""
136+
if self._wecc_states is None or self._center_points is None:
137+
try:
138+
import geopandas
139+
except ModuleNotFoundError:
140+
raise Exception(
141+
"Could not find package 'geopandas'. If on Windows make sure you install it through conda."
142+
)
143+
144+
import shapely
145+
146+
self._shapely = shapely
147+
self._geopandas = geopandas
148+
149+
# Read shape files
150+
try:
151+
wecc_lz = geopandas.read_file(
152+
self._tools.get_file_path("maps/wecc_102009.shp", from_inputs=True),
153+
crs="ESRI:102009",
154+
)
155+
self._wecc_states = geopandas.read_file(
156+
self._tools.get_file_path(
157+
"maps/wecc_states_4326.shp", from_inputs=True
158+
)
159+
)
160+
except FileNotFoundError:
161+
raise Exception(
162+
"Can't create maps, shape files are missing. Try running switch get_inputs."
163+
)
164+
165+
wecc_lz = wecc_lz.rename({"LOAD_AREA": "gen_load_zone"}, axis=1)
166+
167+
# New dataframe with the centerpoint geometry
168+
self._center_points = geopandas.GeoDataFrame(
169+
wecc_lz[["gen_load_zone"]],
170+
geometry=wecc_lz["geometry"].centroid,
171+
crs="ESRI:102009",
172+
).to_crs("EPSG:4326")
173+
174+
return self._wecc_states, self._center_points
175+
176+
def _plot_states(self, ax):
177+
states, _ = self._load_maps()
178+
states.plot(ax=ax, lw=0.5, edgecolor="white", color="#E5E5E5", zorder=-999)
179+
ax.axis("off")
180+
181+
@staticmethod
182+
def _pie_plot(x, y, ratios, colors, size, ax=None):
183+
# REFERENC: https://tinyurl.com/app/myurls
184+
# determine arches
185+
start = 0.0
186+
xy = []
187+
s = []
188+
for ratio in ratios:
189+
x0 = [0] + np.cos(
190+
np.linspace(2 * np.pi * start, 2 * np.pi * (start + ratio), 30)
191+
).tolist() # 30
192+
y0 = [0] + np.sin(
193+
np.linspace(2 * np.pi * start, 2 * np.pi * (start + ratio), 30)
194+
).tolist() # 30
195+
196+
xy1 = np.column_stack([x0, y0])
197+
s1 = np.abs(xy1).max()
198+
199+
xy.append(xy1)
200+
s.append(s1)
201+
start += ratio
202+
203+
for xyi, si, c in zip(xy, s, colors):
204+
ax.scatter(
205+
[x], [y], marker=xyi, s=size * si**2, c=c, edgecolor="k", zorder=10
206+
)
207+
208+
def graph_pie_chart(self, df, max_size=2500):
209+
"""
210+
Graphs the data from the dataframe to a map pie chart.
211+
The dataframe should have 3 columns, gen_load_zone, gen_type and value.
212+
"""
213+
_, center_points = self._load_maps()
214+
215+
# Scale the dataframe so the pie charts have the right size
216+
current_max_size = df.groupby("gen_load_zone")["value"].sum().max()
217+
df["value"] *= max_size / current_max_size
218+
219+
ax = self._tools.get_axes(size=self._figure_size)
220+
self._plot_states(ax)
221+
df = df.merge(center_points, on="gen_load_zone")
222+
223+
assert not df["gen_type"].isnull().values.any()
224+
colors = self._tools.get_colors()
225+
for index, group in df.groupby(["gen_load_zone"]):
226+
x, y = group["geometry"].iloc[0].x, group["geometry"].iloc[0].y
227+
group_sum = group.groupby("gen_type")["value"].sum().sort_values()
228+
group_sum = group_sum[group_sum != 0].copy()
229+
230+
tech_color = [colors[tech] for tech in group_sum.index.values]
231+
total_size = group_sum.sum()
232+
ratios = (group_sum / total_size).values
233+
GraphMapTools._pie_plot(x, y, ratios, tech_color, ax=ax, size=total_size)
234+
235+
def graph_transmission(self, df):
236+
"""
237+
Graphs the data frame a dataframe onto a map.
238+
The dataframe should have 4 columns:
239+
- from: the load zone where we're starting from
240+
- to: the load zone where we're going to
241+
- value: the value to plot
242+
"""
243+
ax = self._tools.get_axes(size=self._figure_size)
244+
_, center_points = self._load_maps()
245+
246+
# Merge duplicate rows if table was unidirectional
247+
df[["from", "to"]] = df[["from", "to"]].apply(
248+
sorted, axis=1, result_type="expand"
249+
)
250+
df = df.groupby(["from", "to"], as_index=False)["value"].sum()
251+
252+
df = df.merge(
253+
center_points.add_prefix("from_"),
254+
left_on="from",
255+
right_on="from_gen_load_zone",
256+
).merge(
257+
center_points.add_prefix("to_"), left_on="to", right_on="to_gen_load_zone"
258+
)[
259+
["from_geometry", "to_geometry", "value"]
260+
]
261+
262+
from shapely.geometry import LineString
263+
264+
def make_line(r):
265+
return LineString([r["from_geometry"], r["to_geometry"]])
266+
267+
df["geometry"] = df.apply(make_line, axis=1)
268+
df = self._geopandas.GeoDataFrame(
269+
df[["geometry", "value"]], geometry="geometry"
270+
)
271+
272+
self._plot_states(ax)
273+
df.plot(ax=ax, column="value", legend=True, cmap="Reds")
274+
275+
122276
class TransformTools:
123277
"""
124278
Provides helper functions that transform dataframes
@@ -590,6 +744,7 @@ def __init__(self, scenarios: List[Scenario], graph_dir: str, skip_long: bool):
590744
pd.options.mode.chained_assignment = None
591745

592746
self.transform = TransformTools(self)
747+
self.maps = GraphMapTools(self)
593748

594749
def _create_axes(self, num_rows=1, size=(8, 5), ylabel=None, **kwargs):
595750
"""
@@ -855,104 +1010,6 @@ def graph_matrix(self, df, value_column, ylabel, row_specifier, col_specifier):
8551010
legend_pairs = legend.items()
8561011
fig.legend([h for _, h in legend_pairs], [l for l, _ in legend_pairs])
8571012

858-
def graph_map_pychart(self, df, max_size=2500):
859-
"""
860-
Graphs the data from the dataframe to a map pychart.
861-
The dataframe should have 3 columns, gen_load_zone, gen_type and value.
862-
"""
863-
# Scale the dataframe so the pie charts have the right size
864-
current_max_size = df.groupby("gen_load_zone")["value"].sum().max()
865-
df["value"] *= max_size / current_max_size
866-
867-
try:
868-
import geopandas
869-
except ModuleNotFoundError:
870-
raise Exception(
871-
"Could not find package 'geopandas'. If on Windows make sure you install it through conda."
872-
)
873-
874-
# Read shape files
875-
try:
876-
wecc_lz = geopandas.read_file(
877-
self.get_file_path("maps/wecc_102009.shp", from_inputs=True),
878-
crs="ESRI:102009",
879-
)
880-
wecc_states = geopandas.read_file(
881-
self.get_file_path("maps/wecc_states_4326.shp", from_inputs=True)
882-
)
883-
except FileNotFoundError:
884-
raise Exception(
885-
"Can't create maps, shape files are missing. Try running switch get_inputs."
886-
)
887-
888-
# Find the center point
889-
wecc_lz["Center_point"] = wecc_lz["geometry"].centroid
890-
891-
# Extract lat and lon from the centerpoint
892-
wecc_lz["lat"] = wecc_lz.Center_point.map(lambda p: p.x)
893-
wecc_lz["lng"] = wecc_lz.Center_point.map(lambda p: p.y)
894-
895-
# New dataframe with the centerpoint geometry
896-
gdf = geopandas.GeoDataFrame(
897-
wecc_lz[["LOAD_AREA", "lat", "lng"]],
898-
geometry=geopandas.points_from_xy(x=wecc_lz.lat, y=wecc_lz.lng),
899-
crs="ESRI:102009",
900-
)
901-
902-
# REFERENC: https://tinyurl.com/app/myurls
903-
def pie_plot(x, y, ratios, colors, size, ax=None):
904-
# determine arches
905-
start = 0.0
906-
xy = []
907-
s = []
908-
for ratio in ratios:
909-
x0 = [0] + np.cos(
910-
np.linspace(2 * np.pi * start, 2 * np.pi * (start + ratio), 30)
911-
).tolist() # 30
912-
y0 = [0] + np.sin(
913-
np.linspace(2 * np.pi * start, 2 * np.pi * (start + ratio), 30)
914-
).tolist() # 30
915-
916-
xy1 = np.column_stack([x0, y0])
917-
s1 = np.abs(xy1).max()
918-
919-
xy.append(xy1)
920-
s.append(s1)
921-
start += ratio
922-
923-
for xyi, si, c in zip(xy, s, colors):
924-
ax.scatter(
925-
[x],
926-
[y],
927-
marker=xyi,
928-
s=size * si**2,
929-
c=c,
930-
edgecolor="k",
931-
zorder=10,
932-
)
933-
934-
projection = "EPSG:4326"
935-
936-
cap_by_lz = gdf.merge(df, right_on="gen_load_zone", left_on="LOAD_AREA").to_crs(
937-
projection
938-
)
939-
940-
ax = self.get_axes(size=(7, 12))
941-
wecc_states.plot(ax=ax, lw=0.5, edgecolor="white", color="#E5E5E5", zorder=-999)
942-
943-
assert not cap_by_lz["gen_type"].isnull().values.any()
944-
colors = self.get_colors()
945-
for index, group in cap_by_lz.groupby(["gen_load_zone"]):
946-
x, y = group["geometry"].iloc[0].x, group["geometry"].iloc[0].y
947-
group_sum = group.groupby("gen_type")["value"].sum().sort_values()
948-
group_sum = group_sum[(group_sum != 0)].copy()
949-
950-
tech_color = [colors[tech] for tech in group_sum.index.values]
951-
total_size = group_sum.sum()
952-
ratios = (group_sum / total_size).values
953-
pie_plot(x, y, ratios, tech_color, ax=ax, size=total_size)
954-
ax.axis("off")
955-
9561013
@staticmethod
9571014
def sort_build_years(x):
9581015
def val(v):
@@ -997,7 +1054,7 @@ def graph_scenarios(
9971054
for module_name in module_names:
9981055
try:
9991056
importlib.import_module(module_name)
1000-
except:
1057+
except ModuleNotFoundError:
10011058
warnings.warn(
10021059
f"Module {module_name} not found. Graphs in this module will not be created."
10031060
)

0 commit comments

Comments
 (0)