Skip to content

Commit

Permalink
[SLD] Animate feeder arrows (#500)
Browse files Browse the repository at this point in the history
* Use new AnimatedFeederInfoStyleProvider for animation
* Add 2 thresholds in AnimatedFeederInfoStyleProvider constructor
* Update svg test reference files
* HvdcTest : fix svg generation at any time

Signed-off-by: Thomas ADAM <tadam@silicom.fr>
Signed-off-by: BenoitJeanson <benoit.jeanson@rte-france.com>
  • Loading branch information
tadam50 authored and BenoitJeanson committed May 2, 2023
1 parent 9cf2675 commit eb81860
Show file tree
Hide file tree
Showing 45 changed files with 743 additions and 281 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ protected LabelProvider getLabelProvider(Network network) {
@Test
public void testHvdcVL1Depth1() {
Network network = HvdcTestNetwork.createVsc();
debugSvg = true;
assertEquals(
toString("/hvdc-vl-depth-1.svg"),
generateSvgString(network,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,10 +128,10 @@ public List<URL> getCssUrls() {
@Override
public List<String> getCellStyles(Cell cell) {
if (cell instanceof ExternCell) {
return Collections.singletonList(EXTERN_CELL);
return List.of(EXTERN_CELL, buildStyle(cell.getDirection()));
}
if (cell instanceof InternCell) {
return List.of(INTERN_CELL, CELL_SHAPE_PREFIX + ((InternCell) cell).getShape().name().toLowerCase());
return List.of(INTERN_CELL, buildStyle(((InternCell) cell).getShape()));
}
if (cell instanceof ShuntCell) {
return Collections.singletonList(SHUNT_CELL);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
*/
package com.powsybl.sld.svg;

import com.powsybl.sld.model.cells.InternCell;
import com.powsybl.sld.model.coordinate.Direction;
import org.apache.commons.lang3.StringUtils;

import java.io.IOException;
Expand Down Expand Up @@ -41,6 +43,7 @@ public final class DiagramStyles {
public static final String INTERN_CELL = STYLE_PREFIX + "intern-cell";
public static final String SHUNT_CELL = STYLE_PREFIX + "shunt-cell";
public static final String CELL_SHAPE_PREFIX = STYLE_PREFIX + "cell-shape-";
public static final String CELL_DIRECTION_PREFIX = STYLE_PREFIX + "cell-direction-";
public static final String LEGEND = STYLE_PREFIX + "legend";
public static final String VOLTAGE = STYLE_PREFIX + "voltage";
public static final String ANGLE = STYLE_PREFIX + "angle";
Expand Down Expand Up @@ -116,4 +119,12 @@ public static String unescape(String input) {
}
return out.toString();
}

public static String buildStyle(Direction direction) {
return CELL_DIRECTION_PREFIX + direction.name().toLowerCase();
}

public static String buildStyle(InternCell.Shape shape) {
return CELL_SHAPE_PREFIX + shape.name().toLowerCase();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,26 @@ public class DirectionalFeederInfo extends AbstractFeederInfo {

private final DiagramLabelProvider.LabelDirection arrowDirection;

private final double value;

public DirectionalFeederInfo(String componentType, DiagramLabelProvider.LabelDirection arrowDirection, String leftLabel, String rightLabel) {
this(componentType, arrowDirection, leftLabel, rightLabel, null);
}

public DirectionalFeederInfo(String componentType, DiagramLabelProvider.LabelDirection arrowDirection, String leftLabel, String rightLabel, String userDefinedId) {
super(componentType, leftLabel, rightLabel, userDefinedId);
this.arrowDirection = Objects.requireNonNull(arrowDirection);
this.value = Double.NaN;
}

public DirectionalFeederInfo(String componentType, double value, DoubleFunction<String> formatter) {
this(componentType, value, formatter, null);
}

public DirectionalFeederInfo(String componentType, double value, DoubleFunction<String> formatter, String userDefinedId) {
this(componentType, getArrowDirection(value), null, formatter.apply(value), userDefinedId);
super(componentType, null, formatter.apply(value), userDefinedId);
this.arrowDirection = Objects.requireNonNull(getArrowDirection(value));
this.value = value;
}

private static DiagramLabelProvider.LabelDirection getArrowDirection(double value) {
Expand All @@ -37,4 +42,8 @@ private static DiagramLabelProvider.LabelDirection getArrowDirection(double valu
public DiagramLabelProvider.LabelDirection getDirection() {
return arrowDirection;
}

public double getValue() {
return value;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/**
* 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.util;

import com.powsybl.iidm.network.*;
import com.powsybl.sld.svg.DirectionalFeederInfo;
import com.powsybl.sld.svg.FeederInfo;

import java.util.*;

import static com.powsybl.sld.svg.DiagramStyles.STYLE_PREFIX;

/**
* @author Thomas Adam <tadam at silicom.fr>
*/
public class AnimatedFeederInfoStyleProvider extends TopologicalStyleProvider {

private static final String ARROW_ANIMATION = STYLE_PREFIX + "arrow-animation";

private static final String ARROW_SPEED = "speed";

private static final String ARROW_ANIMATION_NO_SPEED = ARROW_ANIMATION + "-no-" + ARROW_SPEED;

private static final String ARROW_ANIMATION_LOW_SPEED = ARROW_ANIMATION + "-low-" + ARROW_SPEED;

private static final String ARROW_ANIMATION_AVERAGE_SPEED = ARROW_ANIMATION + "-average-" + ARROW_SPEED;

private static final String ARROW_ANIMATION_HIGH_SPEED = ARROW_ANIMATION + "-high-" + ARROW_SPEED;

private final double threshold1;

private final double threshold2;

public AnimatedFeederInfoStyleProvider(Network network, double threshold1, double threshold2) {
super(network);
this.threshold1 = threshold1;
this.threshold2 = threshold2;
}

@Override
public List<String> getFeederInfoStyles(FeederInfo info) {
List<String> styles = new ArrayList<>(super.getFeederInfoStyles(info));
if (info instanceof DirectionalFeederInfo) {
DirectionalFeederInfo feederInfo = (DirectionalFeederInfo) info;
feederInfo.getRightLabel().ifPresent(label -> {
double value = Math.abs(feederInfo.getValue());
if (!Double.isNaN(value) && value > 0) {
if (value > threshold2) {
styles.add(ARROW_ANIMATION_HIGH_SPEED);
} else if (value > threshold1) {
styles.add(ARROW_ANIMATION_AVERAGE_SPEED);
} else {
styles.add(ARROW_ANIMATION_LOW_SPEED);
}
} else {
styles.add(ARROW_ANIMATION_NO_SPEED);
}
});
} else {
styles.add(ARROW_ANIMATION_NO_SPEED);
}
return styles;
}

@Override
public List<String> getCssFilenames() {
List<String> styles = new ArrayList<>(super.getCssFilenames());
styles.add("animations.css");
return styles;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/* ----------------------------------------------------------------------- */
/* File : animations.css ------------------------------------------------- */
/* feeder info */
.sld-cell-direction-top :not(.sld-arrow-animation-no-speed) .sld-arrow-in {
offset-rotate: 0deg;
offset-path: path('M 0,-10 0,10');
animation: move var(--sld-arrow-animation-parameters, 0s);
}
.sld-cell-direction-top :not(.sld-arrow-animation-no-speed) .sld-arrow-out {
offset-rotate: 0deg;
offset-path: path('M 0,10 0,-10');
animation: move var(--sld-arrow-animation-parameters, 0s);
}
.sld-cell-direction-bottom :not(.sld-arrow-animation-no-speed) .sld-arrow-in {
offset-rotate: 0deg;
offset-path: path('M 0,10 0,-10');
animation: move var(--sld-arrow-animation-parameters, 0s);
}
.sld-cell-direction-bottom :not(.sld-arrow-animation-no-speed) .sld-arrow-out {
offset-rotate: 0deg;
offset-path: path('M 0,-10 0,10');
animation: move var(--sld-arrow-animation-parameters, 0s);
}
@keyframes move {
to {
offset-distance: 100%;
}
}

/* Set arrows animation speeds */
.sld-arrow-animation-low-speed { --sld-arrow-animation-parameters: 3s infinite linear }
.sld-arrow-animation-average-speed { --sld-arrow-animation-parameters: 2s infinite linear }
.sld-arrow-animation-high-speed { --sld-arrow-animation-parameters: 1s infinite linear }
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,19 @@
*/
package com.powsybl.sld.iidm;

import com.powsybl.iidm.network.Country;
import com.powsybl.iidm.network.Network;
import com.powsybl.iidm.network.SwitchKind;
import com.powsybl.iidm.network.TopologyKind;
import com.powsybl.iidm.network.*;
import com.powsybl.iidm.network.extensions.ConnectablePosition;
import com.powsybl.sld.builders.NetworkGraphBuilder;
import com.powsybl.sld.model.coordinate.Direction;
import com.powsybl.sld.model.graphs.VoltageLevelGraph;
import com.powsybl.sld.model.nodes.FeederNode;
import com.powsybl.sld.model.nodes.Node;
import com.powsybl.sld.svg.*;
import com.powsybl.sld.util.AnimatedFeederInfoStyleProvider;
import org.junit.Before;
import org.junit.Test;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.*;

import static com.powsybl.sld.library.ComponentTypeName.ARROW_ACTIVE;
import static com.powsybl.sld.library.ComponentTypeName.ARROW_REACTIVE;
Expand Down Expand Up @@ -112,4 +107,71 @@ public void testFrenchFormatting() {
// write SVG and compare to reference
assertEquals(toString("/TestFormattingFeederInfos.svg"), toSVG(g, "/TestFormattingFeederInfos.svg"));
}

@Test
public void testAnimation() {
// Add load at bottom
createSwitch(vl, "d2", "d2", SwitchKind.DISCONNECTOR, false, false, false, 0, 3);
createLoad(vl, "l2", "l2", "l2", 0, ConnectablePosition.Direction.BOTTOM, 3, 10, 10);

// Add power values to the load
network.getLoad("l").getTerminal().setP(1200.29);
network.getLoad("l").getTerminal().setQ(-1.0);

network.getLoad("l2").getTerminal().setP(501.0);
network.getLoad("l2").getTerminal().setQ(0.0);

layoutParameters.setFeederInfosIntraMargin(20);

DiagramLabelProvider labelProvider = new DefaultDiagramLabelProvider(network, componentLibrary, layoutParameters) {
@Override
public List<FeederInfo> getFeederInfos(FeederNode node) {
Load l = network.getLoad("l");
Load l2 = network.getLoad("l2");

if (Objects.equals(l.getNameOrId(), node.getEquipmentId())) {
return Arrays.asList(
new DirectionalFeederInfo(ARROW_ACTIVE, l.getTerminal().getP(), valueFormatter::formatPower, null),
new DirectionalFeederInfo(ARROW_REACTIVE, l.getTerminal().getQ(), valueFormatter::formatPower, null));
} else {
return Arrays.asList(
new DirectionalFeederInfo(ARROW_ACTIVE, l2.getTerminal().getP(), valueFormatter::formatPower, null),
new DirectionalFeederInfo(ARROW_REACTIVE, l2.getTerminal().getQ(), valueFormatter::formatPower, null),
new DirectionalFeederInfo(ARROW_REACTIVE, Double.NaN, valueFormatter::formatPower, null),
new FeederInfo() {
@Override
public String getUserDefinedId() {
return null;
}

@Override
public String getComponentType() {
return ARROW_ACTIVE;
}

@Override
public Optional<String> getLeftLabel() {
return Optional.of("Left");
}

@Override
public Optional<String> getRightLabel() {
return Optional.of("Right");
}
});
}
}
};

DiagramStyleProvider styleProvider = new AnimatedFeederInfoStyleProvider(network, 500, 1000);

// build graph
VoltageLevelGraph g = graphBuilder.buildVoltageLevelGraph(vl.getId());

// Run layout
voltageLevelGraphLayout(g);

// write SVG and compare to reference
assertEquals(toString("/TestAnimatedFeederInfos.svg"), toSVG(g, "/TestAnimatedFeederInfos.svg", labelProvider, styleProvider));
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit eb81860

Please sign in to comment.