Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Represent subnetworks in network-area diagrams #669

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
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
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2021, RTE (http://www.rte-france.com)
* Copyright (c) 2021-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/.
Expand Down Expand Up @@ -45,20 +45,27 @@ public interface StyleProvider {
String STRETCHABLE_CLASS = CLASSES_PREFIX + "stretchable";
String GLUED_CLASS = CLASSES_PREFIX + "glued";
String GLUED_CENTER_CLASS = CLASSES_PREFIX + "glued-center";
String HIGHLIGHT_CLASS = CLASSES_PREFIX + "highlight";

List<String> getCssFilenames();

String getStyleDefs();

List<String> getNodeStyleClasses(Node node);

List<String> getHighlightNodeStyleClasses(Node node);

List<String> getNodeStyleClasses(BusNode busNode);

List<String> getEdgeStyleClasses(Edge edge);

List<String> getSideEdgeStyleClasses(BranchEdge edge, BranchEdge.Side side);

List<String> getHighlightSideEdgeStyleClasses(BranchEdge edge, BranchEdge.Side side);

List<String> getEdgeInfoStyles(EdgeInfo info);

List<String> getThreeWtNodeStyle(ThreeWtNode threeWtNode, ThreeWtEdge.Side one);

List<String> getHighlightThreeWtEdgStyleClasses(ThreeWtEdge edge);
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2021, RTE (http://www.rte-france.com)
* Copyright (c) 2021-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/.
Expand Down Expand Up @@ -55,7 +55,7 @@ public class SvgParameters {
private EdgeInfoEnum edgeInfoDisplayed = EdgeInfoEnum.ACTIVE_POWER;
private double pstArrowHeadSize = 8;
private String undefinedValueSymbol = "";

private boolean highlightSubnetworks;

public enum CssLocation {
INSERTED_IN_SVG, EXTERNAL_IMPORTED, EXTERNAL_NO_IMPORT
Expand Down Expand Up @@ -491,4 +491,13 @@ public SvgParameters setUndefinedValueSymbol(String undefinedValueSymbol) {
this.undefinedValueSymbol = undefinedValueSymbol;
return this;
}

public boolean isHighlightSubnetworks() {
return highlightSubnetworks;
}

public SvgParameters setHighlightSubnetworks(boolean highlightSubnetworks) {
this.highlightSubnetworks = highlightSubnetworks;
return this;
}
}
124 changes: 108 additions & 16 deletions network-area-diagram/src/main/java/com/powsybl/nad/svg/SvgWriter.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2021, RTE (http://www.rte-france.com)
* Copyright (c) 2021-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/.
Expand Down Expand Up @@ -44,6 +44,7 @@ public class SvgWriter {
private static final String TABLE_ELEMENT_NAME = "table";
private static final String TABLE_ROW_ELEMENT_NAME = "tr";
private static final String TABLE_DATA_ELEMENT_NAME = "td";
private static final String USE_ELEMENT_NAME = "use";
private static final String ID_ATTRIBUTE = "id";
private static final String WIDTH_ATTRIBUTE = "width";
private static final String HEIGHT_ATTRIBUTE = "height";
Expand All @@ -57,6 +58,7 @@ public class SvgWriter {
private static final String Y_ATTRIBUTE = "y";
private static final String DY_ATTRIBUTE = "dy";
private static final String POINTS_ATTRIBUTE = "points";
private static final String HREF_ATTRIBUTE = "href";

private final SvgParameters svgParameters;
private final StyleProvider styleProvider;
Expand Down Expand Up @@ -103,18 +105,32 @@ private void writeSvg(Graph graph, OutputStream svgOs) {
XMLStreamWriter writer = XmlUtil.initializeWriter(true, INDENT, svgOs);
addSvgRoot(graph, writer);
addStyle(writer);
boolean higlightSubnetworks = this.svgParameters.isHighlightSubnetworks();
if (higlightSubnetworks) {
drawHighlightedSection(graph, writer);
}
drawVoltageLevelNodes(graph, writer);
drawBranchEdges(graph, writer);
drawThreeWtEdges(graph, writer);
drawThreeWtNodes(graph, writer);
drawTextEdges(graph, writer);
drawTextNodes(graph, writer);

writer.writeEndDocument();
} catch (XMLStreamException e) {
throw new UncheckedXmlStreamException(e);
}
}

private void drawHighlightedSection(Graph graph, XMLStreamWriter writer) throws XMLStreamException {
writer.writeStartElement(GROUP_ELEMENT_NAME);
writer.writeAttribute(CLASS_ATTRIBUTE, StyleProvider.HIGHLIGHT_CLASS);
drawHighlighVoltageLevelNodes(graph, writer);
drawHighlightBranchEdges(graph, writer);
drawHighlightThreeWtEdges(graph, writer);
writer.writeEndElement();
}

private void drawBranchEdges(Graph graph, XMLStreamWriter writer) throws XMLStreamException {
writer.writeStartElement(GROUP_ELEMENT_NAME);
writer.writeAttribute(CLASS_ATTRIBUTE, StyleProvider.BRANCH_EDGES_CLASS);
Expand All @@ -123,12 +139,22 @@ private void drawBranchEdges(Graph graph, XMLStreamWriter writer) throws XMLStre
writeId(writer, edge);
writeStyleClasses(writer, styleProvider.getEdgeStyleClasses(edge));
insertName(writer, edge::getName);

drawHalfEdge(graph, writer, edge, BranchEdge.Side.ONE);
drawHalfEdge(graph, writer, edge, BranchEdge.Side.TWO);

drawEdgeCenter(writer, edge);
writer.writeEndElement();
}
writer.writeEndElement();
}

private void drawHighlightBranchEdges(Graph graph, XMLStreamWriter writer) throws XMLStreamException {
writer.writeStartElement(GROUP_ELEMENT_NAME);
writer.writeAttribute(CLASS_ATTRIBUTE, StyleProvider.BRANCH_EDGES_CLASS);
for (BranchEdge edge : graph.getBranchEdges()) {
writer.writeStartElement(GROUP_ELEMENT_NAME);
writeStyleClasses(writer, styleProvider.getEdgeStyleClasses(edge));
drawHighlightHalfEdge(graph, writer, edge, BranchEdge.Side.ONE);
drawHighlightHalfEdge(graph, writer, edge, BranchEdge.Side.TWO);
writer.writeEndElement();
}
writer.writeEndElement();
Expand Down Expand Up @@ -243,6 +269,20 @@ private void drawThreeWtEdges(Graph graph, XMLStreamWriter writer) throws XMLStr
writer.writeEndElement();
}

private void drawHighlightThreeWtEdges(Graph graph, XMLStreamWriter writer) throws XMLStreamException {
List<ThreeWtEdge> threeWtEdges = graph.getThreeWtEdges();
if (threeWtEdges.isEmpty()) {
return;
}

writer.writeStartElement(GROUP_ELEMENT_NAME);
writer.writeAttribute(CLASS_ATTRIBUTE, StyleProvider.THREE_WT_EDGES_CLASS);
for (ThreeWtEdge edge : threeWtEdges) {
drawHighlightThreeWtEdge(writer, edge);
}
writer.writeEndElement();
}

private void drawHalfEdge(Graph graph, XMLStreamWriter writer, BranchEdge edge, BranchEdge.Side side) throws XMLStreamException {
// the half edge is only drawn if visible, but if the edge is a TwoWtEdge, the transformer is still drawn
if (!edge.isVisible(side) && !(edge.isTransformerEdge())) {
Expand All @@ -254,24 +294,50 @@ private void drawHalfEdge(Graph graph, XMLStreamWriter writer, BranchEdge edge,
if (edge.isVisible(side)) {
Optional<EdgeInfo> edgeInfo = labelProvider.getEdgeInfo(graph, edge, side);
if (!graph.isLoop(edge)) {
writer.writeEmptyElement(POLYLINE_ELEMENT_NAME);
writeStyleClasses(writer, StyleProvider.EDGE_PATH_CLASS, StyleProvider.STRETCHABLE_CLASS, StyleProvider.GLUED_CLASS + "-" + side.getNum());
writer.writeAttribute(POINTS_ATTRIBUTE, getPolylinePointsString(edge, side));
if (edgeInfo.isPresent()) {
drawBranchEdgeInfo(graph, writer, edge, side, edgeInfo.get());
}
drawHalfEdge(graph, writer, edge, side, edgeInfo);
} else {
writer.writeEmptyElement(PATH_ELEMENT_NAME);
writer.writeAttribute(CLASS_ATTRIBUTE, StyleProvider.EDGE_PATH_CLASS);
writer.writeAttribute(PATH_D_ATTRIBUTE, getLoopPathString(edge, side));
if (edgeInfo.isPresent()) {
drawLoopEdgeInfo(writer, edge, side, edgeInfo.get());
}
drawLoopEdge(writer, edge, side, edgeInfo);
}
}
writer.writeEndElement();
}

private void drawHalfEdge(Graph graph, XMLStreamWriter writer, BranchEdge edge, BranchEdge.Side side, Optional<EdgeInfo> edgeInfo) throws XMLStreamException {
writer.writeEmptyElement(POLYLINE_ELEMENT_NAME);
writeStyleClasses(writer, StyleProvider.EDGE_PATH_CLASS, StyleProvider.STRETCHABLE_CLASS, StyleProvider.GLUED_CLASS + "-" + side.getNum());
writer.writeAttribute(POINTS_ATTRIBUTE, getPolylinePointsString(edge, side));
if (edgeInfo.isPresent()) {
drawBranchEdgeInfo(graph, writer, edge, side, edgeInfo.get());
}
}

private void drawHighlightHalfEdge(Graph graph, XMLStreamWriter writer, BranchEdge edge, BranchEdge.Side side) throws XMLStreamException {
if (!edge.isVisible(side) && !(edge.isTransformerEdge())) {
return;
}
writer.writeStartElement(GROUP_ELEMENT_NAME);
writeStyleClasses(writer, styleProvider.getSideEdgeStyleClasses(edge, side));
if (edge.isVisible(side) && !graph.isLoop(edge)) {
drawHighlighHalfEdge(writer, edge, side);
}
writer.writeEndElement();
}

private void drawHighlighHalfEdge(XMLStreamWriter writer, BranchEdge edge, BranchEdge.Side side) throws XMLStreamException {
writer.writeEmptyElement(POLYLINE_ELEMENT_NAME);
writeStyleClasses(writer, styleProvider.getHighlightSideEdgeStyleClasses(edge, side), StyleProvider.STRETCHABLE_CLASS, StyleProvider.GLUED_CLASS + "-" + side.getNum());
writer.writeAttribute(POINTS_ATTRIBUTE, getPolylinePointsString(edge, side));
}

private void drawLoopEdge(XMLStreamWriter writer, BranchEdge edge, BranchEdge.Side side, Optional<EdgeInfo> edgeInfo) throws XMLStreamException {
writer.writeEmptyElement(PATH_ELEMENT_NAME);
writer.writeAttribute(CLASS_ATTRIBUTE, StyleProvider.EDGE_PATH_CLASS);
writer.writeAttribute(PATH_D_ATTRIBUTE, getLoopPathString(edge, side));
if (edgeInfo.isPresent()) {
drawLoopEdgeInfo(writer, edge, side, edgeInfo.get());
}
}

private String getPolylinePointsString(BranchEdge edge, BranchEdge.Side side) {
return getPolylinePointsString(edge.getPoints(side));
}
Expand Down Expand Up @@ -299,7 +365,6 @@ private void drawThreeWtEdge(Graph graph, XMLStreamWriter writer, ThreeWtEdge ed
writeId(writer, edge);
writeStyleClasses(writer, styleProvider.getEdgeStyleClasses(edge));
insertName(writer, edge::getName);

writer.writeEmptyElement(POLYLINE_ELEMENT_NAME);
writeStyleClasses(writer, StyleProvider.EDGE_PATH_CLASS, StyleProvider.STRETCHABLE_CLASS);
writer.writeAttribute(POINTS_ATTRIBUTE, getPolylinePointsString(edge));
Expand All @@ -308,7 +373,18 @@ private void drawThreeWtEdge(Graph graph, XMLStreamWriter writer, ThreeWtEdge ed
if (edgeInfo.isPresent()) {
drawThreeWtEdgeInfo(graph, writer, edge, edgeInfo.get());
}
writer.writeEndElement();
}

private void drawHighlightThreeWtEdge(XMLStreamWriter writer, ThreeWtEdge edge) throws XMLStreamException {
if (!edge.isVisible()) {
return;
}
writer.writeStartElement(GROUP_ELEMENT_NAME);
writeStyleClasses(writer, styleProvider.getEdgeStyleClasses(edge));
writer.writeEmptyElement(POLYLINE_ELEMENT_NAME);
writeStyleClasses(writer, styleProvider.getHighlightThreeWtEdgStyleClasses(edge), StyleProvider.STRETCHABLE_CLASS);
writer.writeAttribute(POINTS_ATTRIBUTE, getPolylinePointsString(edge));
writer.writeEndElement();
}

Expand Down Expand Up @@ -525,6 +601,22 @@ private void drawVoltageLevelNodes(Graph graph, XMLStreamWriter writer) throws X
writer.writeEndElement();
}

private void drawHighlighVoltageLevelNodes(Graph graph, XMLStreamWriter writer) throws XMLStreamException {
writer.writeStartElement(GROUP_ELEMENT_NAME);
writer.writeAttribute(CLASS_ATTRIBUTE, StyleProvider.VOLTAGE_LEVEL_NODES_CLASS);
for (VoltageLevelNode vlNode : graph.getVoltageLevelNodesStream().filter(VoltageLevelNode::isVisible).collect(Collectors.toList())) {
drawHighlightedNode(writer, vlNode);
}
writer.writeEndElement();
}

private void drawHighlightedNode(XMLStreamWriter writer, VoltageLevelNode vlNode) throws XMLStreamException {
writer.writeStartElement(USE_ELEMENT_NAME);
writer.writeAttribute(HREF_ATTRIBUTE, "#" + getPrefixedId(vlNode.getDiagramId()));
writeStyleClasses(writer, styleProvider.getHighlightNodeStyleClasses(vlNode));
writer.writeEndElement();
}

private void drawTextNodes(Graph graph, XMLStreamWriter writer) throws XMLStreamException {
writer.writeStartElement(GROUP_ELEMENT_NAME);
writer.writeAttribute(CLASS_ATTRIBUTE, StyleProvider.TEXT_NODES_CLASS);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2022, RTE (http://www.rte-france.com)
* Copyright (c) 2022-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/.
Expand All @@ -11,7 +11,11 @@
import com.powsybl.iidm.network.Bus;
import com.powsybl.iidm.network.Network;
import com.powsybl.iidm.network.Terminal;
import com.powsybl.iidm.network.ThreeSides;
import com.powsybl.iidm.network.TwoSides;
import com.powsybl.iidm.network.VoltageLevel;
import com.powsybl.nad.model.*;
import com.powsybl.nad.model.BranchEdge.Side;
import com.powsybl.nad.svg.AbstractStyleProvider;
import com.powsybl.nad.svg.StyleProvider;
import com.powsybl.nad.utils.iidm.IidmUtils;
Expand All @@ -24,14 +28,18 @@
public abstract class AbstractVoltageStyleProvider extends AbstractStyleProvider {

protected final Network network;
private final HashMap<String, String> subnetworksHighlightMap = new HashMap<>();
private final HashMap<String, String> subnetworkEquipmentMap = new HashMap<>();

protected AbstractVoltageStyleProvider(Network network) {
this.network = network;
buildSubnetworkMaps();
}

protected AbstractVoltageStyleProvider(Network network, BaseVoltagesConfig baseVoltageStyle) {
super(baseVoltageStyle);
this.network = network;
buildSubnetworkMaps();
}

@Override
Expand Down Expand Up @@ -117,4 +125,73 @@ protected Optional<String> getBaseVoltageStyle(BranchEdge edge, BranchEdge.Side
}

protected abstract Optional<String> getBaseVoltageStyle(Terminal terminal);

@Override
public List<String> getHighlightNodeStyleClasses(Node node) {
String subnetworkId = subnetworkEquipmentMap.get(node.getEquipmentId());
return List.of(subnetworksHighlightMap.get(subnetworkId));
}

@Override
public List<String> getHighlightSideEdgeStyleClasses(BranchEdge edge, BranchEdge.Side side) {
String subnetworkId = getSubnetworkId(edge, side);
return List.of(subnetworksHighlightMap.get(subnetworkId));
}

@Override
public List<String> getHighlightThreeWtEdgStyleClasses(ThreeWtEdge edge) {
String subnetworkId = getSubnetworkId(edge.getEquipmentId(), edge.getSide());
return List.of(subnetworksHighlightMap.get(subnetworkId));
}

private String getSubnetworkId(BranchEdge edge, Side side) {
String subnetworId = null;
switch (edge.getType()) {
case BranchEdge.LINE_EDGE:
TwoSides lineSide = side.equals(Side.ONE) ? TwoSides.ONE : TwoSides.TWO;
subnetworId = network.getLine(edge.getEquipmentId()).getTerminal(lineSide).getVoltageLevel().getParentNetwork().getId();
break;
case BranchEdge.TWO_WT_EDGE, BranchEdge.PST_EDGE:
TwoSides twSide = side.equals(Side.ONE) ? TwoSides.ONE : TwoSides.TWO;
subnetworId = network.getTwoWindingsTransformer(edge.getEquipmentId()).getTerminal(twSide).getVoltageLevel().getParentNetwork().getId();
break;
case BranchEdge.DANGLING_LINE_EDGE:
subnetworId = network.getDanglingLine(edge.getEquipmentId()).getTerminal().getVoltageLevel().getParentNetwork().getId();
break;
case BranchEdge.TIE_LINE_EDGE:
subnetworId = network.getTieLine(edge.getEquipmentId()).getTerminal(side.equals(Side.ONE) ? TwoSides.ONE : TwoSides.TWO).getVoltageLevel().getParentNetwork().getId();
break;
case BranchEdge.HVDC_LINE_EDGE:
subnetworId = network.getHvdcLine(edge.getEquipmentId()).getConverterStation(side.equals(Side.ONE) ? TwoSides.ONE : TwoSides.TWO).getTerminal().getVoltageLevel().getParentNetwork().getId();
break;
default:
break;
}
return subnetworId;
}

private String getSubnetworkId(String id, ThreeWtEdge.Side side) {
return network.getThreeWindingsTransformer(id).getLeg(ThreeSides.valueOf(side.name())).getTerminal().getVoltageLevel().getParentNetwork().getId();
}

private void buildSubnetworkMaps() {
List<VoltageLevel> voltageLevels = getVoltageLevels();
voltageLevels.forEach(vl -> addNode(vl.getId(), vl.getParentNetwork().getId()));
network.getDanglingLineStream().forEach(dl -> addNode(dl.getId(), dl.getTerminal().getVoltageLevel().getParentNetwork().getId()));
}

private void addNode(String id, String subnetworkId) {
subnetworkEquipmentMap.put(id, subnetworkId);
subnetworksHighlightMap.computeIfAbsent(subnetworkId, k -> getHighlightClass(subnetworksHighlightMap.size()));
}

private String getHighlightClass(int index) {
return StyleProvider.HIGHLIGHT_CLASS + "-" + index % 5;
}

private List<VoltageLevel> getVoltageLevels() {
return network.getVoltageLevelStream()
.sorted(Comparator.comparing(VoltageLevel::getId))
.toList();
}
}
Loading