AbstractStyleProvider.java

/**
 * 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/.
 */
package com.powsybl.nad.svg;

import com.powsybl.commons.config.BaseVoltagesConfig;
import com.powsybl.nad.model.*;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author Florian Dupuy {@literal <florian.dupuy at rte-france.com>}
 */
public abstract class AbstractStyleProvider implements StyleProvider {

    protected static final Logger LOGGER = LoggerFactory.getLogger(AbstractStyleProvider.class);

    private final BaseVoltagesConfig baseVoltagesConfig;

    protected AbstractStyleProvider() {
        this(BaseVoltagesConfig.fromPlatformConfig());
    }

    protected AbstractStyleProvider(BaseVoltagesConfig baseVoltagesConfig) {
        this.baseVoltagesConfig = Objects.requireNonNull(baseVoltagesConfig);
    }

    @Override
    public String getStyleDefs() {
        StringBuilder styleSheetBuilder = new StringBuilder("\n");
        for (URL cssUrl : getCssUrls()) {
            try {
                styleSheetBuilder.append(new String(IOUtils.toByteArray(cssUrl), StandardCharsets.UTF_8));
            } catch (IOException e) {
                throw new UncheckedIOException("Can't read css file " + cssUrl.getPath(), e);
            }
        }
        return styleSheetBuilder.toString()
                .replace("\r\n", "\n") // workaround for https://bugs.openjdk.java.net/browse/JDK-8133452
                .replace("\r", "\n");
    }

    protected List<URL> getCssUrls() {
        return getCssFilenames().stream()
                .map(n -> getClass().getResource("/" + n))
                .collect(Collectors.toList());
    }

    @Override
    public List<String> getNodeStyleClasses(Node node) {
        return node instanceof BoundaryNode ? Collections.singletonList(BOUNDARY_NODE_CLASS) : Collections.emptyList();
    }

    @Override
    public List<String> getBusNodeStyleClasses(BusNode busNode) {
        return busNode == BusNode.UNKNOWN ? Collections.singletonList(UNKNOWN_BUSNODE_CLASS) : Collections.emptyList();
    }

    @Override
    public List<String> getBranchEdgeStyleClasses(BranchEdge branchEdge) {
        List<String> result = new ArrayList<>();
        if (isDisconnected(branchEdge)) {
            result.add(DISCONNECTED_CLASS);
        }
        getBranchTypeStyle(branchEdge).ifPresent(result::add);
        getBaseVoltageStyle(branchEdge).ifPresent(result::add);
        return result;
    }

    private static Optional<String> getBranchTypeStyle(Edge edge) {
        String edgeType = edge.getType();
        return switch (edgeType) {
            case BranchEdge.HVDC_LINE_EDGE -> Optional.of(HVDC_EDGE_CLASS);
            case BranchEdge.DANGLING_LINE_EDGE -> Optional.of(DANGLING_LINE_EDGE_CLASS);
            case BranchEdge.TIE_LINE_EDGE -> Optional.of(TIE_LINE_EDGE_CLASS);
            default -> Optional.empty();
        };
    }

    @Override
    public List<String> getSideEdgeStyleClasses(BranchEdge edge, BranchEdge.Side side) {
        Objects.requireNonNull(side);
        List<String> result = new ArrayList<>();
        if (isDisconnected(edge, side)) {
            result.add(DISCONNECTED_CLASS);
        }
        getBaseVoltageStyle(edge, side).ifPresent(result::add);
        return result;
    }

    @Override
    public List<String> getEdgeInfoStyleClasses(EdgeInfo info) {
        List<String> styles = new LinkedList<>();
        String infoType = info.getInfoType();
        switch (infoType) {
            case EdgeInfo.ACTIVE_POWER:
                styles.add(CLASSES_PREFIX + "active");
                break;
            case EdgeInfo.REACTIVE_POWER:
                styles.add(CLASSES_PREFIX + "reactive");
                break;
            case EdgeInfo.CURRENT:
                styles.add(CLASSES_PREFIX + "current");
                break;
            default:
                LOGGER.warn("The \"{}\" type of information is not handled", infoType);
                break;
        }

        info.getDirection().ifPresent(direction -> styles.add(
                CLASSES_PREFIX + (direction == EdgeInfo.Direction.IN ? "state-in" : "state-out")));
        return styles;
    }

    @Override
    public List<String> getThreeWtEdgeStyleClasses(ThreeWtEdge threeWtEdge) {
        List<String> result = new ArrayList<>();
        if (isDisconnected(threeWtEdge)) {
            result.add(DISCONNECTED_CLASS);
        }
        getBaseVoltageStyle(threeWtEdge).ifPresent(result::add);
        return result;
    }

    protected abstract boolean isDisconnected(ThreeWtEdge threeWtEdge);

    protected abstract boolean isDisconnected(BranchEdge branchEdge);

    protected abstract boolean isDisconnected(BranchEdge edge, BranchEdge.Side side);

    protected abstract Optional<String> getBaseVoltageStyle(Edge edge);

    protected abstract Optional<String> getBaseVoltageStyle(BranchEdge edge, BranchEdge.Side side);

    protected abstract Optional<String> getBaseVoltageStyle(ThreeWtEdge threeWtEdge);

    protected Optional<String> getBaseVoltageStyle(double nominalV) {
        return baseVoltagesConfig.getBaseVoltageName(nominalV, baseVoltagesConfig.getDefaultProfile())
                    .map(bvName -> CLASSES_PREFIX + bvName);
    }

}