HighlightLineStateStyleProvider.java

/**
 * 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.sld.svg.styles.iidm;

import com.powsybl.iidm.network.*;
import com.powsybl.sld.library.ComponentLibrary;
import com.powsybl.sld.library.ComponentTypeName;
import com.powsybl.sld.model.graphs.Graph;
import com.powsybl.sld.model.graphs.VoltageLevelGraph;
import com.powsybl.sld.model.nodes.*;
import com.powsybl.sld.model.nodes.feeders.FeederWithSides;
import com.powsybl.sld.svg.styles.EmptyStyleProvider;
import com.powsybl.sld.svg.styles.StyleClassConstants;

import java.util.*;

/**
 * @author Franck Lecuyer {@literal <franck.lecuyer at rte-france.com>}
 * @author Florian Dupuy {@literal <florian.dupuy at rte-france.com>}
 */
public class HighlightLineStateStyleProvider extends EmptyStyleProvider {

    private final Network network;

    public HighlightLineStateStyleProvider(Network network) {
        this.network = network;
    }

    @Override
    public List<String> getEdgeStyles(Graph graph, Edge edge) {
        return getHighlightLineStateStyle(graph, edge)
                .map(List::of)
                .orElse(Collections.emptyList());
    }

    @Override
    public List<String> getNodeStyles(VoltageLevelGraph graph, Node node, ComponentLibrary componentLibrary, boolean showInternalNodes) {
        if (node instanceof BusNode busNode && !isBusOrBbsConnected(busNode)) {
            return List.of(StyleClassConstants.BUS_DISCONNECTED);
        }
        return Collections.emptyList();
    }

    private boolean isBusOrBbsConnected(BusNode busNode) {
        if (busNode.isFictitious()) {
            return true; // always displayed like a connected bbs
        }
        String equipmentId = busNode.getEquipmentId();
        BusbarSection busbarSection = network.getBusbarSection(equipmentId);
        if (busbarSection != null) {
            return busbarSection.getTerminal().isConnected();
        } else {
            Bus bus = network.getBusBreakerView().getBus(equipmentId);
            if (bus != null) {
                return bus.getVoltageLevel().getBusView().getMergedBus(bus.getId()) != null;
            }
            return true; // should not happen
        }
    }

    private Optional<String> getHighlightLineStateStyle(Graph graph, Edge edge) {
        Node n1 = edge.getNode1();
        Node n2 = edge.getNode2();

        FeederNode n;

        if (n1 instanceof FeederNode || n2 instanceof FeederNode) {
            n = (FeederNode) (n1 instanceof FeederNode ? n1 : n2);
            return getHighlightFeederStateStyle(graph, n);
        } else if (n1 instanceof Internal2WTNode || n2 instanceof Internal2WTNode) {
            return getHighlightFeederStateStyleForInternal2WT(n1, n2);
        } else {
            return Optional.empty();
        }
    }

    protected Optional<String> getHighlightFeederStateStyle(Graph graph, FeederNode n) {
        if (n.getFeeder().getFeederType() == FeederType.INJECTION) {
            Injection<?> injection = (Injection<?>) network.getIdentifiable(n.getEquipmentId());
            return injection.getTerminal().isConnected() ? Optional.empty() : Optional.of(StyleClassConstants.FEEDER_DISCONNECTED_CONNECTED);
        } else if (n.getFeeder() instanceof FeederWithSides) {
            FeederWithSides feederWs = (FeederWithSides) n.getFeeder();
            Map<NodeSide, Boolean> connectionStatus = connectionStatus(n);
            NodeSide side = null;
            NodeSide otherSide = null;
            if (feederWs.getFeederType() == FeederType.BRANCH || feederWs.getFeederType() == FeederType.TWO_WINDINGS_TRANSFORMER_LEG) {
                side = feederWs.getSide();
                otherSide = getOtherSide(side);
                if (ComponentTypeName.LINE.equals(n.getComponentType())) {
                    side = Boolean.TRUE.equals(connectionStatus.get(side)) ? side : otherSide;
                    otherSide = getOtherSide(side);
                }
            } else if (feederWs.getFeederType() == FeederType.THREE_WINDINGS_TRANSFORMER_LEG) {
                String idVl = graph.getVoltageLevelInfos(n).getId();
                ThreeWindingsTransformer transformer = network.getThreeWindingsTransformer(n.getEquipmentId());
                if (transformer != null) {
                    side = getTransformerSide(idVl, transformer);
                }
                otherSide = feederWs.getSide();
            }
            return getFeederStateStyle(side, otherSide, connectionStatus);
        } else {
            return Optional.empty();
        }
    }

