Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion data/transmission_projects/manual/new_links.csv
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,6 @@ TYNDP2024_1197,Hornsea (UK),Moerdijk (NL),460,1200,under_consideration,2031,"{na
TYNDP2024_1210,La Farga (ES),La Spezia (IT),725,2091,under_consideration,2032,"{name:APOLLO-LINK,url:{name:TYNDP2024_1210,url:https://eepublicdownloads.blob.core.windows.net/public-cdn-container/tyndp-documents/TYNDP2024/240220_TYNDP2024_project_portfolio.xlsx}}",2.883,42.062,9.884,44.107
TYNDP2024_1211,Lubmin (DE),Sindi (EE),960,2000,under_consideration,2037,"{name:Baltic WindConnector (BWC),url:{name:TYNDP2024_1211,url:https://eepublicdownloads.blob.core.windows.net/public-cdn-container/tyndp-documents/TYNDP2024/240220_TYNDP2024_project_portfolio.xlsx}}",13.686,54.139,24.671903,58.429161
TYNDP2024_1216,Constanta (RO),Albertirsa (HU),930,2500,under_consideration,2030,"{name:High-Voltage Direct Current Interconnector Project Romania-Hungary,url:{name:TYNDP2024_1216,url:https://eepublicdownloads.blob.core.windows.net/public-cdn-container/tyndp-documents/TYNDP2024/240220_TYNDP2024_project_portfolio.xlsx}}",28.588,44.201,19.584,47.224
TYNDP2024_1229,Ovelgönne (DE),Marxheim (DE),433,4000,in_permitting,2035,"{name:Rhine-Main-Link,url:{name:TYNDP2024_1229,url:https://eepublicdownloads.blob.core.windows.net/public-cdn-container/tyndp-documents/TYNDP2024/240220_TYNDP2024_project_portfolio.xlsx}}",8.379,53.315,8.435,50.078
TYNDP2024_1229_NEP_DC34,Ovelgönne (DE),Bürstadt (DE),523,2000,in_permitting,2033,"{name:Rhine-Main-Link,url:{name:TYNDP2024_1229,url:https://eepublicdownloads.blob.core.windows.net/public-cdn-container/tyndp-documents/TYNDP2024/240220_TYNDP2024_project_portfolio.xlsx}}",8.4209977,53.3427799,8.4747076,49.6531823
TYNDP2024_1229_NEP_DC35,Ovelgönne (DE),Marxheim (DE),461,2000,in_permitting,2035,"{name:Rhine-Main-Link,url:{name:TYNDP2024_1229,url:https://eepublicdownloads.blob.core.windows.net/public-cdn-container/tyndp-documents/TYNDP2024/240220_TYNDP2024_project_portfolio.xlsx}}",8.4209977,53.3427799,8.4320722,50.0724011
TYNDP2024_1231,Arachthos (GR),Ottenhofen (DE),600,3000,under_consideration,2035,"{name:Green Aegean Interconnector,url:{name:TYNDP2024_1231,url:https://eepublicdownloads.blob.core.windows.net/public-cdn-container/tyndp-documents/TYNDP2024/240220_TYNDP2024_project_portfolio.xlsx}}",20.967,39.185,11.868,48.207
2 changes: 2 additions & 0 deletions doc/release_notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
<!-- Upcoming Release -->
<!-- ================= -->

* Fix: Removed a duplicate tranmission project between NEP and TYNDP sources; improved the matching algorithm for duplicate projects

