@@ -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+
122276class 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