Conditions | 49 |
Total Lines | 1324 |
Code Lines | 748 |
Lines | 0 |
Ratio | 0 % |
Changes | 0 |
Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.
For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.
Commonly applied refactorings include:
If many parameters/temporary variables are present:
Complex classes like data.datasets.power_plants.pv_ground_mounted.insert() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
1 | import geopandas as gpd |
||
10 | def insert(): |
||
11 | def mastr_existing_pv(pow_per_area): |
||
12 | """Import MaStR data from csv-files. |
||
13 | |||
14 | Parameters |
||
15 | ---------- |
||
16 | pow_per_area: int |
||
17 | Assumption for areas of existing pv farms and power of new built |
||
18 | pv farms depending on area in kW/m² |
||
19 | |||
20 | """ |
||
21 | # get config |
||
22 | cfg = egon.data.config.datasets()["power_plants"] |
||
23 | |||
24 | # import MaStR data: locations, grid levels and installed capacities |
||
25 | |||
26 | # get relevant pv plants: ground mounted |
||
27 | df = pd.read_csv( |
||
28 | WORKING_DIR_MASTR_NEW / cfg["sources"]["mastr_pv"], |
||
29 | usecols=[ |
||
30 | "Lage", |
||
31 | "Laengengrad", |
||
32 | "Breitengrad", |
||
33 | "Nettonennleistung", |
||
34 | "EinheitMastrNummer", |
||
35 | "LokationMastrNummer", |
||
36 | ], |
||
37 | ) |
||
38 | df = df[df["Lage"] == "Freifläche"] |
||
39 | |||
40 | # examine data concerning geographical locations and drop NaNs |
||
41 | x1 = df["Laengengrad"].isnull().sum() |
||
42 | x2 = df["Breitengrad"].isnull().sum() |
||
43 | print(" ") |
||
44 | print("Examination of MaStR data set:") |
||
45 | print("original number of rows in the data set: " + str(len(df))) |
||
46 | print("NaNs for longitude and latitude: " + str(x1) + " & " + str(x2)) |
||
47 | df.dropna(inplace=True) |
||
48 | print("Number of rows after neglecting NaNs: " + str(len(df))) |
||
49 | print(" ") |
||
50 | |||
51 | # derive dataframe for locations |
||
52 | mastr = gpd.GeoDataFrame( |
||
53 | index=df.index, |
||
54 | geometry=gpd.points_from_xy(df["Laengengrad"], df["Breitengrad"]), |
||
55 | crs={"init": "epsg:4326"}, |
||
56 | ) |
||
57 | mastr = mastr.to_crs(3035) |
||
58 | |||
59 | # derive installed capacities |
||
60 | mastr["installed capacity in kW"] = df["Nettonennleistung"] |
||
61 | |||
62 | # create buffer around locations |
||
63 | |||
64 | # calculate bufferarea and -radius considering installed capacity |
||
65 | df_radius = ( |
||
66 | mastr["installed capacity in kW"].div(pow_per_area * np.pi) ** 0.5 |
||
67 | ) # in m |
||
68 | |||
69 | # create buffer |
||
70 | mastr["buffer"] = mastr["geometry"].buffer(df_radius) |
||
71 | mastr["buffer"].crs = 3035 |
||
72 | |||
73 | # derive MaStR-Nummer |
||
74 | mastr["LokationMastrNummer"] = df["LokationMastrNummer"] |
||
75 | |||
76 | # derive voltage level |
||
77 | |||
78 | mastr["voltage_level"] = pd.Series(dtype=int) |
||
79 | lvl = pd.read_csv( |
||
80 | WORKING_DIR_MASTR_NEW / cfg["sources"]["mastr_location"], |
||
81 | usecols=["Spannungsebene", "MaStRNummer"], |
||
82 | ) |
||
83 | |||
84 | # assign voltage_level to MaStR-unit: |
||
85 | |||
86 | vlevel_mapping = { |
||
87 | "Höchstspannung": 1, |
||
88 | "UmspannungZurHochspannung": 2, |
||
89 | "Hochspannung": 3, |
||
90 | "UmspannungZurMittelspannung": 4, |
||
91 | "Mittelspannung": 5, |
||
92 | "UmspannungZurNiederspannung": 6, |
||
93 | "Niederspannung": 7, |
||
94 | } |
||
95 | |||
96 | mastr = mastr.merge( |
||
97 | lvl[["MaStRNummer", "Spannungsebene"]], |
||
98 | left_on="LokationMastrNummer", |
||
99 | right_on="MaStRNummer", |
||
100 | how="left", |
||
101 | ) |
||
102 | |||
103 | mastr["voltage_level"] = mastr.Spannungsebene.replace(vlevel_mapping) |
||
104 | |||
105 | mastr.drop(["MaStRNummer", "Spannungsebene"], axis=1, inplace=True) |
||
106 | |||
107 | # ### examine data concerning voltage level |
||
108 | x1 = mastr["voltage_level"].isnull().sum() |
||
109 | print(" ") |
||
110 | print("Examination of voltage levels in MaStR data set:") |
||
111 | print("Original number of rows in MaStR: " + str(len(mastr))) |
||
112 | print( |
||
113 | "NaNs in voltage level caused by a) a missing assignment to the " |
||
114 | "number or b) insufficient data: " + str(x1) |
||
115 | ) |
||
116 | # drop PVs with missing values due to a) no assignment of |
||
117 | # MaStR-numbers or b) missing data in row |
||
118 | mastr.dropna(inplace=True) |
||
119 | print("Number of rows after neglecting NaNs: " + str(len(mastr))) |
||
120 | |||
121 | # drop PVs in low voltage level |
||
122 | index_names = mastr[mastr["voltage_level"] == "Niederspannung"].index |
||
123 | x2 = len(index_names) |
||
124 | mastr.drop(index_names, inplace=True) |
||
125 | index_names = mastr[ |
||
126 | mastr["voltage_level"] == "UmspannungZurNiederspannung" |
||
127 | ].index |
||
128 | x3 = len(index_names) |
||
129 | mastr.drop(index_names, inplace=True) |
||
130 | |||
131 | # ### further examination |
||
132 | print("Number of PVs in low voltage level: " + str(x2)) |
||
133 | print("Number of PVs in LVMV level: " + str(x3)) |
||
134 | print( |
||
135 | "Number of rows after dropping entries assigned to these levels: " |
||
136 | + str(len(mastr)) |
||
137 | ) |
||
138 | print(" ") |
||
139 | |||
140 | return mastr |
||
141 | |||
142 | def potential_areas(con, join_buffer): |
||
143 | """Import potential areas and choose and prepare areas suitable for PV |
||
144 | ground mounted. |
||
145 | |||
146 | Parameters |
||
147 | ---------- |
||
148 | con: |
||
149 | Connection to database |
||
150 | join_buffer: int |
||
151 | Maximum distance for joining of potential areas (only small ones |
||
152 | to big ones) in m |
||
153 | |||
154 | """ |
||
155 | |||
156 | # import potential areas: railways and roads & agriculture |
||
157 | |||
158 | # roads and railway |
||
159 | sql = ( |
||
160 | "SELECT id, geom FROM " |
||
161 | "supply.egon_re_potential_area_pv_road_railway" |
||
162 | ) |
||
163 | potentials_rora = gpd.GeoDataFrame.from_postgis(sql, con) |
||
164 | potentials_rora = potentials_rora.set_index("id") |
||
165 | |||
166 | # agriculture |
||
167 | sql = ( |
||
168 | "SELECT id, geom FROM " |
||
169 | "supply.egon_re_potential_area_pv_agriculture" |
||
170 | ) |
||
171 | potentials_agri = gpd.GeoDataFrame.from_postgis(sql, con) |
||
172 | potentials_agri = potentials_agri.set_index("id") |
||
173 | |||
174 | # add areas < 1 ha to bigger areas if they are very close, otherwise |
||
175 | # exclude areas < 1 ha |
||
176 | |||
177 | # calculate area |
||
178 | potentials_rora["area"] = potentials_rora.area |
||
179 | potentials_agri["area"] = potentials_agri.area |
||
180 | |||
181 | # roads and railways |
||
182 | |||
183 | # ### counting variable for examination |
||
184 | before = len(potentials_rora) |
||
185 | |||
186 | # get small areas and create buffer for joining around them |
||
187 | small_areas = potentials_rora[potentials_rora["area"] < 10000] |
||
188 | small_buffers = small_areas.copy() |
||
189 | small_buffers["geom"] = small_areas["geom"].buffer(join_buffer) |
||
190 | |||
191 | # drop small areas in potential areas |
||
192 | index_names = potentials_rora[potentials_rora["area"] < 10000].index |
||
193 | potentials_rora.drop(index_names, inplace=True) |
||
194 | |||
195 | # check intersection of small areas with other potential areas |
||
196 | overlay = gpd.sjoin(potentials_rora, small_buffers) |
||
197 | o = overlay["index_right"] |
||
198 | o.drop_duplicates(inplace=True) |
||
199 | |||
200 | # add small areas to big ones if buffer intersects |
||
201 | for i in range(0, len(o)): |
||
202 | index_potentials = o.index[i] |
||
203 | index_small = o.iloc[i] |
||
204 | x = potentials_rora["geom"].loc[index_potentials] |
||
205 | y = small_areas["geom"].loc[index_small] |
||
206 | join = gpd.GeoSeries(data=[x, y]) |
||
207 | potentials_rora["geom"].loc[index_potentials] = join.unary_union |
||
208 | |||
209 | # ### examination of joining of areas |
||
210 | count_small = len(small_buffers) |
||
211 | count_join = len(o) |
||
212 | count_delete = count_small - count_join |
||
213 | print(" ") |
||
214 | print( |
||
215 | "Examination of potential areas in category 'Roads and Railways'" |
||
216 | ) |
||
217 | print("Length of original data frame: " + str(before)) |
||
218 | print("Number of small areas: " + str(count_small)) |
||
219 | print("Number of joins: " + str(count_join)) |
||
220 | print("Deleted areas (not joined): " + str(count_delete)) |
||
221 | print("Length of resulting data frame: " + str(len(potentials_rora))) |
||
222 | print(" ") |
||
223 | |||
224 | # agriculture |
||
225 | |||
226 | # ### counting variable for examination |
||
227 | before = len(potentials_agri) |
||
228 | |||
229 | # get small areas and create buffer for joining around them |
||
230 | small_areas = potentials_agri[potentials_agri["area"] < 10000] |
||
231 | small_buffers = small_areas.copy() |
||
232 | small_buffers["geom"] = small_areas["geom"].buffer(join_buffer) |
||
233 | |||
234 | # drop small areas in potential areas |
||
235 | index_names = potentials_agri[potentials_agri["area"] < 10000].index |
||
236 | potentials_agri.drop(index_names, inplace=True) |
||
237 | |||
238 | # check intersection of small areas with other potential areas |
||
239 | overlay = gpd.sjoin(potentials_agri, small_buffers) |
||
240 | o = overlay["index_right"] |
||
241 | o.drop_duplicates(inplace=True) |
||
242 | |||
243 | # add small areas to big ones if buffer intersects |
||
244 | for i in range(0, len(o)): |
||
245 | index_potentials = o.index[i] |
||
246 | index_small = o.iloc[i] |
||
247 | x = potentials_agri["geom"].loc[index_potentials] |
||
248 | y = small_areas["geom"].loc[index_small] |
||
249 | join = gpd.GeoSeries(data=[x, y]) |
||
250 | potentials_agri["geom"].loc[index_potentials] = join.unary_union |
||
251 | |||
252 | # ### examination of joining of areas |
||
253 | count_small = len(small_buffers) |
||
254 | count_join = len(o) |
||
255 | count_delete = count_small - count_join |
||
256 | print(" ") |
||
257 | print("Examination of potential areas in category 'Agriculture'") |
||
258 | print("Length of original data frame: " + str(before)) |
||
259 | print("Number of small areas: " + str(count_small)) |
||
260 | print("Number of joins: " + str(count_join)) |
||
261 | print("Deleted areas (not joined): " + str(count_delete)) |
||
262 | print("Length of resulting data frame: " + str(len(potentials_agri))) |
||
263 | print(" ") |
||
264 | |||
265 | # calculate new areas |
||
266 | potentials_rora["area"] = potentials_rora.area |
||
267 | potentials_agri["area"] = potentials_agri.area |
||
268 | |||
269 | # check intersection of potential areas |
||
270 | |||
271 | # ### counting variable |
||
272 | agri_vorher = len(potentials_agri) |
||
273 | |||
274 | # if areas intersect, keep road & railway potential areas and drop |
||
275 | # agricultural ones |
||
276 | overlay = gpd.sjoin(potentials_rora, potentials_agri) |
||
277 | o = overlay["index_right"] |
||
278 | o.drop_duplicates(inplace=True) |
||
279 | for i in range(0, len(o)): |
||
280 | index = o.iloc[i] |
||
281 | potentials_agri.drop([index], inplace=True) |
||
282 | |||
283 | # ### examination of intersection of areas |
||
284 | print(" ") |
||
285 | print("Review function to avoid intersection of potential areas:") |
||
286 | print("Initial length potentials_agri: " + str(agri_vorher)) |
||
287 | print("Number of occurred cases: " + str(len(o))) |
||
288 | print("Resulting length potentials_agri: " + str(len(potentials_agri))) |
||
289 | print(" ") |
||
290 | |||
291 | return potentials_rora, potentials_agri |
||
292 | |||
293 | def select_pot_areas(mastr, potentials_pot): |
||
294 | """Select potential areas where there are existing pv parks |
||
295 | (MaStR-data). |
||
296 | |||
297 | Parameters |
||
298 | ---------- |
||
299 | mastr: gpd.GeoDataFrame() |
||
300 | MaStR-DataFrame with existing pv parks |
||
301 | potentials_pot: gpd.GeoDataFrame() |
||
302 | Suitable potential areas |
||
303 | |||
304 | """ |
||
305 | |||
306 | # select potential areas with existing pv parks |
||
307 | # (potential areas intersect buffer around existing plants) |
||
308 | |||
309 | # prepare dataframes to check intersection |
||
310 | pvs = gpd.GeoDataFrame() |
||
311 | pvs["geom"] = mastr["buffer"].copy() |
||
312 | pvs.set_geometry("geom", inplace=True) |
||
313 | pvs.crs = 3035 |
||
314 | pvs = pvs.set_geometry("geom") |
||
315 | potentials = gpd.GeoDataFrame() |
||
316 | potentials["geom"] = potentials_pot["geom"].copy() |
||
317 | potentials.set_geometry("geom", inplace=True) |
||
318 | potentials.crs = 3035 |
||
319 | potentials = potentials.set_geometry("geom") |
||
320 | |||
321 | # check intersection of potential areas with exisiting PVs (MaStR) |
||
322 | overlay = gpd.sjoin(pvs, potentials) |
||
323 | o = overlay["index_right"] |
||
324 | o.drop_duplicates(inplace=True) |
||
325 | |||
326 | # define selected potentials areas |
||
327 | pot_sel = potentials_pot.copy() |
||
328 | pot_sel["selected"] = pd.Series() |
||
329 | pot_sel["voltage_level"] = pd.Series(dtype=int) |
||
330 | for i in range(0, len(o)): |
||
331 | index_pot = o.iloc[i] |
||
332 | pot_sel["selected"].loc[index_pot] = True |
||
333 | # get voltage level of existing PVs |
||
334 | index_pv = o.index[i] |
||
335 | pot_sel["voltage_level"] = mastr["voltage_level"].loc[index_pv] |
||
336 | pot_sel = pot_sel[pot_sel["selected"] == True] |
||
337 | pot_sel.drop("selected", axis=1, inplace=True) |
||
338 | |||
339 | # drop selected existing pv parks from mastr |
||
340 | mastr.drop(index=o.index, inplace=True) |
||
341 | |||
342 | return (pot_sel, mastr) |
||
343 | |||
344 | def build_pv(pv_pot, pow_per_area): |
||
345 | """Build new pv parks in selected potential areas. |
||
346 | |||
347 | Parameters |
||
348 | ---------- |
||
349 | pv_pot: gpd.GeoDataFrame() |
||
350 | Selected potential areas |
||
351 | pow_per_area: int |
||
352 | Assumption for areas of existing pv farms and power of new built |
||
353 | pv farms depending on area in kW/m² |
||
354 | |||
355 | """ |
||
356 | |||
357 | # build pv farms in selected areas |
||
358 | |||
359 | # calculation of centroids |
||
360 | pv_pot["centroid"] = pv_pot["geom"].representative_point() |
||
361 | |||
362 | # calculation of power in kW |
||
363 | pv_pot["installed capacity in kW"] = pd.Series() |
||
364 | pv_pot["installed capacity in kW"] = pv_pot["area"] * pow_per_area |
||
365 | |||
366 | # check for maximal capacity for PV ground mounted |
||
367 | limit_cap = 120000 # in kW |
||
368 | pv_pot["installed capacity in kW"] = pv_pot[ |
||
369 | "installed capacity in kW" |
||
370 | ].apply(lambda x: x if x < limit_cap else limit_cap) |
||
371 | |||
372 | return pv_pot |
||
373 | |||
374 | def adapt_grid_level(pv_pot, max_dist_hv, con): |
||
375 | """Check and if needed adapt grid level of newly built pv parks. |
||
376 | |||
377 | Parameters |
||
378 | ---------- |
||
379 | pv_pot: gpd.GeoDataFrame() |
||
380 | Newly built pv parks on selected potential areas |
||
381 | max_dist_hv: int |
||
382 | Assumption for maximum distance of park with hv-power to next |
||
383 | substation in m |
||
384 | con: |
||
385 | Connection to database |
||
386 | |||
387 | """ |
||
388 | |||
389 | # divide dataframe in MV and HV |
||
390 | pv_pot_mv = pv_pot[pv_pot["voltage_level"] == 5] |
||
391 | pv_pot_hv = pv_pot[pv_pot["voltage_level"] == 4] |
||
392 | |||
393 | # check installed capacity in MV |
||
394 | |||
395 | max_cap_mv = 5500 # in kW |
||
396 | |||
397 | # find PVs which need to be HV or to have reduced capacity |
||
398 | pv_pot_mv_to_hv = pv_pot_mv[ |
||
399 | pv_pot_mv["installed capacity in kW"] > max_cap_mv |
||
400 | ] |
||
401 | |||
402 | if len(pv_pot_mv_to_hv) > 0: |
||
403 | # import data for HV substations |
||
404 | |||
405 | sql = "SELECT point, voltage FROM grid.egon_hvmv_substation" |
||
406 | hvmv_substation = gpd.GeoDataFrame.from_postgis( |
||
407 | sql, con, geom_col="point" |
||
408 | ) |
||
409 | hvmv_substation = hvmv_substation.to_crs(3035) |
||
410 | hvmv_substation["voltage"] = hvmv_substation["voltage"].apply( |
||
411 | lambda x: int(x.split(";")[0]) |
||
412 | ) |
||
413 | hv_substations = hvmv_substation[ |
||
414 | hvmv_substation["voltage"] >= 110000 |
||
415 | ] |
||
416 | hv_substations = ( |
||
417 | hv_substations.unary_union |
||
418 | ) # join all the hv_substations |
||
419 | |||
420 | # check distance to HV substations of PVs with too high installed |
||
421 | # capacity for MV |
||
422 | |||
423 | # calculate distance to substations |
||
424 | pv_pot_mv_to_hv["dist_to_HV"] = ( |
||
425 | pv_pot_mv_to_hv["geom"].to_crs(3035).distance(hv_substations) |
||
426 | ) |
||
427 | |||
428 | # adjust grid level and keep capacity if transmission lines are |
||
429 | # close |
||
430 | pv_pot_mv_to_hv = pv_pot_mv_to_hv[ |
||
431 | pv_pot_mv_to_hv["dist_to_HV"] <= max_dist_hv |
||
432 | ] |
||
433 | pv_pot_mv_to_hv = pv_pot_mv_to_hv.drop(columns=["dist_to_HV"]) |
||
434 | pv_pot_hv = pd.concat([pv_pot_hv, pv_pot_mv_to_hv]) |
||
435 | |||
436 | # delete PVs which are now HV from MV dataframe |
||
437 | for index, pot in pv_pot_mv_to_hv.iterrows(): |
||
438 | pv_pot_mv = pv_pot_mv.drop([index]) |
||
439 | pv_pot_hv["voltage_level"] = 4 |
||
440 | |||
441 | # keep grid level adjust capacity if transmission lines are too |
||
442 | # far |
||
443 | pv_pot_mv["installed capacity in kW"] = pv_pot_mv[ |
||
444 | "installed capacity in kW" |
||
445 | ].apply(lambda x: x if x < max_cap_mv else max_cap_mv) |
||
446 | pv_pot_mv["voltage_level"] = 5 |
||
447 | |||
448 | pv_pot = pd.concat([pv_pot_mv, pv_pot_hv]) |
||
449 | |||
450 | return pv_pot |
||
451 | |||
452 | def build_additional_pv(potentials, pv, pow_per_area, con): |
||
453 | """Build additional pv parks if pv parks on selected potential areas |
||
454 | do not hit the target value. |
||
455 | |||
456 | Parameters |
||
457 | ---------- |
||
458 | potenatials: gpd.GeoDataFrame() |
||
459 | All suitable potential areas |
||
460 | pv: gpd.GeoDataFrame() |
||
461 | Newly built pv parks on selected potential areas |
||
462 | pow_per_area: int |
||
463 | Assumption for areas of existing pv farms and power of new built |
||
464 | pv farms depending on area in kW/m² |
||
465 | con: |
||
466 | Connection to database |
||
467 | |||
468 | """ |
||
469 | |||
470 | # get MV grid districts |
||
471 | sql = "SELECT bus_id, geom FROM grid.egon_mv_grid_district" |
||
472 | distr = gpd.GeoDataFrame.from_postgis(sql, con) |
||
473 | distr = distr.set_index("bus_id") |
||
474 | |||
475 | # identify potential areas where there are no PV parks yet |
||
476 | for index, pv in pv.iterrows(): |
||
477 | potentials = potentials.drop([index]) |
||
478 | |||
479 | # aggregate potential area per MV grid district |
||
480 | pv_per_distr = gpd.GeoDataFrame() |
||
481 | pv_per_distr["geom"] = distr["geom"].copy() |
||
482 | centroids = potentials.copy() |
||
483 | centroids["geom"] = centroids["geom"].representative_point() |
||
484 | |||
485 | overlay = gpd.sjoin(centroids, distr) |
||
486 | |||
487 | # ### examine potential area per grid district |
||
488 | anz = len(overlay) |
||
489 | anz_distr = len(overlay["index_right"].unique()) |
||
490 | size = 137500 # m2 Fläche für > 5,5 MW: (5500 kW / (0,04 kW/m2)) |
||
491 | anz_big = len(overlay[overlay["area"] >= size]) |
||
492 | anz_small = len(overlay[overlay["area"] < size]) |
||
493 | |||
494 | print(" ") |
||
495 | print( |
||
496 | "Examination of remaining potential areas in MV grid districts: " |
||
497 | ) |
||
498 | print("Number of potential areas: " + str(anz)) |
||
499 | print(" -> distributed to " + str(anz_distr) + " districts") |
||
500 | print("Number of areas with a potential >= 5,5 MW: " + str(anz_big)) |
||
501 | print("Number of areas with a potential < 5,5 MW: " + str(anz_small)) |
||
502 | print(" ") |
||
503 | |||
504 | for index, dist in distr.iterrows(): |
||
505 | pots = overlay[overlay["index_right"] == index]["geom"].index |
||
506 | p = gpd.GeoSeries(index=pots) |
||
507 | for i in pots: |
||
508 | p.loc[i] = potentials["geom"].loc[i] |
||
509 | pv_per_distr["geom"].loc[index] = p.unary_union |
||
510 | |||
511 | # calculate area per MV grid district and linearly distribute needed |
||
512 | # capacity considering pow_per_area |
||
513 | pv_per_distr["area"] = pv_per_distr["geom"].area |
||
514 | pv_per_distr["installed capacity in kW"] = ( |
||
515 | pv_per_distr["area"] * pow_per_area |
||
516 | ) |
||
517 | |||
518 | # calculate centroid |
||
519 | pv_per_distr["centroid"] = pv_per_distr["geom"].representative_point() |
||
520 | |||
521 | return pv_per_distr |
||
522 | |||
523 | def check_target( |
||
524 | pv_rora_i, |
||
525 | pv_agri_i, |
||
526 | pv_exist_i, |
||
527 | potentials_rora_i, |
||
528 | potentials_agri_i, |
||
529 | target_power, |
||
530 | pow_per_area, |
||
531 | con, |
||
532 | ): |
||
533 | """Check target value per scenario and per state. |
||
534 | |||
535 | Parameters |
||
536 | ---------- |
||
537 | pv_rora_i: gpd.GeoDataFrame() |
||
538 | Newly built pv parks on selected potential areas of road and |
||
539 | railways p |
||
540 | pv_agri_i: gpd.GeoDataFrame() |
||
541 | Newly built pv parks on selected potential areas of agriculture |
||
542 | pv_exist_i: gpd.GeoDataFrame() |
||
543 | existing pv parks that don't intercept any potential area |
||
544 | potenatials_rora_i: gpd.GeoDataFrame() |
||
545 | All suitable potential areas of road and railway |
||
546 | potenatials_rora_i: gpd.GeoDataFrame() |
||
547 | All suitable potential areas of agriculture |
||
548 | target_power: int |
||
549 | Target for installed capacity of pv ground mounted in referenced |
||
550 | state |
||
551 | pow_per_area: int |
||
552 | Assumption for areas of existing pv farms and power of new built |
||
553 | pv farms depending on area in kW/m² |
||
554 | con: |
||
555 | Connection to database |
||
556 | |||
557 | """ |
||
558 | |||
559 | # sum overall installed capacity for MV and HV |
||
560 | |||
561 | total_pv_power = ( |
||
562 | pv_rora_i["installed capacity in kW"].sum() |
||
563 | + pv_agri_i["installed capacity in kW"].sum() |
||
564 | + pv_exist_i["installed capacity in kW"].sum() |
||
565 | ) |
||
566 | |||
567 | pv_per_distr_i = gpd.GeoDataFrame() |
||
568 | |||
569 | # check target value |
||
570 | |||
571 | ### |
||
572 | print(" ") |
||
573 | print( |
||
574 | "Installed capacity on areas with existing plants: " |
||
575 | + str(total_pv_power / 1000) |
||
576 | + " MW" |
||
577 | ) |
||
578 | |||
579 | # linear scale farms to meet target if sum of installed capacity is |
||
580 | # too high |
||
581 | if total_pv_power >= target_power: |
||
582 | scale_factor = target_power / total_pv_power |
||
583 | pv_rora_i["installed capacity in kW"] = ( |
||
584 | pv_rora_i["installed capacity in kW"] * scale_factor |
||
585 | ) |
||
586 | pv_agri_i["installed capacity in kW"] = ( |
||
587 | pv_agri_i["installed capacity in kW"] * scale_factor |
||
588 | ) |
||
589 | pv_exist_i["installed capacity in kW"] = ( |
||
590 | pv_exist_i["installed capacity in kW"] * scale_factor |
||
591 | ) |
||
592 | |||
593 | pv_per_distr_i["grid_district"] = pd.Series() |
||
594 | pv_per_distr_i["installed capacity in kW"] = pd.Series(0) |
||
595 | |||
596 | ### |
||
597 | print( |
||
598 | "Expansion of existing PV parks on potential areas to " |
||
599 | "achieve target capacity is sufficient." |
||
600 | ) |
||
601 | print( |
||
602 | "Installed power is greater than the target value, scaling " |
||
603 | "is applied:" |
||
604 | ) |
||
605 | print("Scaling factor: " + str(scale_factor)) |
||
606 | |||
607 | # build new pv parks if sum of installed capacity is below target |
||
608 | # value |
||
609 | elif total_pv_power < target_power: |
||
610 | rest_cap = target_power - total_pv_power |
||
611 | |||
612 | ### |
||
613 | print( |
||
614 | "Expansion of existing PV parks on potential areas to " |
||
615 | "achieve target capacity is unsufficient:" |
||
616 | ) |
||
617 | print("Residual capacity: " + str(rest_cap / 1000) + " MW") |
||
618 | print( |
||
619 | "Residual capacity will initially be distributed via " |
||
620 | "remaining potential areas 'Road & Railway'." |
||
621 | ) |
||
622 | |||
623 | # build pv parks in potential areas road & railway |
||
624 | pv_per_distr_i = build_additional_pv( |
||
625 | potentials_rora_i, pv_rora_i, pow_per_area, con |
||
626 | ) |
||
627 | # change index to add different Dataframes in the end |
||
628 | pv_per_distr_i["grid_district"] = pv_per_distr_i.index.copy() |
||
629 | pv_per_distr_i.index = range(0, len(pv_per_distr_i)) |
||
630 | # delete empty grid districts |
||
631 | index_names = pv_per_distr_i[ |
||
632 | pv_per_distr_i["installed capacity in kW"].isna() |
||
633 | ].index |
||
634 | pv_per_distr_i.drop(index_names, inplace=True) |
||
635 | |||
636 | if pv_per_distr_i["installed capacity in kW"].sum() > rest_cap: |
||
637 | scale_factor = ( |
||
638 | rest_cap / pv_per_distr_i["installed capacity in kW"].sum() |
||
639 | ) |
||
640 | pv_per_distr_i["installed capacity in kW"] = ( |
||
641 | pv_per_distr_i["installed capacity in kW"] * scale_factor |
||
642 | ) |
||
643 | |||
644 | ### |
||
645 | print( |
||
646 | "Residual capacity got distributed via scaling factor " |
||
647 | + str(scale_factor) |
||
648 | + " to remaining potential areas 'Road & Railway'." |
||
649 | ) |
||
650 | |||
651 | # build pv parks on potential areas agriculture if still necessary |
||
652 | elif pv_per_distr_i["installed capacity in kW"].sum() < rest_cap: |
||
653 | rest_cap = ( |
||
654 | target_power |
||
655 | - total_pv_power |
||
656 | - pv_per_distr_i["installed capacity in kW"].sum() |
||
657 | ) |
||
658 | |||
659 | ### |
||
660 | print( |
||
661 | "Distribution via potential areas Road & Railway " |
||
662 | "unsufficient to achieve target capacity:" |
||
663 | ) |
||
664 | print("Residual capacity: " + str(rest_cap / 1000) + " MW") |
||
665 | print( |
||
666 | "Residual capacity is distributed to remaining potential " |
||
667 | "areas 'Agriculture'." |
||
668 | ) |
||
669 | |||
670 | pv_per_distr_i_2 = build_additional_pv( |
||
671 | potentials_agri_i, pv_agri_i, pow_per_area, con |
||
672 | ) |
||
673 | # change index to add different Dataframes in the end |
||
674 | pv_per_distr_i_2["grid_district"] = pv_per_distr_i_2.index |
||
675 | pv_per_distr_i_2.index = range(len(pv_per_distr_i_2)) |
||
676 | |||
677 | # delete empty grid districts |
||
678 | index_names = pv_per_distr_i_2[ |
||
679 | pv_per_distr_i_2["installed capacity in kW"].isna() |
||
680 | ].index |
||
681 | pv_per_distr_i_2.drop(index_names, inplace=True) |
||
682 | |||
683 | if ( |
||
684 | pv_per_distr_i_2["installed capacity in kW"].sum() |
||
685 | > rest_cap |
||
686 | ): |
||
687 | scale_factor = ( |
||
688 | rest_cap |
||
689 | / pv_per_distr_i_2["installed capacity in kW"].sum() |
||
690 | ) |
||
691 | pv_per_distr_i_2["installed capacity in kW"] = ( |
||
692 | pv_per_distr_i_2["installed capacity in kW"] |
||
693 | * scale_factor |
||
694 | ) |
||
695 | |||
696 | ### |
||
697 | print( |
||
698 | "Residual capacity got distributed via scaling " |
||
699 | "factor " |
||
700 | + str(scale_factor) |
||
701 | + " to remaining potential areas 'Road & Railway' " |
||
702 | "and 'Agriculture'." |
||
703 | ) |
||
704 | |||
705 | pv_per_distr_i = pd.concat( |
||
706 | [pv_per_distr_i, pv_per_distr_i_2], ignore_index=True |
||
707 | ) |
||
708 | |||
709 | # assign grid level to pv_per_distr |
||
710 | v_lvl = pd.Series(dtype=int, index=pv_per_distr_i.index) |
||
711 | for index, distr in pv_per_distr_i.iterrows(): |
||
712 | if distr["installed capacity in kW"] > 5500: # > 5 MW |
||
713 | v_lvl[index] = 4 |
||
714 | else: |
||
715 | v_lvl[index] = 5 |
||
716 | pv_per_distr_i["voltage_level"] = v_lvl |
||
717 | |||
718 | # new overall installed capacity |
||
719 | total_pv_power = ( |
||
720 | pv_rora_i["installed capacity in kW"].sum() |
||
721 | + pv_agri_i["installed capacity in kW"].sum() |
||
722 | + pv_exist_i["installed capacity in kW"].sum() |
||
723 | + pv_per_distr_i["installed capacity in kW"].sum() |
||
724 | ) |
||
725 | |||
726 | ### |
||
727 | print( |
||
728 | "Total installed capacity of PV farms: " |
||
729 | + str(total_pv_power / 1000) |
||
730 | + " MW" |
||
731 | ) |
||
732 | print(" ") |
||
733 | |||
734 | pv_rora_i = pv_rora_i[pv_rora_i["installed capacity in kW"] > 0] |
||
735 | pv_agri_i = pv_agri_i[pv_agri_i["installed capacity in kW"] > 0] |
||
736 | pv_exist_i = pv_exist_i[pv_exist_i["installed capacity in kW"] > 0] |
||
737 | pv_per_distr_i = pv_per_distr_i[ |
||
738 | pv_per_distr_i["installed capacity in kW"] > 0 |
||
739 | ] |
||
740 | |||
741 | return pv_rora_i, pv_agri_i, pv_exist_i, pv_per_distr_i |
||
742 | |||
743 | def keep_existing_pv(mastr, con): |
||
744 | pv_exist = mastr[ |
||
745 | [ |
||
746 | "geometry", |
||
747 | "installed capacity in kW", |
||
748 | "voltage_level", |
||
749 | ] |
||
750 | ] |
||
751 | pv_exist.rename(columns={"geometry": "centroid"}, inplace=True) |
||
752 | pv_exist = gpd.GeoDataFrame(pv_exist, geometry="centroid", crs=3035) |
||
753 | |||
754 | # German states |
||
755 | sql = "SELECT geometry as geom, gf FROM boundaries.vg250_lan" |
||
756 | land = gpd.GeoDataFrame.from_postgis(sql, con).to_crs(3035) |
||
757 | land = land[(land["gf"] != 1) & (land["gf"] != 2)] |
||
758 | land = land.unary_union |
||
759 | pv_exist = gpd.clip(pv_exist, land) |
||
760 | |||
761 | return pv_exist |
||
762 | |||
763 | def run_methodology( |
||
764 | con=db.engine(), |
||
765 | pow_per_area=0.04, |
||
766 | join_buffer=10, |
||
767 | max_dist_hv=20000, |
||
768 | show_map=False, |
||
769 | ): |
||
770 | """Execute methodology to distribute pv ground mounted. |
||
771 | |||
772 | Parameters |
||
773 | ---------- |
||
774 | con: |
||
775 | Connection to database |
||
776 | pow_per_area: int, default 0.4 |
||
777 | Assumption for areas of existing pv farms and power of new built |
||
778 | pv farms depending on area in kW/m² |
||
779 | join_buffer : int, default 10 |
||
780 | Maximum distance for joining of potential areas (only small ones |
||
781 | to big ones) in m |
||
782 | max_dist_hv : int, default 20000 |
||
783 | Assumption for maximum distance of park with hv-power to next |
||
784 | substation in m |
||
785 | show_map: boolean |
||
786 | Optional creation of map to show distribution of installed |
||
787 | capacity |
||
788 | |||
789 | """ |
||
790 | ### |
||
791 | print(" ") |
||
792 | print("MaStR-Data") |
||
793 | print(" ") |
||
794 | |||
795 | # MaStR-data: existing PV farms |
||
796 | mastr = mastr_existing_pv(pow_per_area) |
||
797 | |||
798 | ### |
||
799 | print(" ") |
||
800 | print("potential area") |
||
801 | print(" ") |
||
802 | |||
803 | # database-data: potential areas for new PV farms |
||
804 | potentials_rora, potentials_agri = potential_areas(con, join_buffer) |
||
805 | |||
806 | ### |
||
807 | print(" ") |
||
808 | print("select potentials area") |
||
809 | print(" ") |
||
810 | |||
811 | # select potential areas with existing PV farms to build new PV farms |
||
812 | pv_rora, mastr = select_pot_areas(mastr, potentials_rora) |
||
813 | pv_agri, mastr = select_pot_areas(mastr, potentials_agri) |
||
814 | |||
815 | ### |
||
816 | print(" ") |
||
817 | print( |
||
818 | "build PV parks where there is PV ground mounted already " |
||
819 | "(-> MaStR) on potential area" |
||
820 | ) |
||
821 | print(" ") |
||
822 | |||
823 | # build new PV farms |
||
824 | pv_rora = build_pv(pv_rora, pow_per_area) |
||
825 | pv_agri = build_pv(pv_agri, pow_per_area) |
||
826 | |||
827 | # keep the existing pv_farms that don't intercept potential areas |
||
828 | exist = keep_existing_pv(mastr, con) |
||
829 | |||
830 | ### |
||
831 | print(" ") |
||
832 | print("adapt grid level of PV parks") |
||
833 | print(" ") |
||
834 | |||
835 | # adapt grid level to new farms |
||
836 | rora = adapt_grid_level(pv_rora, max_dist_hv, con) |
||
837 | agri = adapt_grid_level(pv_agri, max_dist_hv, con) |
||
838 | |||
839 | ### |
||
840 | print(" ") |
||
841 | print( |
||
842 | "check target value and build more PV parks on potential area if " |
||
843 | "necessary" |
||
844 | ) |
||
845 | print(" ") |
||
846 | |||
847 | # initialize final dataframe |
||
848 | pv_rora = gpd.GeoDataFrame() |
||
849 | pv_agri = gpd.GeoDataFrame() |
||
850 | pv_exist = gpd.GeoDataFrame() |
||
851 | pv_per_distr = gpd.GeoDataFrame() |
||
852 | |||
853 | # 1) scenario: eGon2035 |
||
854 | if ( |
||
855 | "eGon2035" |
||
856 | in egon.data.config.settings()["egon-data"]["--scenarios"] |
||
857 | ): |
||
858 | ### |
||
859 | print(" ") |
||
860 | print("scenario: eGon2035") |
||
861 | print(" ") |
||
862 | |||
863 | # German states |
||
864 | sql = "SELECT geometry as geom, nuts FROM boundaries.vg250_lan" |
||
865 | states = gpd.GeoDataFrame.from_postgis(sql, con) |
||
866 | |||
867 | # assumption for target value of installed capacity |
||
868 | sql = ( |
||
869 | "SELECT capacity,scenario_name,nuts FROM " |
||
870 | "supply.egon_scenario_capacities WHERE carrier='solar'" |
||
871 | ) |
||
872 | target = pd.read_sql(sql, con) |
||
873 | target = target[target["scenario_name"] == "eGon2035"] |
||
874 | nuts = np.unique(target["nuts"]) |
||
875 | |||
876 | # prepare selection per state |
||
877 | rora = rora.set_geometry("centroid") |
||
878 | agri = agri.set_geometry("centroid") |
||
879 | potentials_rora = potentials_rora.set_geometry("geom") |
||
880 | potentials_agri = potentials_agri.set_geometry("geom") |
||
881 | |||
882 | # check target value per state |
||
883 | for i in nuts: |
||
884 | target_power = ( |
||
885 | target[target["nuts"] == i]["capacity"].iloc[0] * 1000 |
||
886 | ) |
||
887 | |||
888 | ### |
||
889 | land = target[target["nuts"] == i]["nuts"].iloc[0] |
||
890 | print(" ") |
||
891 | print("Bundesland (NUTS): " + land) |
||
892 | print("target power: " + str(target_power / 1000) + " MW") |
||
893 | |||
894 | # select state |
||
895 | state = states[states["nuts"] == i] |
||
896 | state = state.to_crs(3035) |
||
897 | |||
898 | # select PVs in state |
||
899 | rora_i = gpd.sjoin(rora, state) |
||
900 | agri_i = gpd.sjoin(agri, state) |
||
901 | exist_i = gpd.sjoin(exist, state) |
||
902 | rora_i.drop("index_right", axis=1, inplace=True) |
||
903 | agri_i.drop("index_right", axis=1, inplace=True) |
||
904 | exist_i.drop("index_right", axis=1, inplace=True) |
||
905 | rora_i.drop_duplicates(inplace=True) |
||
906 | agri_i.drop_duplicates(inplace=True) |
||
907 | exist_i.drop_duplicates(inplace=True) |
||
908 | |||
909 | # select potential areas in state |
||
910 | potentials_rora_i = gpd.sjoin(potentials_rora, state) |
||
911 | potentials_agri_i = gpd.sjoin(potentials_agri, state) |
||
912 | potentials_rora_i.drop("index_right", axis=1, inplace=True) |
||
913 | potentials_agri_i.drop("index_right", axis=1, inplace=True) |
||
914 | potentials_rora_i.drop_duplicates(inplace=True) |
||
915 | potentials_agri_i.drop_duplicates(inplace=True) |
||
916 | |||
917 | # check target value and adapt installed capacity if necessary |
||
918 | rora_i, agri_i, exist_i, distr_i = check_target( |
||
919 | rora_i, |
||
920 | agri_i, |
||
921 | exist_i, |
||
922 | potentials_rora_i, |
||
923 | potentials_agri_i, |
||
924 | target_power, |
||
925 | pow_per_area, |
||
926 | con, |
||
927 | ) |
||
928 | |||
929 | if len(distr_i) > 0: |
||
930 | distr_i["nuts"] = target[target["nuts"] == i]["nuts"].iloc[ |
||
931 | 0 |
||
932 | ] |
||
933 | |||
934 | # ### examination of built PV parks per state |
||
935 | rora_i_mv = rora_i[rora_i["voltage_level"] == 5] |
||
936 | rora_i_hv = rora_i[rora_i["voltage_level"] == 4] |
||
937 | agri_i_mv = agri_i[agri_i["voltage_level"] == 5] |
||
938 | agri_i_hv = agri_i[agri_i["voltage_level"] == 4] |
||
939 | print( |
||
940 | "eGon2035: Examination of voltage level per federal state:" |
||
941 | ) |
||
942 | print("a) PVs on potential areas Road & Railway: ") |
||
943 | print( |
||
944 | "Total installed capacity: " |
||
945 | + str(rora_i["installed capacity in kW"].sum() / 1000) |
||
946 | + " MW" |
||
947 | ) |
||
948 | print("Number of PV farms: " + str(len(rora_i))) |
||
949 | print(" - thereof MV: " + str(len(rora_i_mv))) |
||
950 | print(" - thereof HV: " + str(len(rora_i_hv))) |
||
951 | print("b) PVs on potential areas Agriculture: ") |
||
952 | print( |
||
953 | "Total installed capacity: " |
||
954 | + str(agri_i["installed capacity in kW"].sum() / 1000) |
||
955 | + " MW" |
||
956 | ) |
||
957 | print("Number of PV farms: " + str(len(agri_i))) |
||
958 | print(" - thereof MV: " + str(len(agri_i_mv))) |
||
959 | print(" - dthereof HV: " + str(len(agri_i_hv))) |
||
960 | print("c) Existing PVs not in potential areas: ") |
||
961 | print("Number of PV farms: " + str(len(exist_i))) |
||
962 | print("d) PVs on additional potential areas per MV-District: ") |
||
963 | if len(distr_i) > 0: |
||
964 | distr_i_mv = distr_i[distr_i["voltage_level"] == 5] |
||
965 | distr_i_hv = distr_i[distr_i["voltage_level"] == 4] |
||
966 | print( |
||
967 | "Total installed capacity: " |
||
968 | + str(distr_i["installed capacity in kW"].sum() / 1000) |
||
969 | + " MW" |
||
970 | ) |
||
971 | print("Number of PV farms: " + str(len(distr_i))) |
||
972 | print(" - thereof MV: " + str(len(distr_i_mv))) |
||
973 | print(" - thereof HV: " + str(len(distr_i_hv))) |
||
974 | else: |
||
975 | print(" -> No additional expansion necessary") |
||
976 | print(" ") |
||
977 | |||
978 | pv_rora = pv_rora.append(rora_i) |
||
979 | pv_agri = pv_agri.append(agri_i) |
||
980 | pv_exist = pv_exist.append(exist_i) |
||
981 | if len(distr_i) > 0: |
||
982 | pv_per_distr = pd.concat([pv_per_distr, distr_i]) |
||
983 | |||
984 | if ( |
||
985 | "eGon100RE" |
||
986 | in egon.data.config.settings()["egon-data"]["--scenarios"] |
||
987 | ): |
||
988 | # 2) scenario: eGon100RE |
||
989 | |||
990 | # assumption for target value of installed capacity in Germany per |
||
991 | # scenario |
||
992 | sql = ( |
||
993 | "SELECT capacity,scenario_name FROM " |
||
994 | "supply.egon_scenario_capacities WHERE carrier='solar'" |
||
995 | ) |
||
996 | target_power = pd.read_sql(sql, con) |
||
997 | target_power = target_power[ |
||
998 | target_power["scenario_name"] == "eGon100RE" |
||
999 | ] |
||
1000 | target_power = target_power["capacity"].sum() * 1000 |
||
1001 | |||
1002 | ### |
||
1003 | print(" ") |
||
1004 | print("scenario: eGon100RE") |
||
1005 | print("target power: " + str(target_power) + " kW") |
||
1006 | print(" ") |
||
1007 | |||
1008 | # check target value and adapt installed capacity if necessary |
||
1009 | ( |
||
1010 | pv_rora_100RE, |
||
1011 | pv_agri_100RE, |
||
1012 | pv_exist_100RE, |
||
1013 | pv_per_distr_100RE, |
||
1014 | ) = check_target( |
||
1015 | rora, |
||
1016 | agri, |
||
1017 | exist, |
||
1018 | potentials_rora, |
||
1019 | potentials_agri, |
||
1020 | target_power, |
||
1021 | pow_per_area, |
||
1022 | con, |
||
1023 | ) |
||
1024 | |||
1025 | # ### create map to show distribution of installed capacity |
||
1026 | if show_map == True: |
||
1027 | # 1) eGon2035 |
||
1028 | |||
1029 | # get MV grid districts |
||
1030 | sql = "SELECT bus_id, geom FROM grid.egon_mv_grid_district" |
||
1031 | distr = gpd.GeoDataFrame.from_postgis(sql, con) |
||
1032 | distr = distr.set_index("bus_id") |
||
1033 | |||
1034 | # assign pv_per_distr-power to districts |
||
1035 | distr["capacity"] = pd.Series() |
||
1036 | for index, row in distr.iterrows(): |
||
1037 | if index in np.unique(pv_per_distr["grid_district"]): |
||
1038 | pv = pv_per_distr[pv_per_distr["grid_district"] == index] |
||
1039 | x = pv["installed capacity in kW"].iloc[0] |
||
1040 | distr["capacity"].loc[index] = x |
||
1041 | else: |
||
1042 | distr["capacity"].loc[index] = 0 |
||
1043 | distr["capacity"] = distr["capacity"] / 1000 |
||
1044 | |||
1045 | # add pv_rora- and pv_agri-power to district |
||
1046 | pv_rora = pv_rora.set_geometry("centroid") |
||
1047 | pv_agri = pv_agri.set_geometry("centroid") |
||
1048 | overlay_rora = gpd.sjoin(pv_rora, distr) |
||
1049 | overlay_agri = gpd.sjoin(pv_agri, distr) |
||
1050 | |||
1051 | for index, row in distr.iterrows(): |
||
1052 | o_rora = overlay_rora[overlay_rora["index_right"] == index] |
||
1053 | o_agri = overlay_agri[overlay_agri["index_right"] == index] |
||
1054 | cap_rora = o_rora["installed capacity in kW"].sum() / 1000 |
||
1055 | cap_agri = o_agri["installed capacity in kW"].sum() / 1000 |
||
1056 | distr["capacity"].loc[index] = ( |
||
1057 | distr["capacity"].loc[index] + cap_rora + cap_agri |
||
1058 | ) |
||
1059 | |||
1060 | from matplotlib import pyplot as plt |
||
1061 | |||
1062 | fig, ax = plt.subplots(1, 1) |
||
1063 | distr.boundary.plot(linewidth=0.2, ax=ax, color="black") |
||
1064 | distr.plot( |
||
1065 | ax=ax, |
||
1066 | column="capacity", |
||
1067 | cmap="magma_r", |
||
1068 | legend=True, |
||
1069 | legend_kwds={ |
||
1070 | "label": "Installed capacity in MW", |
||
1071 | "orientation": "vertical", |
||
1072 | }, |
||
1073 | ) |
||
1074 | plt.savefig("pv_per_distr_map_eGon2035.png", dpi=300) |
||
1075 | |||
1076 | # 2) eGon100RE |
||
1077 | |||
1078 | # get MV grid districts |
||
1079 | sql = "SELECT bus_id, geom FROM grid.egon_mv_grid_district" |
||
1080 | distr = gpd.GeoDataFrame.from_postgis(sql, con) |
||
1081 | distr = distr.set_index("bus_id") |
||
1082 | |||
1083 | # assign pv_per_distr-power to districts |
||
1084 | distr["capacity"] = pd.Series() |
||
1085 | for index, row in distr.iterrows(): |
||
1086 | if index in np.unique(pv_per_distr_100RE["grid_district"]): |
||
1087 | pv = pv_per_distr_100RE[ |
||
1088 | pv_per_distr_100RE["grid_district"] == index |
||
1089 | ] |
||
1090 | x = pv["installed capacity in kW"].iloc[0] |
||
1091 | distr["capacity"].loc[index] = x |
||
1092 | else: |
||
1093 | distr["capacity"].loc[index] = 0 |
||
1094 | distr["capacity"] = distr["capacity"] / 1000 |
||
1095 | |||
1096 | # add pv_rora- and pv_agri-power to district |
||
1097 | pv_rora_100RE = pv_rora_100RE.set_geometry("centroid") |
||
1098 | pv_agri_100RE = pv_agri_100RE.set_geometry("centroid") |
||
1099 | overlay_rora = gpd.sjoin(pv_rora_100RE, distr) |
||
1100 | overlay_agri = gpd.sjoin(pv_agri_100RE, distr) |
||
1101 | |||
1102 | for index, row in distr.iterrows(): |
||
1103 | o_rora = overlay_rora[overlay_rora["index_right"] == index] |
||
1104 | o_agri = overlay_agri[overlay_agri["index_right"] == index] |
||
1105 | cap_rora = o_rora["installed capacity in kW"].sum() / 1000 |
||
1106 | cap_agri = o_agri["installed capacity in kW"].sum() / 1000 |
||
1107 | distr["capacity"].loc[index] = ( |
||
1108 | distr["capacity"].loc[index] + cap_rora + cap_agri |
||
1109 | ) |
||
1110 | |||
1111 | from matplotlib import pyplot as plt |
||
1112 | |||
1113 | fig, ax = plt.subplots(1, 1) |
||
1114 | distr.boundary.plot(linewidth=0.2, ax=ax, color="black") |
||
1115 | distr.plot( |
||
1116 | ax=ax, |
||
1117 | column="capacity", |
||
1118 | cmap="magma_r", |
||
1119 | legend=True, |
||
1120 | legend_kwds={ |
||
1121 | "label": "Installed capacity in MW", |
||
1122 | "orientation": "vertical", |
||
1123 | }, |
||
1124 | ) |
||
1125 | plt.savefig("pv_per_distr_map_eGon100RE.png", dpi=300) |
||
1126 | |||
1127 | pv_rora_100RE = pv_rora_100RE[ |
||
1128 | pv_rora_100RE["installed capacity in kW"] > 0 |
||
1129 | ] |
||
1130 | pv_agri_100RE = pv_agri_100RE[ |
||
1131 | pv_agri_100RE["installed capacity in kW"] > 0 |
||
1132 | ] |
||
1133 | pv_per_distr_100RE = pv_per_distr_100RE[ |
||
1134 | pv_per_distr_100RE["installed capacity in kW"] > 0 |
||
1135 | ] |
||
1136 | |||
1137 | return ( |
||
1138 | pv_rora, |
||
1139 | pv_agri, |
||
1140 | pv_exist, |
||
1141 | pv_per_distr, |
||
1142 | pv_rora_100RE, |
||
1143 | pv_agri_100RE, |
||
1144 | pv_exist_100RE, |
||
1145 | pv_per_distr_100RE, |
||
1146 | ) |
||
1147 | |||
1148 | def insert_pv_parks( |
||
1149 | pv_rora, pv_agri, pv_exist, pv_per_distr, scenario_name |
||
1150 | ): |
||
1151 | """Write to database. |
||
1152 | |||
1153 | Parameters |
||
1154 | ---------- |
||
1155 | pv_rora : gpd.GeoDataFrame() |
||
1156 | Pv parks on selected potential areas of raod and railway |
||
1157 | pv_agri : gpd.GeoDataFrame() |
||
1158 | Pv parks on selected potential areas of raod and railway |
||
1159 | pv_exist : gpd.GeoDataFrame() |
||
1160 | Existing Pv parks on selected areas |
||
1161 | pv_per_distr: gpd.GeoDataFrame() |
||
1162 | Additionally built pv parks on potential areas per mv grid |
||
1163 | district |
||
1164 | scenario_name: |
||
1165 | Scenario name of calculation |
||
1166 | |||
1167 | """ |
||
1168 | |||
1169 | # prepare dataframe for integration in supply.egon_power_plants |
||
1170 | |||
1171 | pv_parks = pd.concat( |
||
1172 | [pv_rora, pv_agri, pv_exist, pv_per_distr], ignore_index=True |
||
1173 | ) |
||
1174 | pv_parks["el_capacity"] = pv_parks["installed capacity in kW"] / 1000 |
||
1175 | pv_parks.rename(columns={"centroid": "geometry"}, inplace=True) |
||
1176 | pv_parks = gpd.GeoDataFrame(pv_parks, geometry="geometry", crs=3035) |
||
1177 | pv_parks = pv_parks[["el_capacity", "voltage_level", "geometry"]] |
||
1178 | |||
1179 | # integration in supply.egon_power_plants |
||
1180 | |||
1181 | con = db.engine() |
||
1182 | |||
1183 | # maximum ID in egon_power_plants |
||
1184 | sql = "SELECT MAX(id) FROM supply.egon_power_plants" |
||
1185 | max_id = pd.read_sql(sql, con) |
||
1186 | max_id = max_id["max"].iat[0] |
||
1187 | if max_id is None: |
||
1188 | max_id = 1 |
||
1189 | |||
1190 | pv_park_id = max_id + 1 |
||
1191 | |||
1192 | # copy relevant columns from pv_parks |
||
1193 | insert_pv_parks = pv_parks[ |
||
1194 | ["el_capacity", "voltage_level", "geometry"] |
||
1195 | ] |
||
1196 | insert_pv_parks = insert_pv_parks.set_geometry("geometry") |
||
1197 | insert_pv_parks["voltage_level"] = insert_pv_parks[ |
||
1198 | "voltage_level" |
||
1199 | ].apply(int) |
||
1200 | |||
1201 | # set static column values |
||
1202 | insert_pv_parks["carrier"] = "solar" |
||
1203 | insert_pv_parks["scenario"] = scenario_name |
||
1204 | |||
1205 | # change name and crs of geometry column |
||
1206 | insert_pv_parks.set_crs(epsg=3035, allow_override=True, inplace=True) |
||
1207 | insert_pv_parks = ( |
||
1208 | insert_pv_parks.rename({"geometry": "geom"}, axis=1) |
||
1209 | .set_geometry("geom") |
||
1210 | .to_crs(4326) |
||
1211 | ) |
||
1212 | |||
1213 | # reset index |
||
1214 | insert_pv_parks.index = pd.RangeIndex( |
||
1215 | start=pv_park_id, stop=pv_park_id + len(insert_pv_parks), name="id" |
||
1216 | ) |
||
1217 | |||
1218 | # insert into database |
||
1219 | insert_pv_parks.reset_index().to_postgis( |
||
1220 | "egon_power_plants", |
||
1221 | schema="supply", |
||
1222 | con=db.engine(), |
||
1223 | if_exists="append", |
||
1224 | ) |
||
1225 | |||
1226 | return pv_parks |
||
1227 | |||
1228 | # ######################################################################## |
||
1229 | |||
1230 | # execute methodology |
||
1231 | |||
1232 | ( |
||
1233 | pv_rora, |
||
1234 | pv_agri, |
||
1235 | pv_exist, |
||
1236 | pv_per_distr, |
||
1237 | pv_rora_100RE, |
||
1238 | pv_agri_100RE, |
||
1239 | pv_exist_100RE, |
||
1240 | pv_per_distr_100RE, |
||
1241 | ) = run_methodology( |
||
1242 | con=db.engine(), |
||
1243 | pow_per_area=0.04, |
||
1244 | join_buffer=10, |
||
1245 | max_dist_hv=20000, |
||
1246 | show_map=False, |
||
1247 | ) |
||
1248 | |||
1249 | # ### examination of results |
||
1250 | if len(pv_per_distr) > 0: |
||
1251 | pv_per_distr_mv = pv_per_distr[pv_per_distr["voltage_level"] == 5] |
||
1252 | pv_per_distr_hv = pv_per_distr[pv_per_distr["voltage_level"] == 4] |
||
1253 | if len(pv_rora) > 0: |
||
1254 | pv_rora_mv = pv_rora[pv_rora["voltage_level"] == 5] |
||
1255 | pv_rora_hv = pv_rora[pv_rora["voltage_level"] == 4] |
||
1256 | pv_agri_mv = pv_agri[pv_agri["voltage_level"] == 5] |
||
1257 | pv_agri_hv = pv_agri[pv_agri["voltage_level"] == 4] |
||
1258 | |||
1259 | print(" ") |
||
1260 | print("eGon2035: Examination of overall voltage levels:") |
||
1261 | print("a) PVs on potential areas Road & Railway: ") |
||
1262 | print( |
||
1263 | "Total installed capacity: " |
||
1264 | + str(pv_rora["installed capacity in kW"].sum() / 1000) |
||
1265 | + " MW" |
||
1266 | ) |
||
1267 | print("Number of PV farms: " + str(len(pv_rora))) |
||
1268 | print(" - thereof MV: " + str(len(pv_rora_mv))) |
||
1269 | print(" - thereof HV: " + str(len(pv_rora_hv))) |
||
1270 | print("b) PVs on potential areas Agriculture: ") |
||
1271 | print( |
||
1272 | "Total installed capacity: " |
||
1273 | + str(pv_agri["installed capacity in kW"].sum() / 1000) |
||
1274 | + " MW" |
||
1275 | ) |
||
1276 | print("Number of PV farms: " + str(len(pv_agri))) |
||
1277 | print(" - thereof MV: " + str(len(pv_agri_mv))) |
||
1278 | print(" - thereof HV: " + str(len(pv_agri_hv))) |
||
1279 | print("c) Existing PVs not in potential areas: ") |
||
1280 | print("Number of PV farms: " + str(len(pv_exist))) |
||
1281 | print("d) PVs on additional potential areas per MV-District: ") |
||
1282 | if len(pv_per_distr) > 0: |
||
1283 | print( |
||
1284 | "Total installed capacity: " |
||
1285 | + str(pv_per_distr["installed capacity in kW"].sum() / 1000) |
||
1286 | + " MW" |
||
1287 | ) |
||
1288 | print("Number of PV farms: " + str(len(pv_per_distr))) |
||
1289 | print(" - thereof MV: " + str(len(pv_per_distr_mv))) |
||
1290 | print(" - thereof HV: " + str(len(pv_per_distr_hv))) |
||
1291 | else: |
||
1292 | print(" -> No additional expansion needed") |
||
1293 | print(" ") |
||
1294 | ### |
||
1295 | |||
1296 | # save to DB |
||
1297 | if "eGon2035" in egon.data.config.settings()["egon-data"]["--scenarios"]: |
||
1298 | if ( |
||
1299 | pv_rora["installed capacity in kW"].sum() > 0 |
||
1300 | or pv_agri["installed capacity in kW"].sum() > 0 |
||
1301 | or pv_per_distr["installed capacity in kW"].sum() > 0 |
||
1302 | or pv_exist["installed capacity in kW"].sum() > 0 |
||
1303 | ): |
||
1304 | pv_parks = insert_pv_parks( |
||
1305 | pv_rora, pv_agri, pv_exist, pv_per_distr, "eGon2035" |
||
1306 | ) |
||
1307 | |||
1308 | else: |
||
1309 | pv_parks = gpd.GeoDataFrame() |
||
1310 | else: |
||
1311 | pv_parks = gpd.GeoDataFrame() |
||
1312 | |||
1313 | if "eGon100RE" in egon.data.config.settings()["egon-data"]["--scenarios"]: |
||
1314 | if ( |
||
1315 | pv_rora_100RE["installed capacity in kW"].sum() > 0 |
||
1316 | or pv_agri_100RE["installed capacity in kW"].sum() > 0 |
||
1317 | or pv_per_distr_100RE["installed capacity in kW"].sum() > 0 |
||
1318 | or pv_exist_100RE["installed capacity in kW"].sum() > 0 |
||
1319 | ): |
||
1320 | pv_parks_100RE = insert_pv_parks( |
||
1321 | pv_rora_100RE, |
||
1322 | pv_agri_100RE, |
||
1323 | pv_exist_100RE, |
||
1324 | pv_per_distr_100RE, |
||
1325 | "eGon100RE", |
||
1326 | ) |
||
1327 | |||
1328 | else: |
||
1329 | pv_parks_100RE = gpd.GeoDataFrame() |
||
1330 | else: |
||
1331 | pv_parks_100RE = gpd.GeoDataFrame() |
||
1332 | |||
1333 | return pv_parks, pv_parks_100RE |
||
1334 |