diff --git a/network-area-diagram/src/main/java/com/powsybl/nad/CountryDiagram.java b/network-area-diagram/src/main/java/com/powsybl/nad/CountryDiagram.java new file mode 100644 index 000000000..cc0cf0c74 --- /dev/null +++ b/network-area-diagram/src/main/java/com/powsybl/nad/CountryDiagram.java @@ -0,0 +1,98 @@ +/** + * Copyright (c) 2021, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * SPDX-License-Identifier: MPL-2.0 + */ +package com.powsybl.nad; + +import com.powsybl.iidm.network.Network; +import com.powsybl.nad.build.iidm.DefaultCountryStyleProvider; +import com.powsybl.nad.build.iidm.CountryGraphBuilder; +import com.powsybl.nad.build.iidm.DefaultCountryLabelProvider; +import com.powsybl.nad.model.Graph; +import com.powsybl.nad.svg.SvgParameters; +import com.powsybl.nad.svg.SvgWriter; +import com.powsybl.nad.svg.metadata.DiagramMetadata; +import org.apache.commons.io.output.NullWriter; + +import java.io.IOException; +import java.io.StringWriter; +import java.io.UncheckedIOException; +import java.io.Writer; +import java.nio.file.Path; +import java.util.Objects; + +/** + * @author Florian Dupuy {@literal } + */ +public final class CountryDiagram { + + private CountryDiagram() { + } + + public static void draw(Network network, Path svgFile) { + draw(network, svgFile, new NadParameters()); + } + + public static void draw(Network network, Writer writer, Writer metadataWriter) { + draw(network, writer, metadataWriter, new NadParameters()); + } + + public static void draw(Network network, Path svgFile, NadParameters param) { + Objects.requireNonNull(network); + Objects.requireNonNull(svgFile); + Objects.requireNonNull(param); + + Graph graph = getLayoutResult(network, param); + createSvgWriter(network, param).writeSvg(graph, svgFile); + createMetadata(graph, param).writeJson(getMetadataPath(svgFile)); + } + + public static void draw(Network network, Writer writer, Writer metadataWriter, NadParameters param) { + Objects.requireNonNull(network); + Objects.requireNonNull(writer); + Objects.requireNonNull(metadataWriter); + Objects.requireNonNull(param); + + Graph graph = getLayoutResult(network, param); + createSvgWriter(network, param).writeSvg(graph, writer); + createMetadata(graph, param).writeJson(metadataWriter); + } + + private static DiagramMetadata createMetadata(Graph graph, NadParameters param) { + return new DiagramMetadata(param.getLayoutParameters(), param.getSvgParameters()).addMetadata(graph); + } + + private static Graph getLayoutResult(Network network, NadParameters param) { + var builder = new CountryGraphBuilder(network, param.getIdProviderFactory().create(), new DefaultCountryLabelProvider()); + var graph = builder.buildGraph(); + param.getLayoutFactory().create().run(graph, param.getLayoutParameters()); + return graph; + } + + private static SvgWriter createSvgWriter(Network network, NadParameters param) { + return new SvgWriter(param.getSvgParameters(), new DefaultCountryStyleProvider(network), + param.getComponentLibrary(), param.getEdgeRouting()); + } + + private static Path getMetadataPath(Path svgPath) { + Path dir = svgPath.toAbsolutePath().getParent(); + String svgFileName = svgPath.getFileName().toString(); + if (!svgFileName.endsWith(".svg")) { + svgFileName = svgFileName + ".svg"; + } + return dir.resolve(svgFileName.replace(".svg", "_metadata.json")); + } + + public static String drawToString(Network network, SvgParameters svgParameters) { + try (StringWriter writer = new StringWriter()) { + NadParameters nadParameters = new NadParameters().setSvgParameters(svgParameters); + draw(network, writer, NullWriter.INSTANCE, nadParameters); + return writer.toString(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } +} diff --git a/network-area-diagram/src/main/java/com/powsybl/nad/build/iidm/CountryGraphBuilder.java b/network-area-diagram/src/main/java/com/powsybl/nad/build/iidm/CountryGraphBuilder.java new file mode 100644 index 000000000..6e7a64131 --- /dev/null +++ b/network-area-diagram/src/main/java/com/powsybl/nad/build/iidm/CountryGraphBuilder.java @@ -0,0 +1,217 @@ +/* + * Copyright (c) 2025, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * SPDX-License-Identifier: MPL-2.0 + */ +package com.powsybl.nad.build.iidm; + +import com.powsybl.commons.PowsyblException; +import com.powsybl.iidm.network.*; +import com.powsybl.nad.build.GraphBuilder; +import com.powsybl.nad.model.BranchEdge; +import com.powsybl.nad.model.BusNode; +import com.powsybl.nad.model.Graph; +import com.powsybl.nad.model.VoltageLevelNode; +import com.powsybl.nad.svg.EdgeInfo; + +import java.util.*; + +/** + * Graph builder that creates a graph based on substation countries. + * Creates one VoltageLevelNode with one BusNode for each country found in the network substations, + * and BranchEdges between countries representing the existing lines between countries. + * + * @author Florian Dupuy {@literal } + */ +public class CountryGraphBuilder implements GraphBuilder { + + private final Network network; + private final IdProvider idProvider; + private final CountryLabelProvider labelProvider; + + /** + * Creates a new CountryGraphBuilder. + * + * @param network the network + * @param idProvider the ID provider + * @param labelProvider the label provider + */ + public CountryGraphBuilder(Network network, IdProvider idProvider, CountryLabelProvider labelProvider) { + this.network = Objects.requireNonNull(network); + this.idProvider = Objects.requireNonNull(idProvider); + this.labelProvider = Objects.requireNonNull(labelProvider); + } + + @Override + public Graph buildGraph() { + Graph graph = new Graph(); + + // Get all countries from substations + + // Create a VoltageLevelNode with one BusNode for each country + Map countryToVlNode = new EnumMap<>(Country.class); + + for (Country country : getCountries()) { + CountryLabelProvider.CountryLegend legend = labelProvider.getCountryLegend(country); + VoltageLevelNode vlNode = new VoltageLevelNode( + idProvider, + country.name(), + country.name(), + false, + true, + legend.header(), + legend.footer() + ); + + BusNode busNode = new BusNode(idProvider, country.name(), Collections.emptyList(), null); + + vlNode.addBusNode(busNode); + graph.addNode(vlNode); + graph.addTextNode(vlNode); + + countryToVlNode.put(country, vlNode); + } + + // Create edges between countries based on lines + createCountryConnections(graph, countryToVlNode); + + return graph; + } + + /** + * Gets all countries from substations in the network. + * + * @return list of countries + */ + private List getCountries() { + return network.getSubstationStream() + .map(Substation::getNullableCountry) + .filter(Objects::nonNull) + .distinct() + .toList(); + } + + /** + * Creates connections (BranchEdges) between countries based on lines in the network. + * + * @param graph the graph to add edges to + * @param countryToVlNode mapping from country to voltage level node + */ + private void createCountryConnections(Graph graph, + Map countryToVlNode) { + + // Map to store aggregated active powers between countries + Map borderEdgesMap = new LinkedHashMap<>(); + + // Process all lines + network.getLineStream().forEach(line -> fillBorderEdgesMap(line, borderEdgesMap)); + network.getTieLineStream().forEach(tieLine -> fillBorderEdgesMap(tieLine, borderEdgesMap)); + + // Process HVDC lines + network.getHvdcLineStream().forEach(hvdcLine -> fillBorderEdgesMap(hvdcLine, borderEdgesMap)); + + // Create BranchEdges for each country pair with connections + borderEdgesMap.forEach((border, borderEdges) -> { + Country country1 = border.country1(); + Country country2 = border.country2(); + + VoltageLevelNode vlNode1 = countryToVlNode.get(country1); + VoltageLevelNode vlNode2 = countryToVlNode.get(country2); + + if (vlNode1 != null && vlNode2 != null) { + createCountryEdge(graph, country1, country2, borderEdges, vlNode1, vlNode2); + } + }); + } + + /** + * Processes a branch to aggregate active power between countries. + */ + private void fillBorderEdgesMap(Branch branch, Map allBorderLines) { + Country country1 = getCountryFromTerminal(branch.getTerminal1()); + Country country2 = getCountryFromTerminal(branch.getTerminal2()); + + if (country1 != null && country2 != null && country1 != country2) { + Border pair = new Border(country1, country2); + allBorderLines.computeIfAbsent(pair, k -> new BorderEdges()) + .addBranch(branch); + } + } + + /** + * Processes a tie line to aggregate active power between countries. + */ + private void fillBorderEdgesMap(HvdcLine hvdcLine, Map allBorderLines) { + Country country1 = getCountryFromTerminal(hvdcLine.getConverterStation1().getTerminal()); + Country country2 = getCountryFromTerminal(hvdcLine.getConverterStation2().getTerminal()); + + if (country1 != null && country2 != null && country1 != country2) { + Border pair = new Border(country1, country2); + allBorderLines.computeIfAbsent(pair, k -> new BorderEdges()) + .hvdcLines().add(hvdcLine); + } + } + + /** + * Gets the country from a terminal's substation. + */ + private Country getCountryFromTerminal(Terminal terminal) { + return terminal.getVoltageLevel().getSubstation().map(Substation::getNullableCountry).orElse(null); + } + + /** + * Creates a BranchEdge between two countries. + */ + private void createCountryEdge(Graph graph, Country country1, Country country2, BorderEdges borderEdges, + VoltageLevelNode vlNode1, VoltageLevelNode vlNode2) { + + String edgeId = country1.name() + "-" + country2.name(); + Optional edgeInfo1 = labelProvider.getCountryEdgeInfo(country1, country2, borderEdges.lines, borderEdges.tieLines, borderEdges.hvdcLines, BranchEdge.Side.ONE); + Optional edgeInfo2 = labelProvider.getCountryEdgeInfo(country1, country2, borderEdges.lines, borderEdges.tieLines, borderEdges.hvdcLines, BranchEdge.Side.TWO); + String label = labelProvider.getBranchLabel(country1, country2, borderEdges.lines, borderEdges.tieLines, borderEdges.hvdcLines); + + BranchEdge edge = new BranchEdge( + idProvider, + edgeId, + edgeId, + BranchEdge.LINE_EDGE, + edgeInfo1.orElse(null), + edgeInfo2.orElse(null), + label + ); + + graph.addEdge(vlNode1, vlNode1.getBusNodes().getFirst(), vlNode2, vlNode2.getBusNodes().getFirst(), edge); + } + + /** + * Record to represent a pair of countries, ensuring consistent ordering. + */ + private record Border(Country country1, Country country2) { + Border { + // Ensure consistent ordering to avoid duplicate pairs + if (country1.compareTo(country2) > 0) { + Country temp = country1; + country1 = country2; + country2 = temp; + } + } + } + + private record BorderEdges(List lines, List tieLines, List hvdcLines) { + private BorderEdges() { + this(new ArrayList<>(), new ArrayList<>(), new ArrayList<>()); + } + + public void addBranch(Branch branch) { + if (branch instanceof Line line) { + lines.add(line); + } else if (branch instanceof TieLine tieLine) { + tieLines.add(tieLine); + } else { + throw new PowsyblException("Unexcepted branch class: " + branch.getClass()); + } + } + } +} diff --git a/network-area-diagram/src/main/java/com/powsybl/nad/build/iidm/CountryLabelProvider.java b/network-area-diagram/src/main/java/com/powsybl/nad/build/iidm/CountryLabelProvider.java new file mode 100644 index 000000000..d3d21bd97 --- /dev/null +++ b/network-area-diagram/src/main/java/com/powsybl/nad/build/iidm/CountryLabelProvider.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2025, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * SPDX-License-Identifier: MPL-2.0 + */ +package com.powsybl.nad.build.iidm; + +import com.powsybl.iidm.network.Country; +import com.powsybl.iidm.network.HvdcLine; +import com.powsybl.iidm.network.Line; +import com.powsybl.iidm.network.TieLine; +import com.powsybl.nad.model.BranchEdge; +import com.powsybl.nad.svg.EdgeInfo; + +import java.util.List; +import java.util.Optional; + +/** + * Interface for providing labels and legends for countries. + * + * @author Florian Dupuy {@literal } + */ +public interface CountryLabelProvider { + /** + * Gets the voltage level legend for a country. + */ + CountryLegend getCountryLegend(Country country); + + /** + * Gets EdgeInfo for the connection between two countries. + */ + Optional getCountryEdgeInfo(Country country1, Country country2, List lines, List tieLines, List hvdcLines, BranchEdge.Side side); + + /** + * Gets the branch label for the connection between two countries. + */ + String getBranchLabel(Country country1, Country country2, List lines, List tieLines, List hvdcLines); + + record CountryLegend(List header, List footer) { + } +} diff --git a/network-area-diagram/src/main/java/com/powsybl/nad/build/iidm/DefaultCountryLabelProvider.java b/network-area-diagram/src/main/java/com/powsybl/nad/build/iidm/DefaultCountryLabelProvider.java new file mode 100644 index 000000000..2cc951328 --- /dev/null +++ b/network-area-diagram/src/main/java/com/powsybl/nad/build/iidm/DefaultCountryLabelProvider.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2025, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * SPDX-License-Identifier: MPL-2.0 + */ + +package com.powsybl.nad.build.iidm; + +import com.powsybl.iidm.network.*; +import com.powsybl.nad.model.BranchEdge; +import com.powsybl.nad.svg.EdgeInfo; + +import java.util.List; +import java.util.Locale; +import java.util.Optional; + +/** + * @author Florian Dupuy {@literal } + */ +public class DefaultCountryLabelProvider implements CountryLabelProvider { + @Override + public CountryLegend getCountryLegend(Country country) { + return new CountryLegend(List.of(country.getName()), List.of()); + } + + @Override + public Optional getCountryEdgeInfo(Country country1, Country country2, List lines, List tieLines, List hvdcLines, BranchEdge.Side side) { + if (country1 != Country.FR && country2 != Country.FR && side == BranchEdge.Side.TWO) { + return Optional.empty(); + } + if (country2 == Country.FR && side == BranchEdge.Side.ONE || country1 == Country.FR && side == BranchEdge.Side.TWO) { + return Optional.empty(); + } + Country edgeInfoCountry = side == BranchEdge.Side.ONE ? country1 : country2; + double p12Lines = lines.stream().mapToDouble(l -> getBranchActivePower(edgeInfoCountry, l.getTerminal1(), l.getTerminal2())).sum(); + double p12TieLines = tieLines.stream().mapToDouble(tl -> getBranchActivePower(edgeInfoCountry, tl.getTerminal1(), tl.getTerminal2())).sum(); + double p12HvdcLines = hvdcLines.stream().mapToDouble(hvdcLine -> getBranchActivePower(edgeInfoCountry, hvdcLine.getConverterStation1().getTerminal(), hvdcLine.getConverterStation2().getTerminal())).sum(); + double totalActivePower = p12Lines + p12TieLines + p12HvdcLines; + return Optional.of(new EdgeInfo(EdgeInfo.ACTIVE_POWER, totalActivePower, value -> String.format(Locale.US, "%.1f MW", value))); + } + + /** + * Gets the active power from a line, handling NaN values. + */ + private double getBranchActivePower(Country country1, Terminal terminal1, Terminal terminal2) { + Country countryTerminal1 = terminal1.getVoltageLevel().getSubstation() + .flatMap(Substation::getCountry).orElse(null); + double p = (country1 == countryTerminal1 ? terminal1 : terminal2).getP(); + return Double.isNaN(p) ? 0.0 : p; + } + + @Override + public String getBranchLabel(Country country1, Country country2, List lines, List tieLines, List hvdcLines) { + return ""; + } +} diff --git a/network-area-diagram/src/main/java/com/powsybl/nad/build/iidm/DefaultCountryStyleProvider.java b/network-area-diagram/src/main/java/com/powsybl/nad/build/iidm/DefaultCountryStyleProvider.java new file mode 100644 index 000000000..455bd1232 --- /dev/null +++ b/network-area-diagram/src/main/java/com/powsybl/nad/build/iidm/DefaultCountryStyleProvider.java @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2025, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * SPDX-License-Identifier: MPL-2.0 + */ +package com.powsybl.nad.build.iidm; + +import com.powsybl.iidm.network.Network; +import com.powsybl.nad.model.*; +import com.powsybl.nad.svg.EdgeInfo; +import com.powsybl.nad.svg.StyleProvider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.net.URL; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.stream.Collectors; + +/** + * @author Florian Dupuy {@literal } + */ +public class DefaultCountryStyleProvider implements StyleProvider { + + protected static final Logger LOGGER = LoggerFactory.getLogger(DefaultCountryStyleProvider.class); + + public DefaultCountryStyleProvider(Network network) { + } + + @Override + public List getCssFilenames() { + return Collections.singletonList("countriesStyle.css"); + } + + @Override + public List getCssUrls() { + return getCssFilenames().stream() + .map(n -> getClass().getResource("/" + n)) + .collect(Collectors.toList()); + } + + @Override + public List getNodeStyleClasses(Node node) { + return List.of(); + } + + @Override + public List getHighlightNodeStyleClasses(Node node) { + return List.of(); + } + + @Override + public List getBusNodeStyleClasses(BusNode busNode) { + return List.of(); + } + + @Override + public List getBranchEdgeStyleClasses(BranchEdge branchEdge) { + return List.of(); + } + + @Override + public List getSideEdgeStyleClasses(BranchEdge edge, BranchEdge.Side side) { + return List.of(); + } + + @Override + public List getHighlightSideEdgeStyleClasses(BranchEdge edge, BranchEdge.Side side) { + return List.of(); + } + + @Override + public List getEdgeInfoStyleClasses(EdgeInfo info) { + List styles = new LinkedList<>(); + switch (info.getInfoType()) { + case EdgeInfo.ACTIVE_POWER -> styles.add(CLASSES_PREFIX + "active"); + case EdgeInfo.REACTIVE_POWER -> styles.add(CLASSES_PREFIX + "reactive"); + case EdgeInfo.CURRENT -> styles.add(CLASSES_PREFIX + "current"); + default -> LOGGER.warn("The \"{}\" type of information is not handled", info.getInfoType()); + } + return styles; + } + + @Override + public List getThreeWtEdgeStyleClasses(ThreeWtEdge threeWtedge) { + return List.of(); + } + + @Override + public List getInjectionStyleClasses(Injection injection) { + return List.of(); + } + + @Override + public List getHighlightThreeWtEdgStyleClasses(ThreeWtEdge edge) { + return List.of(); + } +} diff --git a/network-area-diagram/src/main/resources/countriesStyle.css b/network-area-diagram/src/main/resources/countriesStyle.css new file mode 100644 index 000000000..5d929299d --- /dev/null +++ b/network-area-diagram/src/main/resources/countriesStyle.css @@ -0,0 +1,10 @@ +.nad-branch-edges .nad-edge-path {stroke: darkgreen; stroke-width: 5; fill: none} +.nad-text-edges {stroke: black; stroke-width: 3; stroke-dasharray: 6,7; fill: none} +.nad-vl-nodes .nad-busnode {fill: var(--nad-vl-color, lightblue)} +.nad-active {fill: #546e7a} +.nad-reactive {fill: #0277bd} +.nad-current {fill: #bd4802} +.nad-text-nodes {font: 20px serif; fill: black; dominant-baseline: central} +foreignObject.nad-text-nodes {overflow: visible; color: black} +.nad-label-box {background-color: #6c6c6c20; width: max-content; padding: 10px; border-radius: 10px;} +.nad-edge-infos text, .nad-edge-label text {font: 25px serif; dominant-baseline:middle; stroke: #FFFFFFAA; stroke-width: 10; stroke-linejoin:round; paint-order: stroke} diff --git a/network-area-diagram/src/test/java/com/powsybl/nad/CountryDiagramTest.java b/network-area-diagram/src/test/java/com/powsybl/nad/CountryDiagramTest.java new file mode 100644 index 000000000..4a168c6d8 --- /dev/null +++ b/network-area-diagram/src/test/java/com/powsybl/nad/CountryDiagramTest.java @@ -0,0 +1,66 @@ +/** + * Copyright (c) 2023, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * SPDX-License-Identifier: MPL-2.0 + */ +package com.powsybl.nad; + +import com.google.common.jimfs.Configuration; +import com.google.common.jimfs.Jimfs; +import com.powsybl.iidm.network.Network; +import com.powsybl.iidm.network.test.EurostagTutorialExample1Factory; +import com.powsybl.nad.layout.LayoutParameters; +import com.powsybl.nad.svg.LabelProvider; +import com.powsybl.nad.svg.StyleProvider; +import com.powsybl.nad.svg.SvgParameters; +import com.powsybl.nad.svg.iidm.TopologicalStyleProvider; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.nio.file.FileSystem; +import java.nio.file.Path; + +/** + * @author Florian Dupuy {@literal } + */ +class CountryDiagramTest extends AbstractTest { + + protected FileSystem fileSystem; + + @BeforeEach + void setup() { + fileSystem = Jimfs.newFileSystem(Configuration.unix()); + setLayoutParameters(new LayoutParameters()); + setSvgParameters(new SvgParameters() + .setSvgWidthAndHeightAdded(true) + .setFixedWidth(800)); + } + + @Override + protected StyleProvider getStyleProvider(Network network) { + return null; + } + + @Override + protected LabelProvider getLabelProvider(Network network) { + return null; + } + + @Test + void testDrawSvg() { + Network network = EurostagTutorialExample1Factory.createWithFixedCurrentLimits(); + NadParameters nadParameters = new NadParameters() + .setSvgParameters(new SvgParameters() + .setArrowPathOut("M-15 0 h5 V20 h20 V0 h5 L0 -20z") + .setArrowPathIn("M-15 0 h5 V-20 h20 V0 h5 L0 20z") + .setEdgeInfoAlongEdge(false) + .setArrowShift(140) + .setArrowLabelShift(30)) + .setStyleProviderFactory(TopologicalStyleProvider::new); + Path svgFile = fileSystem.getPath("countries-test.svg"); + CountryDiagram.draw(network, svgFile, nadParameters); + assertFileEquals("/eurostag_country_diagram.svg", svgFile); + } +} diff --git a/network-area-diagram/src/test/resources/countries-test.svg b/network-area-diagram/src/test/resources/countries-test.svg new file mode 100644 index 000000000..b2c00dad9 --- /dev/null +++ b/network-area-diagram/src/test/resources/countries-test.svg @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + 1120.0 MW + + + + + + + +
+
+
BELGIUM
+
+
+
FRANCE
+
+
+
+
diff --git a/network-area-diagram/src/test/resources/eurostag_country_diagram.svg b/network-area-diagram/src/test/resources/eurostag_country_diagram.svg new file mode 100644 index 000000000..2621b32fc --- /dev/null +++ b/network-area-diagram/src/test/resources/eurostag_country_diagram.svg @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + 1120.0 MW + + + + + + + +
+
+
FRANCE
+
+
+
BELGIUM
+
+
+
+