    protected Optional<String> getHighlightFeederStateStyleForInternal2WT(Node n1, Node n2) {
        Internal2WTNode n2WT = (Internal2WTNode) (n1 instanceof Internal2WTNode ? n1 : n2);
        TwoWindingsTransformer twt = (TwoWindingsTransformer) network.getIdentifiable(n2WT.getEquipmentId());
        boolean connected1 = twt.getTerminal1().isConnected();
        boolean connected2 = twt.getTerminal2().isConnected();
        if (!connected1 && !connected2) {
            return Optional.of(StyleClassConstants.FEEDER_DISCONNECTED);
        } else if (connected1 != connected2) {
            // TODO we cannot with SLD model as it is, detect which side is connected: to improve later.
            return Optional.of(StyleClassConstants.FEEDER_DISCONNECTED_CONNECTED);
        }
        return Optional.empty();
    }

    protected Map<NodeSide, Boolean> connectionStatus(FeederNode node) {
        Map<NodeSide, Boolean> res = new EnumMap<>(NodeSide.class);
        if (node.getFeeder().getFeederType() == FeederType.BRANCH || node.getFeeder().getFeederType() == FeederType.TWO_WINDINGS_TRANSFORMER_LEG) {
            Branch<?> branch = network.getBranch(node.getEquipmentId());
            if (branch != null) {
                res.put(NodeSide.ONE, branch.getTerminal(TwoSides.ONE).isConnected());
                res.put(NodeSide.TWO, branch.getTerminal(TwoSides.TWO).isConnected());
            }
        } else if (node.getFeeder().getFeederType() == FeederType.THREE_WINDINGS_TRANSFORMER_LEG) {
            ThreeWindingsTransformer transformer = network.getThreeWindingsTransformer(node.getEquipmentId());
            if (transformer != null) {
                res.put(NodeSide.ONE, transformer.getTerminal(ThreeSides.ONE).isConnected());
                res.put(NodeSide.TWO, transformer.getTerminal(ThreeSides.TWO).isConnected());
                res.put(NodeSide.THREE, transformer.getTerminal(ThreeSides.THREE).isConnected());
            }
        }
        return res;
    }

    private static NodeSide getOtherSide(NodeSide side) {
        return side == NodeSide.ONE ? NodeSide.TWO : NodeSide.ONE;
    }

    private static NodeSide getTransformerSide(String idVl, ThreeWindingsTransformer transformer) {
        if (transformer.getTerminal(ThreeSides.ONE).getVoltageLevel().getId().equals(idVl)) {
            return NodeSide.ONE;
        } else if (transformer.getTerminal(ThreeSides.TWO).getVoltageLevel().getId().equals(idVl)) {
            return NodeSide.TWO;
        } else {
            return NodeSide.THREE;
        }
    }

    private static Optional<String> getFeederStateStyle(NodeSide side, NodeSide otherSide, Map<NodeSide, Boolean> connectionStatus) {
        if (side != null && otherSide != null) {
            if (Boolean.FALSE.equals(connectionStatus.get(side)) && Boolean.FALSE.equals(connectionStatus.get(otherSide))) {  // disconnected on both ends
                return Optional.of(StyleClassConstants.FEEDER_DISCONNECTED);
            } else if (Boolean.TRUE.equals(connectionStatus.get(side)) && Boolean.FALSE.equals(connectionStatus.get(otherSide))) {  // connected on side and disconnected on other side
                return Optional.of(StyleClassConstants.FEEDER_CONNECTED_DISCONNECTED);
            } else if (Boolean.FALSE.equals(connectionStatus.get(side)) && Boolean.TRUE.equals(connectionStatus.get(otherSide))) {  // disconnected on side and connected on other side
                return Optional.of(StyleClassConstants.FEEDER_DISCONNECTED_CONNECTED);
            }
        }
        return Optional.empty();
    }

    @Override
    public List<String> getCssFilenames() {
        return List.of("highlightLineStates.css");
    }
}