* Feature: New switches `industry: solid_biomass_for_industry_cc`, `industry: gas_for_industry_cc` and `industry: process_emissions_cc` to disable the carbon capture options for solid biomass for industry, gas for industry and industrial process emissions. All default to `true`, which matches the previous behaviour ([#2357](https://github.com/PyPSA/pypsa-eur/pull/2357)).

* Feature: New option `sector: electrolysis` to allow hydrogen electrolysis to be configurable ([#2358](https://github.com/PyPSA/pypsa-eur/pull/2358)).
Expand Down
76 changes: 48 additions & 28 deletions scripts/build_transmission_projects.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,14 +134,16 @@ def connect_new_lines(
return lines, new_buses_df


def get_branch_coords_from_geometry(linestring, reversed=False):
def get_branch_coords_from_geometry(
linestring: LineString | str, reversed: bool = False
) -> np.ndarray:
"""
Reduces a linestring to its start and end points. Used to simplify the
linestring which can have more than two points.

Parameters
----------
linestring : Shapely linestring
linestring : Shapely linestring or WKT string
The linestring to reduce.
reversed : bool, optional
If True, returns the end and start points instead of the start and end points.
Expand All @@ -152,30 +154,14 @@ def get_branch_coords_from_geometry(linestring, reversed=False):
numpy.ndarray
Flattened array of start and end coordinates.
"""
if isinstance(linestring, str):
linestring = shapely.from_wkt(linestring)
coords = np.asarray(linestring.coords)
ind = [0, -1] if not reversed else [-1, 0]
start_end_coords = coords[ind]
return start_end_coords.flatten()


def get_branch_coords_from_buses(line):
"""
Gets line string for branch component in an pypsa network.

Parameters
----------
line : pandas.Series
A row from a branch component DataFrame with bus0 and bus1 attributes.

Returns
-------
numpy.ndarray: Flattened array of start and end coordinates.
"""
start_coords = n.buses.loc[line.bus0, ["x", "y"]].values
end_coords = n.buses.loc[line.bus1, ["x", "y"]].values
return np.array([start_coords, end_coords]).flatten()


def get_bus_coords_from_port(linestring, port=0):
"""
Extracts the coordinates of a specified port from a given linestring.
Expand All @@ -196,30 +182,59 @@ def get_bus_coords_from_port(linestring, port=0):
return coords


def find_closest_lines(lines, new_lines, distance_upper_bound=0.1, type="new"):
def get_existing_branch_coords(
lines: pd.DataFrame, buses: pd.DataFrame | None = None
) -> np.ndarray:
"""Extract geometry endpoints, falling back to buses for missing geometry."""
coords = []
for name, branch in lines.iterrows():
geometry = branch.get("geometry")
if isinstance(geometry, str):
geometry = shapely.from_wkt(geometry) if geometry.strip() else None
if pd.isna(geometry) or geometry.is_empty:
if buses is None:
raise ValueError("Missing geometry; bus coordinates are required.")
logger.warning(
"Missing or empty geometry for %s; using bus coordinates.", name
)
coords.append(
buses.loc[[branch.bus0, branch.bus1], ["x", "y"]].to_numpy().flatten()
)
else:
coords.append(get_branch_coords_from_geometry(geometry))
return np.vstack(coords)


def find_closest_lines(
lines: pd.DataFrame,
new_lines: pd.DataFrame,
distance_upper_bound: float = 0.1,
type: str = "new",
buses: pd.DataFrame | None = None,
) -> pd.Series:
"""
Find the closest lines in the existing set of lines to a set of new lines.

Parameters
----------
lines (pandas.DataFrame): DataFrame of the existing lines.
lines (pandas.DataFrame): DataFrame of the existing lines with column geometry.
new_lines (pandas.DataFrame): DataFrame with column geometry containing the new lines.
distance_upper_bound (float, optional): Maximum distance to consider a line as a match. Defaults to 0.1 which corresponds to approximately 15 km.
distance_upper_bound (float, optional): Maximum Euclidean distance between geometry endpoints (x0, y0, x1, y1), in degrees. Defaults to 0.1.
buses (pandas.DataFrame, optional): Bus coordinates used when existing geometry is missing or empty.

Returns
-------
pandas.Series: Series containing with index the new lines and values providing closest existing line.
"""

# get coordinates of start and end points of all lines, for new lines we need to check both directions
treelines = lines.apply(get_branch_coords_from_buses, axis=1)
treelines = get_existing_branch_coords(lines, buses)
querylines = pd.concat(
[
new_lines["geometry"].apply(get_branch_coords_from_geometry),
new_lines["geometry"].apply(get_branch_coords_from_geometry, reversed=True),
]
)
treelines = np.vstack(treelines)
querylines = np.vstack(querylines)
tree = spatial.KDTree(treelines)
dist, ind = tree.query(querylines, distance_upper_bound=distance_upper_bound)
Expand Down Expand Up @@ -399,7 +414,7 @@ def add_projects(
lines, n, new_buses_df, bus_carrier="AC"
)
duplicate_lines = find_closest_lines(
n.lines, new_lines, distance_upper_bound=0.10, type="new"
n.lines, new_lines, distance_upper_bound=0.10, type="new", buses=n.buses
)
new_lines = new_lines.drop(duplicate_lines.index, errors="ignore")
new_lines_df = pd.concat([new_lines_df, new_lines])
Expand All @@ -415,7 +430,7 @@ def add_projects(
bus_carrier=["AC", "DC"],
)
duplicate_links = find_closest_lines(
n.links, new_links, distance_upper_bound=0.10, type="new"
n.links, new_links, distance_upper_bound=0.10, type="new", buses=n.buses
)
new_links = new_links.drop(duplicate_links.index, errors="ignore")
set_underwater_fraction(new_links, offshore_shapes)
Expand All @@ -424,7 +439,11 @@ def add_projects(
n.add("Link", new_links.index, **new_links)
elif key == "upgraded_lines":
line_map = find_closest_lines(
n.lines, lines, distance_upper_bound=0.30, type="upgraded"
n.lines,
lines,
distance_upper_bound=0.30,
type="upgraded",
buses=n.buses,
)
upgraded_lines = lines.loc[line_map.index]
lines_to_adjust = adjust_decommissioning(upgraded_lines, line_map)
Expand All @@ -437,6 +456,7 @@ def add_projects(
lines,
distance_upper_bound=0.30,
type="upgraded",
buses=n.buses,
)
upgraded_links = lines.loc[line_map.index]
links_to_adjust = adjust_decommissioning(upgraded_links, line_map)
Expand Down
Loading