TopologicalStyleProvider.java
/**
* Copyright (c) 2019, 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/.
*/
package com.powsybl.sld.svg.styles.iidm;
import com.powsybl.commons.config.BaseVoltagesConfig;
import com.powsybl.iidm.network.*;
import com.powsybl.sld.model.graphs.Graph;
import com.powsybl.sld.model.graphs.VoltageLevelGraph;
import com.powsybl.sld.model.graphs.VoltageLevelInfos;
import com.powsybl.sld.model.nodes.*;
import com.powsybl.sld.model.nodes.Node.NodeType;
import com.powsybl.sld.model.nodes.feeders.FeederTwLeg;
import com.powsybl.sld.svg.SvgParameters;
import com.powsybl.sld.svg.styles.AbstractVoltageStyleProvider;
import com.powsybl.sld.svg.styles.StyleClassConstants;
import java.util.*;
import java.util.stream.Collectors;
import static com.powsybl.sld.svg.styles.StyleClassConstants.NODE_INFOS;
import static com.powsybl.sld.svg.styles.StyleClassConstants.STYLE_PREFIX;
/**
* @author Giovanni Ferrari {@literal <giovanni.ferrari at techrain.eu>}
* @author Franck Lecuyer {@literal <franck.lecuyer@rte-france.com>}
* @author Florian Dupuy {@literal <florian.dupuy at rte-france.com>}
*/
public class TopologicalStyleProvider extends AbstractVoltageStyleProvider {
private final Map<String, Map<String, String>> vlNodeIdStyleMap = new HashMap<>();
private final Map<String, Map<String, String>> vlBusIdStyleMap = new HashMap<>();
private final Map<String, Integer> stylesIndices = new HashMap<>();
private final Network network;
private final SvgParameters svgParameters;
public TopologicalStyleProvider(Network network) {
this(BaseVoltagesConfig.fromPlatformConfig(), network, new SvgParameters());
}
public TopologicalStyleProvider(Network network, SvgParameters svgParameters) {
this(BaseVoltagesConfig.fromPlatformConfig(), network, svgParameters);
}
public TopologicalStyleProvider(BaseVoltagesConfig baseVoltagesConfig, Network network, SvgParameters svgParameters) {
super(baseVoltagesConfig);
this.network = network;
this.svgParameters = svgParameters;
}
@Override
protected List<String> getVoltageLevelEdgeStyles(Graph graph, Edge edge) {
Node node1 = edge.getNode1();
Node node2 = edge.getNode2();
if (node1.getType() == NodeType.SWITCH && ((SwitchNode) node1).isOpen()) {
return getSwitchEdgeStyles(graph, node2);
}
if (node2.getType() == NodeType.SWITCH && ((SwitchNode) node2).isOpen()) {
return getSwitchEdgeStyles(graph, node1);
}
return super.getVoltageLevelEdgeStyles(graph, edge);
}
private List<String> getSwitchEdgeStyles(Graph graph, Node node) {
return graph.getVoltageLevelInfos(node) != null ? getNodeStyles(graph.getVoltageLevelInfos(node), node) : List.of();
}
@Override
protected boolean isNodeSeparatingStyles(Node node) {
return isMultiTerminalNode(node)
// filtering out leg nodes as they are nodes with the same voltage level at each side
&& !(node instanceof FeederNode && ((FeederNode) node).getFeeder() instanceof FeederTwLeg);
}
private boolean isMultiTerminalNode(Node node) {
if (node instanceof EquipmentNode) {
Identifiable<?> identifiable = network.getIdentifiable(((EquipmentNode) node).getEquipmentId());
if (identifiable instanceof Connectable<?>) {
return ((Connectable<?>) identifiable).getTerminals().size() > 1;
}
}
return false;
}
@Override
public void reset() {
vlNodeIdStyleMap.clear();
vlBusIdStyleMap.clear();
}
private Map<String, String> createBusIdStyleMap(String baseVoltageName, String vlId) {
String baseBusStyle = STYLE_PREFIX + "bus";
List<Bus> buses = network.getVoltageLevel(vlId)
.getBusView().getBusStream().collect(Collectors.toList());
Map<String, String> busIdStyleMap = new HashMap<>();
if (svgParameters.isUnifyVoltageLevelColors()) {
for (int i = 0; i < buses.size(); i++) {
Bus bus = buses.get(i);
busIdStyleMap.put(bus.getId(), baseBusStyle + '-' + i);
}
} else {
for (Bus b : buses) {
int newIndex = stylesIndices.compute(baseVoltageName, (s, i) -> i == null ? 0 : i + 1);
String style = baseBusStyle + '-' + newIndex;
busIdStyleMap.put(b.getId(), style);
}
}
return busIdStyleMap;
}
private List<String> getNodeTopologicalStyles(String baseVoltageName, String vlId, Node node) {
Map<String, String> busIdStyleMap = vlBusIdStyleMap.computeIfAbsent(vlId, k -> createBusIdStyleMap(baseVoltageName, vlId));
Map<String, String> nodeIdStyleMap = vlNodeIdStyleMap.computeIfAbsent(vlId, k -> new HashMap<>());
String nodeTopologicalStyle = nodeIdStyleMap.get(node.getId());
List<String> styles = new ArrayList<>();
styles.add(StyleClassConstants.STYLE_PREFIX + baseVoltageName);
if (nodeTopologicalStyle == null) {
nodeTopologicalStyle = findConnectedStyle(vlId, busIdStyleMap, nodeIdStyleMap, node);
}
if (nodeTopologicalStyle != null) {
styles.add(nodeTopologicalStyle);
}
return styles;
}
private String findConnectedStyle(String vlId, Map<String, String> busIdStyleMap, Map<String, String> nodeIdStyleMap, Node node) {
Set<Node> connectedNodes = new LinkedHashSet<>();
findConnectedNodes(node, connectedNodes);
String connectedStyle = getConnectedStyle(vlId, busIdStyleMap, connectedNodes);
connectedNodes.forEach(n -> nodeIdStyleMap.put(n.getId(), connectedStyle));
return connectedStyle;
}
private String getConnectedStyle(String vlId, Map<String, String> busIdStyleMap, Set<Node> connectedNodes) {
return connectedNodes.stream()
.filter(EquipmentNode.class::isInstance)
.map(EquipmentNode.class::cast)
.map(en -> network.getIdentifiable(en.getEquipmentId()))
.filter(identifiable -> identifiable instanceof Connectable<?>)
.map(i -> (Connectable<?>) i)
.map(c -> {
List<Terminal> terminals = c.getTerminals().stream()
.filter(t -> t.getVoltageLevel().getId().equals(vlId))
.collect(Collectors.toList());
if (terminals.size() == 1) {
// if more than one (vl-internal transformer), we don't know which side to take
Bus bus = terminals.get(0).getBusView().getBus();
return bus != null ? bus.getId() : null;
}
return null;
})
.filter(Objects::nonNull)
.map(busIdStyleMap::get)
.findFirst()
.orElse(StyleClassConstants.DISCONNECTED_STYLE_CLASS);
}
private void findConnectedNodes(Node node, Set<Node> visitedNodes) {
if (visitedNodes.contains(node)) {
return;
}
if (node.getType() == NodeType.SWITCH && ((SwitchNode) node).isOpen()) {
return;
}
if (isMultiTerminalInternalNode(node)) {
visitedNodes.add(node);
return;
}
visitedNodes.add(node);
for (Node adjNode : node.getAdjacentNodes()) {
findConnectedNodes(adjNode, visitedNodes);
}
}
private boolean isMultiTerminalInternalNode(Node node) {
return isMultiTerminalNode(node) && node.getAdjacentEdges().size() > 1;
}
@Override
public List<String> getNodeStyles(VoltageLevelInfos voltageLevelInfos, Node node) {
if (node.getType() == NodeType.SWITCH && ((SwitchNode) node).isOpen()) {
return List.of(StyleClassConstants.DISCONNECTED_STYLE_CLASS);
}
return Optional.ofNullable(voltageLevelInfos)
.flatMap(vli -> baseVoltagesConfig.getBaseVoltageName(vli.getNominalVoltage(), BASE_VOLTAGE_PROFILE))
.map(baseVoltageName -> getNodeTopologicalStyles(baseVoltageName, voltageLevelInfos.getId(), node))
.orElse(List.of(StyleClassConstants.DISCONNECTED_STYLE_CLASS));
}
@Override
public List<String> getNodeStyles(VoltageLevelInfos vlInfo, Node node, NodeSide side) {
return getNodeStyles(vlInfo, node.getAdjacentNodes().get(side.getIntValue() - 1));
}
@Override
public List<String> getBusStyles(String busId, VoltageLevelGraph graph) {
String busStyle = vlBusIdStyleMap.getOrDefault(graph.getVoltageLevelInfos().getId(), Collections.emptyMap())
.getOrDefault(busId, null);
return busStyle != null ? List.of(busStyle, NODE_INFOS) : List.of(NODE_INFOS);
}
@Override
public List<String> getCssFilenames() {
return Arrays.asList("tautologies.css", "topologicalBaseVoltages.css");
}
}