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