Merge remote-tracking branch 'origin/master' into graphics-to-doc-img
This commit is contained in:
commit
9318dd6b2b
Binary file not shown.
Before Width: | Height: | Size: 227 KiB After Width: | Height: | Size: 208 KiB |
@ -135,7 +135,7 @@ as part of the `Stromnetze Research Initiative
|
||||
Workflow
|
||||
========
|
||||
|
||||
.. image:: ../doc/img/workflow.png
|
||||
.. image:: img/workflow.png
|
||||
:class: full-width
|
||||
:align: center
|
||||
|
||||
|
@ -21,8 +21,7 @@ Having downloaded the necessary data,
|
||||
With these and the externally extracted ENTSO-E online map topology
|
||||
(``data/entsoegridkit``), it can build a base PyPSA network with the following rules:
|
||||
|
||||
- :mod:`base_network` builds and stores the base network with all buses, HVAC lines and HVDC links, while
|
||||
- :mod:`build_bus_regions` determines `Voronoi cells <https://en.wikipedia.org/wiki/Voronoi_diagram>`__ for all substations.
|
||||
- :mod:`base_network` builds and stores the base network with all buses, HVAC lines and HVDC links, and determines `Voronoi cells <https://en.wikipedia.org/wiki/Voronoi_diagram>`__ for all substations.
|
||||
|
||||
Then the process continues by calculating conventional power plant capacities, potentials, and per-unit availability time series for variable renewable energy carriers and hydro power plants with the following rules:
|
||||
|
||||
@ -35,13 +34,6 @@ Then the process continues by calculating conventional power plant capacities, p
|
||||
The central rule :mod:`add_electricity` then ties all the different data inputs
|
||||
together into a detailed PyPSA network stored in ``networks/elec.nc``.
|
||||
|
||||
.. _busregions:
|
||||
|
||||
Rule ``build_bus_regions``
|
||||
=============================
|
||||
|
||||
.. automodule:: build_bus_regions
|
||||
|
||||
.. _cutout:
|
||||
|
||||
Rule ``build_cutout``
|
||||
|
@ -186,7 +186,7 @@ Upcoming Release
|
||||
|
||||
* Data on existing renewable capacities is now consistently taken from powerplantmatching (instead of being retrieved separately); the dataset has also been updated to include 2023 values.
|
||||
|
||||
* Added shapes to .nc file for different stages of the network object in `base_network`, `build_bus_regions`, and `cluster_network`.
|
||||
* Added shapes to .nc file for different stages of the network object in `base_network`, `simplify_network`, and `cluster_network`; the `build_bus_regions` rule is now integrated into the `base_network` rule.
|
||||
|
||||
* Fix p_nom_min of renewables generators for myopic approach and add check of existing capacities in `add_land_use_constraint_m`.
|
||||
|
||||
|
124
doc/tutorial.rst
124
doc/tutorial.rst
@ -133,32 +133,31 @@ This triggers a workflow of multiple preceding jobs that depend on each rule's i
|
||||
graph[bgcolor=white, margin=0];
|
||||
node[shape=box, style=rounded, fontname=sans, fontsize=10, penwidth=2];
|
||||
edge[penwidth=2, color=grey];
|
||||
0[label = "solve_network", color = "0.39 0.6 0.85", style="rounded"];
|
||||
1[label = "prepare_network\nll: copt\nopts: Co2L-24H", color = "0.29 0.6 0.85", style="rounded"];
|
||||
2[label = "add_extra_components", color = "0.28 0.6 0.85", style="rounded"];
|
||||
3[label = "cluster_network\nclusters: 6", color = "0.19 0.6 0.85", style="rounded"];
|
||||
4[label = "simplify_network\nsimpl: ", color = "0.01 0.6 0.85", style="rounded"];
|
||||
5[label = "add_electricity", color = "0.49 0.6 0.85", style="rounded"];
|
||||
6[label = "build_renewable_profiles\ntechnology: solar", color = "0.21 0.6 0.85", style="rounded"];
|
||||
7[label = "base_network", color = "0.27 0.6 0.85", style="rounded"];
|
||||
8[label = "build_shapes", color = "0.26 0.6 0.85", style="rounded"];
|
||||
9[label = "retrieve_databundle", color = "0.59 0.6 0.85", style="rounded"];
|
||||
10[label = "retrieve_natura_raster", color = "0.47 0.6 0.85", style="rounded"];
|
||||
11[label = "build_bus_regions", color = "0.13 0.6 0.85", style="rounded"];
|
||||
12[label = "retrieve_cutout\ncutout: be-03-2013-era5", color = "0.36 0.6 0.85", style="rounded,dashed"];
|
||||
13[label = "build_renewable_profiles\ntechnology: onwind", color = "0.21 0.6 0.85", style="rounded"];
|
||||
14[label = "build_renewable_profiles\ntechnology: offwind-ac", color = "0.21 0.6 0.85", style="rounded"];
|
||||
15[label = "build_ship_raster", color = "0.00 0.6 0.85", style="rounded"];
|
||||
16[label = "retrieve_ship_raster", color = "0.51 0.6 0.85", style="rounded,dashed"];
|
||||
17[label = "build_renewable_profiles\ntechnology: offwind-dc", color = "0.21 0.6 0.85", style="rounded"];
|
||||
18[label = "build_line_rating", color = "0.05 0.6 0.85", style="rounded"];
|
||||
19[label = "retrieve_cost_data\nyear: 2030", color = "0.15 0.6 0.85", style="rounded"];
|
||||
20[label = "build_powerplants", color = "0.54 0.6 0.85", style="rounded"];
|
||||
21[label = "build_electricity_demand", color = "0.52 0.6 0.85", style="rounded"];
|
||||
22[label = "retrieve_electricity_demand", color = "0.22 0.6 0.85", style="rounded"];
|
||||
23[label = "copy_config", color = "0.44 0.6 0.85", style="rounded"];
|
||||
0[label = "solve_network", color = "0.24 0.6 0.85", style="rounded"];
|
||||
1[label = "prepare_network\nll: vopt\nopts: Co2L-3H", color = "0.10 0.6 0.85", style="rounded"];
|
||||
2[label = "add_extra_components", color = "0.33 0.6 0.85", style="rounded"];
|
||||
3[label = "cluster_network\nclusters: 128", color = "0.59 0.6 0.85", style="rounded"];
|
||||
4[label = "simplify_network\nsimpl: ", color = "0.18 0.6 0.85", style="rounded"];
|
||||
5[label = "add_electricity", color = "0.48 0.6 0.85", style="rounded"];
|
||||
6[label = "build_renewable_profiles\ntechnology: solar", color = "0.29 0.6 0.85", style="rounded"];
|
||||
7[label = "base_network", color = "0.30 0.6 0.85", style="rounded"];
|
||||
8[label = "build_shapes", color = "0.61 0.6 0.85", style="rounded"];
|
||||
9[label = "retrieve_databundle", color = "0.06 0.6 0.85", style="rounded"];
|
||||
10[label = "retrieve_natura_raster", color = "0.03 0.6 0.85", style="rounded"];
|
||||
11[label = "retrieve_cutout\ncutout: europe-2013-sarah", color = "0.50 0.6 0.85", style="rounded"];
|
||||
12[label = "build_renewable_profiles\ntechnology: onwind", color = "0.29 0.6 0.85", style="rounded"];
|
||||
13[label = "retrieve_cutout\ncutout: europe-2013-era5", color = "0.50 0.6 0.85", style="rounded"];
|
||||
14[label = "build_renewable_profiles\ntechnology: offwind-ac", color = "0.29 0.6 0.85", style="rounded"];
|
||||
15[label = "build_ship_raster", color = "0.16 0.6 0.85", style="rounded"];
|
||||
16[label = "retrieve_ship_raster", color = "0.53 0.6 0.85", style="rounded"];
|
||||
17[label = "build_renewable_profiles\ntechnology: offwind-dc", color = "0.29 0.6 0.85", style="rounded"];
|
||||
18[label = "build_hydro_profile", color = "0.47 0.6 0.85", style="rounded"];
|
||||
19[label = "retrieve_cost_data\nyear: 2030", color = "0.21 0.6 0.85", style="rounded"];
|
||||
20[label = "build_powerplants", color = "0.56 0.6 0.85", style="rounded"];
|
||||
21[label = "build_electricity_demand", color = "0.54 0.6 0.85", style="rounded"];
|
||||
22[label = "retrieve_electricity_demand", color = "0.34 0.6 0.85", style="rounded"];
|
||||
23[label = "retrieve_synthetic_electricity_demand", color = "0.65 0.6 0.85", style="rounded"];
|
||||
1 -> 0
|
||||
23 -> 0
|
||||
2 -> 1
|
||||
19 -> 1
|
||||
3 -> 2
|
||||
@ -167,15 +166,14 @@ This triggers a workflow of multiple preceding jobs that depend on each rule's i
|
||||
19 -> 3
|
||||
5 -> 4
|
||||
19 -> 4
|
||||
11 -> 4
|
||||
7 -> 4
|
||||
6 -> 5
|
||||
13 -> 5
|
||||
12 -> 5
|
||||
14 -> 5
|
||||
17 -> 5
|
||||
7 -> 5
|
||||
18 -> 5
|
||||
7 -> 5
|
||||
19 -> 5
|
||||
11 -> 5
|
||||
20 -> 5
|
||||
9 -> 5
|
||||
21 -> 5
|
||||
@ -185,37 +183,32 @@ This triggers a workflow of multiple preceding jobs that depend on each rule's i
|
||||
10 -> 6
|
||||
8 -> 6
|
||||
11 -> 6
|
||||
12 -> 6
|
||||
8 -> 7
|
||||
9 -> 8
|
||||
8 -> 11
|
||||
7 -> 11
|
||||
7 -> 13
|
||||
9 -> 13
|
||||
10 -> 13
|
||||
8 -> 13
|
||||
11 -> 13
|
||||
12 -> 13
|
||||
7 -> 12
|
||||
9 -> 12
|
||||
10 -> 12
|
||||
8 -> 12
|
||||
13 -> 12
|
||||
7 -> 14
|
||||
9 -> 14
|
||||
10 -> 14
|
||||
15 -> 14
|
||||
8 -> 14
|
||||
11 -> 14
|
||||
12 -> 14
|
||||
13 -> 14
|
||||
16 -> 15
|
||||
12 -> 15
|
||||
13 -> 15
|
||||
7 -> 17
|
||||
9 -> 17
|
||||
10 -> 17
|
||||
15 -> 17
|
||||
8 -> 17
|
||||
11 -> 17
|
||||
12 -> 17
|
||||
7 -> 18
|
||||
12 -> 18
|
||||
13 -> 17
|
||||
8 -> 18
|
||||
13 -> 18
|
||||
7 -> 20
|
||||
22 -> 21
|
||||
23 -> 21
|
||||
}
|
||||
|
||||
|
|
||||
@ -226,28 +219,25 @@ In the terminal, this will show up as a list of jobs to be run:
|
||||
|
||||
Building DAG of jobs...
|
||||
Job stats:
|
||||
job count
|
||||
--------------------------- -------
|
||||
add_electricity 1
|
||||
add_extra_components 1
|
||||
base_network 1
|
||||
build_bus_regions 1
|
||||
build_electricity_demand 1
|
||||
build_line_rating 1
|
||||
build_powerplants 1
|
||||
build_renewable_profiles 4
|
||||
build_shapes 1
|
||||
build_ship_raster 1
|
||||
cluster_network 1
|
||||
copy_config 1
|
||||
prepare_network 1
|
||||
retrieve_cost_data 1
|
||||
retrieve_databundle 1
|
||||
retrieve_electricity_demand 1
|
||||
retrieve_natura_raster 1
|
||||
simplify_network 1
|
||||
solve_network 1
|
||||
total 22
|
||||
job count
|
||||
------------------------------------- -------
|
||||
add_electricity 1
|
||||
add_extra_components 1
|
||||
build_line_rating 1
|
||||
build_renewable_profiles 4
|
||||
build_ship_raster 1
|
||||
cluster_network 1
|
||||
prepare_network 1
|
||||
retrieve_cost_data 1
|
||||
retrieve_cutout 1
|
||||
retrieve_databundle 1
|
||||
retrieve_electricity_demand 1
|
||||
retrieve_natura_raster 1
|
||||
retrieve_ship_raster 1
|
||||
retrieve_synthetic_electricity_demand 1
|
||||
simplify_network 1
|
||||
solve_network 1
|
||||
total 19
|
||||
|
||||
|
||||
``snakemake`` then runs these jobs in the correct order.
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -86,7 +86,9 @@ rule base_network:
|
||||
offshore_shapes=resources("offshore_shapes.geojson"),
|
||||
europe_shape=resources("europe_shape.geojson"),
|
||||
output:
|
||||
resources("networks/base.nc"),
|
||||
base_network=resources("networks/base.nc"),
|
||||
regions_onshore=resources("regions_onshore.geojson"),
|
||||
regions_offshore=resources("regions_offshore.geojson"),
|
||||
log:
|
||||
logs("base_network.log"),
|
||||
benchmark:
|
||||
@ -127,27 +129,6 @@ rule build_shapes:
|
||||
"../scripts/build_shapes.py"
|
||||
|
||||
|
||||
rule build_bus_regions:
|
||||
params:
|
||||
countries=config_provider("countries"),
|
||||
input:
|
||||
country_shapes=resources("country_shapes.geojson"),
|
||||
offshore_shapes=resources("offshore_shapes.geojson"),
|
||||
base_network=resources("networks/base.nc"),
|
||||
output:
|
||||
regions_onshore=resources("regions_onshore.geojson"),
|
||||
regions_offshore=resources("regions_offshore.geojson"),
|
||||
log:
|
||||
logs("build_bus_regions.log"),
|
||||
threads: 1
|
||||
resources:
|
||||
mem_mb=1000,
|
||||
conda:
|
||||
"../envs/environment.yaml"
|
||||
script:
|
||||
"../scripts/build_bus_regions.py"
|
||||
|
||||
|
||||
if config["enable"].get("build_cutout", False):
|
||||
|
||||
rule build_cutout:
|
||||
|
@ -5,10 +5,7 @@
|
||||
|
||||
# coding: utf-8
|
||||
"""
|
||||
Creates the network topology from a `ENTSO-E map extract.
|
||||
|
||||
<https://github.com/PyPSA/GridKit/tree/master/entsoe>`_ (March 2022) as a PyPSA
|
||||
network.
|
||||
Creates the network topology from an `ENTSO-E map extract <https://github.com/PyPSA/GridKit/tree/master/entsoe>`_ (March 2022) as a PyPSA network.
|
||||
|
||||
Relevant Settings
|
||||
-----------------
|
||||
@ -59,8 +56,19 @@ Outputs
|
||||
.. image:: img/base.png
|
||||
:scale: 33 %
|
||||
|
||||
- ``resources/regions_onshore.geojson``:
|
||||
|
||||
.. image:: img/regions_onshore.png
|
||||
:scale: 33 %
|
||||
|
||||
- ``resources/regions_offshore.geojson``:
|
||||
|
||||
.. image:: img/regions_offshore.png
|
||||
:scale: 33 %
|
||||
|
||||
Description
|
||||
-----------
|
||||
Creates the network topology from an ENTSO-E map extract, and create Voronoi shapes for each bus representing both onshore and offshore regions.
|
||||
"""
|
||||
|
||||
import logging
|
||||
@ -75,11 +83,11 @@ import shapely
|
||||
import shapely.prepared
|
||||
import shapely.wkt
|
||||
import yaml
|
||||
from _helpers import configure_logging, get_snapshots, set_scenario_config
|
||||
from _helpers import REGION_COLS, configure_logging, get_snapshots, set_scenario_config
|
||||
from packaging.version import Version, parse
|
||||
from scipy import spatial
|
||||
from scipy.sparse import csgraph
|
||||
from shapely.geometry import LineString, Point
|
||||
from shapely.geometry import LineString, Point, Polygon
|
||||
|
||||
PD_GE_2_2 = parse(pd.__version__) >= Version("2.2")
|
||||
|
||||
@ -779,9 +787,147 @@ def base_network(
|
||||
return n
|
||||
|
||||
|
||||
def voronoi_partition_pts(points, outline):
|
||||
"""
|
||||
Compute the polygons of a voronoi partition of `points` within the polygon
|
||||
`outline`. Taken from
|
||||
https://github.com/FRESNA/vresutils/blob/master/vresutils/graph.py.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
points : Nx2 - ndarray[dtype=float]
|
||||
outline : Polygon
|
||||
Returns
|
||||
-------
|
||||
polygons : N - ndarray[dtype=Polygon|MultiPolygon]
|
||||
"""
|
||||
points = np.asarray(points)
|
||||
|
||||
if len(points) == 1:
|
||||
polygons = [outline]
|
||||
else:
|
||||
xmin, ymin = np.amin(points, axis=0)
|
||||
xmax, ymax = np.amax(points, axis=0)
|
||||
xspan = xmax - xmin
|
||||
yspan = ymax - ymin
|
||||
|
||||
# to avoid any network positions outside all Voronoi cells, append
|
||||
# the corners of a rectangle framing these points
|
||||
vor = spatial.Voronoi(
|
||||
np.vstack(
|
||||
(
|
||||
points,
|
||||
[
|
||||
[xmin - 3.0 * xspan, ymin - 3.0 * yspan],
|
||||
[xmin - 3.0 * xspan, ymax + 3.0 * yspan],
|
||||
[xmax + 3.0 * xspan, ymin - 3.0 * yspan],
|
||||
[xmax + 3.0 * xspan, ymax + 3.0 * yspan],
|
||||
],
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
polygons = []
|
||||
for i in range(len(points)):
|
||||
poly = Polygon(vor.vertices[vor.regions[vor.point_region[i]]])
|
||||
|
||||
if not poly.is_valid:
|
||||
poly = poly.buffer(0)
|
||||
|
||||
with np.errstate(invalid="ignore"):
|
||||
poly = poly.intersection(outline)
|
||||
|
||||
polygons.append(poly)
|
||||
|
||||
return polygons
|
||||
|
||||
|
||||
def build_bus_shapes(n, country_shapes, offshore_shapes, countries):
|
||||
country_shapes = gpd.read_file(country_shapes).set_index("name")["geometry"]
|
||||
offshore_shapes = gpd.read_file(offshore_shapes)
|
||||
offshore_shapes = offshore_shapes.reindex(columns=REGION_COLS).set_index("name")[
|
||||
"geometry"
|
||||
]
|
||||
|
||||
onshore_regions = []
|
||||
offshore_regions = []
|
||||
|
||||
for country in countries:
|
||||
c_b = n.buses.country == country
|
||||
|
||||
onshore_shape = country_shapes[country]
|
||||
onshore_locs = (
|
||||
n.buses.loc[c_b & n.buses.onshore_bus]
|
||||
.sort_values(
|
||||
by="substation_lv", ascending=False
|
||||
) # preference for substations
|
||||
.drop_duplicates(subset=["x", "y"], keep="first")[["x", "y"]]
|
||||
)
|
||||
onshore_regions.append(
|
||||
gpd.GeoDataFrame(
|
||||
{
|
||||
"name": onshore_locs.index,
|
||||
"x": onshore_locs["x"],
|
||||
"y": onshore_locs["y"],
|
||||
"geometry": voronoi_partition_pts(
|
||||
onshore_locs.values, onshore_shape
|
||||
),
|
||||
"country": country,
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
if country not in offshore_shapes.index:
|
||||
continue
|
||||
offshore_shape = offshore_shapes[country]
|
||||
offshore_locs = n.buses.loc[c_b & n.buses.substation_off, ["x", "y"]]
|
||||
offshore_regions_c = gpd.GeoDataFrame(
|
||||
{
|
||||
"name": offshore_locs.index,
|
||||
"x": offshore_locs["x"],
|
||||
"y": offshore_locs["y"],
|
||||
"geometry": voronoi_partition_pts(offshore_locs.values, offshore_shape),
|
||||
"country": country,
|
||||
}
|
||||
)
|
||||
offshore_regions_c = offshore_regions_c.loc[offshore_regions_c.area > 1e-2]
|
||||
offshore_regions.append(offshore_regions_c)
|
||||
|
||||
shapes = pd.concat(onshore_regions, ignore_index=True)
|
||||
|
||||
return onshore_regions, offshore_regions, shapes
|
||||
|
||||
|
||||
def append_bus_shapes(n, shapes, type):
|
||||
"""
|
||||
Append shapes to the network. If shapes with the same component and type
|
||||
already exist, they will be removed.
|
||||
|
||||
Parameters:
|
||||
n (pypsa.Network): The network to which the shapes will be appended.
|
||||
shapes (geopandas.GeoDataFrame): The shapes to be appended.
|
||||
**kwargs: Additional keyword arguments used in `n.madd`.
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
remove = n.shapes.query("component == 'Bus' and type == @type").index
|
||||
n.mremove("Shape", remove)
|
||||
|
||||
offset = n.shapes.index.astype(int).max() + 1 if not n.shapes.empty else 0
|
||||
shapes = shapes.rename(lambda x: int(x) + offset)
|
||||
n.madd(
|
||||
"Shape",
|
||||
shapes.index,
|
||||
geometry=shapes.geometry,
|
||||
idx=shapes.name,
|
||||
component="Bus",
|
||||
type=type,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if "snakemake" not in globals():
|
||||
|
||||
from _helpers import mock_snakemake
|
||||
|
||||
snakemake = mock_snakemake("base_network")
|
||||
@ -803,5 +949,22 @@ if __name__ == "__main__":
|
||||
snakemake.config,
|
||||
)
|
||||
|
||||
onshore_regions, offshore_regions, shapes = build_bus_shapes(
|
||||
n,
|
||||
snakemake.input.country_shapes,
|
||||
snakemake.input.offshore_shapes,
|
||||
snakemake.params.countries,
|
||||
)
|
||||
|
||||
shapes.to_file(snakemake.output.regions_onshore)
|
||||
append_bus_shapes(n, shapes, "onshore")
|
||||
|
||||
if offshore_regions:
|
||||
shapes = pd.concat(offshore_regions, ignore_index=True)
|
||||
shapes.to_file(snakemake.output.regions_offshore)
|
||||
append_bus_shapes(n, shapes, "offshore")
|
||||
else:
|
||||
offshore_shapes.to_frame().to_file(snakemake.output.regions_offshore)
|
||||
|
||||
n.meta = snakemake.config
|
||||
n.export_to_netcdf(snakemake.output[0])
|
||||
n.export_to_netcdf(snakemake.output.base_network)
|
||||
|
@ -1,218 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# SPDX-FileCopyrightText: : 2017-2024 The PyPSA-Eur Authors
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
"""
|
||||
Creates Voronoi shapes for each bus representing both onshore and offshore
|
||||
regions.
|
||||
|
||||
Relevant Settings
|
||||
-----------------
|
||||
|
||||
.. code:: yaml
|
||||
|
||||
countries:
|
||||
|
||||
.. seealso::
|
||||
Documentation of the configuration file ``config/config.yaml`` at
|
||||
:ref:`toplevel_cf`
|
||||
|
||||
Inputs
|
||||
------
|
||||
|
||||
- ``resources/country_shapes.geojson``: confer :ref:`shapes`
|
||||
- ``resources/offshore_shapes.geojson``: confer :ref:`shapes`
|
||||
- ``networks/base.nc``: confer :ref:`base`
|
||||
|
||||
Outputs
|
||||
-------
|
||||
|
||||
- ``resources/regions_onshore.geojson``:
|
||||
|
||||
.. image:: img/regions_onshore.png
|
||||
:scale: 33 %
|
||||
|
||||
- ``resources/regions_offshore.geojson``:
|
||||
|
||||
.. image:: img/regions_offshore.png
|
||||
:scale: 33 %
|
||||
|
||||
Description
|
||||
-----------
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
import geopandas as gpd
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import pypsa
|
||||
from _helpers import REGION_COLS, configure_logging, set_scenario_config
|
||||
from scipy.spatial import Voronoi
|
||||
from shapely.geometry import Polygon
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def voronoi_partition_pts(points, outline):
|
||||
"""
|
||||
Compute the polygons of a voronoi partition of `points` within the polygon
|
||||
`outline`. Taken from
|
||||
https://github.com/FRESNA/vresutils/blob/master/vresutils/graph.py.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
points : Nx2 - ndarray[dtype=float]
|
||||
outline : Polygon
|
||||
Returns
|
||||
-------
|
||||
polygons : N - ndarray[dtype=Polygon|MultiPolygon]
|
||||
"""
|
||||
points = np.asarray(points)
|
||||
|
||||
if len(points) == 1:
|
||||
polygons = [outline]
|
||||
else:
|
||||
xmin, ymin = np.amin(points, axis=0)
|
||||
xmax, ymax = np.amax(points, axis=0)
|
||||
xspan = xmax - xmin
|
||||
yspan = ymax - ymin
|
||||
|
||||
# to avoid any network positions outside all Voronoi cells, append
|
||||
# the corners of a rectangle framing these points
|
||||
vor = Voronoi(
|
||||
np.vstack(
|
||||
(
|
||||
points,
|
||||
[
|
||||
[xmin - 3.0 * xspan, ymin - 3.0 * yspan],
|
||||
[xmin - 3.0 * xspan, ymax + 3.0 * yspan],
|
||||
[xmax + 3.0 * xspan, ymin - 3.0 * yspan],
|
||||
[xmax + 3.0 * xspan, ymax + 3.0 * yspan],
|
||||
],
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
polygons = []
|
||||
for i in range(len(points)):
|
||||
poly = Polygon(vor.vertices[vor.regions[vor.point_region[i]]])
|
||||
|
||||
if not poly.is_valid:
|
||||
poly = poly.buffer(0)
|
||||
|
||||
with np.errstate(invalid="ignore"):
|
||||
poly = poly.intersection(outline)
|
||||
|
||||
polygons.append(poly)
|
||||
|
||||
return polygons
|
||||
|
||||
|
||||
def append_bus_shapes(n, shapes, type):
|
||||
"""
|
||||
Append shapes to the network. If shapes with the same component and type
|
||||
already exist, they will be removed.
|
||||
|
||||
Parameters:
|
||||
n (pypsa.Network): The network to which the shapes will be appended.
|
||||
shapes (geopandas.GeoDataFrame): The shapes to be appended.
|
||||
**kwargs: Additional keyword arguments used in `n.madd`.
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
remove = n.shapes.query("component == 'Bus' and type == @type").index
|
||||
n.mremove("Shape", remove)
|
||||
|
||||
offset = n.shapes.index.astype(int).max() + 1 if not n.shapes.empty else 0
|
||||
shapes = shapes.rename(lambda x: int(x) + offset)
|
||||
n.madd(
|
||||
"Shape",
|
||||
shapes.index,
|
||||
geometry=shapes.geometry,
|
||||
idx=shapes.name,
|
||||
component="Bus",
|
||||
type=type,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if "snakemake" not in globals():
|
||||
from _helpers import mock_snakemake
|
||||
|
||||
snakemake = mock_snakemake("build_bus_regions")
|
||||
configure_logging(snakemake)
|
||||
set_scenario_config(snakemake)
|
||||
|
||||
countries = snakemake.params.countries
|
||||
|
||||
base_network = snakemake.input.base_network
|
||||
n = pypsa.Network(base_network)
|
||||
|
||||
country_shapes = gpd.read_file(snakemake.input.country_shapes).set_index("name")[
|
||||
"geometry"
|
||||
]
|
||||
offshore_shapes = gpd.read_file(snakemake.input.offshore_shapes)
|
||||
offshore_shapes = offshore_shapes.reindex(columns=REGION_COLS).set_index("name")[
|
||||
"geometry"
|
||||
]
|
||||
|
||||
onshore_regions = []
|
||||
offshore_regions = []
|
||||
|
||||
for country in countries:
|
||||
c_b = n.buses.country == country
|
||||
|
||||
onshore_shape = country_shapes[country]
|
||||
onshore_locs = (
|
||||
n.buses.loc[c_b & n.buses.onshore_bus]
|
||||
.sort_values(
|
||||
by="substation_lv", ascending=False
|
||||
) # preference for substations
|
||||
.drop_duplicates(subset=["x", "y"], keep="first")[["x", "y"]]
|
||||
)
|
||||
onshore_regions.append(
|
||||
gpd.GeoDataFrame(
|
||||
{
|
||||
"name": onshore_locs.index,
|
||||
"x": onshore_locs["x"],
|
||||
"y": onshore_locs["y"],
|
||||
"geometry": voronoi_partition_pts(
|
||||
onshore_locs.values, onshore_shape
|
||||
),
|
||||
"country": country,
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
if country not in offshore_shapes.index:
|
||||
continue
|
||||
offshore_shape = offshore_shapes[country]
|
||||
offshore_locs = n.buses.loc[c_b & n.buses.substation_off, ["x", "y"]]
|
||||
offshore_regions_c = gpd.GeoDataFrame(
|
||||
{
|
||||
"name": offshore_locs.index,
|
||||
"x": offshore_locs["x"],
|
||||
"y": offshore_locs["y"],
|
||||
"geometry": voronoi_partition_pts(offshore_locs.values, offshore_shape),
|
||||
"country": country,
|
||||
}
|
||||
)
|
||||
offshore_regions_c = offshore_regions_c.loc[offshore_regions_c.area > 1e-2]
|
||||
offshore_regions.append(offshore_regions_c)
|
||||
|
||||
shapes = pd.concat(onshore_regions, ignore_index=True)
|
||||
shapes.to_file(snakemake.output.regions_onshore)
|
||||
append_bus_shapes(n, shapes, "onshore")
|
||||
|
||||
if offshore_regions:
|
||||
shapes = pd.concat(offshore_regions, ignore_index=True)
|
||||
shapes.to_file(snakemake.output.regions_offshore)
|
||||
append_bus_shapes(n, shapes, "offshore")
|
||||
|
||||
else:
|
||||
offshore_shapes.to_frame().to_file(snakemake.output.regions_offshore)
|
||||
|
||||
# save network with shapes
|
||||
n.export_to_netcdf(base_network)
|
@ -135,7 +135,7 @@ import pypsa
|
||||
import seaborn as sns
|
||||
from _helpers import configure_logging, set_scenario_config, update_p_nom_max
|
||||
from add_electricity import load_costs
|
||||
from build_bus_regions import append_bus_shapes
|
||||
from base_network import append_bus_shapes
|
||||
from packaging.version import Version, parse
|
||||
from pypsa.clustering.spatial import (
|
||||
busmap_by_greedy_modularity,
|
||||
|
@ -95,7 +95,7 @@ import pypsa
|
||||
import scipy as sp
|
||||
from _helpers import configure_logging, set_scenario_config, update_p_nom_max
|
||||
from add_electricity import load_costs
|
||||
from build_bus_regions import append_bus_shapes
|
||||
from base_network import append_bus_shapes
|
||||
from cluster_network import cluster_regions, clustering_for_n_clusters
|
||||
from pypsa.clustering.spatial import (
|
||||
aggregateoneport,
|
||||
|
Loading…
Reference in New Issue
Block a user