From a84b0667854089644bb46929217acc5d054e7f00 Mon Sep 17 00:00:00 2001 From: Sophie Frasnedo Date: Tue, 5 Mar 2024 16:16:49 +0100 Subject: [PATCH 01/14] First attempt at using SubstationPosition extension Signed-off-by: Sophie Frasnedo --- network-area-diagram/pom.xml | 4 + .../nad/layout/GeographicalLayoutFactory.java | 85 +++++++++++++++++++ .../LayoutWithGeographicalPositionsTest.java | 49 +++++++++++ 3 files changed, 138 insertions(+) create mode 100644 network-area-diagram/src/main/java/com/powsybl/nad/layout/GeographicalLayoutFactory.java create mode 100644 network-area-diagram/src/test/java/com/powsybl/nad/layout/LayoutWithGeographicalPositionsTest.java diff --git a/network-area-diagram/pom.xml b/network-area-diagram/pom.xml index 21f6eaeb0..41aef5f09 100644 --- a/network-area-diagram/pom.xml +++ b/network-area-diagram/pom.xml @@ -38,6 +38,10 @@ com.powsybl powsybl-iidm-api + + com.powsybl + powsybl-iidm-extensions + com.powsybl powsybl-commons diff --git a/network-area-diagram/src/main/java/com/powsybl/nad/layout/GeographicalLayoutFactory.java b/network-area-diagram/src/main/java/com/powsybl/nad/layout/GeographicalLayoutFactory.java new file mode 100644 index 000000000..049e834b8 --- /dev/null +++ b/network-area-diagram/src/main/java/com/powsybl/nad/layout/GeographicalLayoutFactory.java @@ -0,0 +1,85 @@ +/** + * Copyright (c) 2024, 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.nad.layout; + +import com.powsybl.iidm.network.Network; +import com.powsybl.iidm.network.VoltageLevel; +import com.powsybl.iidm.network.extensions.Coordinate; +import com.powsybl.iidm.network.extensions.SubstationPosition; +import com.powsybl.nad.model.Point; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +/** + * @author Sophie Frasnedo {@literal } + */ +public class GeographicalLayoutFactory implements LayoutFactory { + + private Network network; + + private int scalingFactor = 100; + private double repulsionFactor = 50; + + public GeographicalLayoutFactory(Network network) { + Objects.requireNonNull(network); + this.network = network; + } + + public GeographicalLayoutFactory(Network network, int scalingFactor, double repulsionFactor) { + Objects.requireNonNull(network); + Objects.requireNonNull(scalingFactor); + Objects.requireNonNull(repulsionFactor); + this.network = network; + this.scalingFactor = scalingFactor; + this.repulsionFactor = repulsionFactor; + } + + @Override + public Layout create() { + AbstractLayout layout = new BasicForceLayout(); + layout.setFixedNodePositions(getFixedNodePosition()); + return layout; + } + + private Map getFixedNodePosition() { + Map fixedNodePositionMap = new HashMap<>(); + + network.getSubstationStream().forEach( + substation -> { + SubstationPosition substationPosition = substation.getExtension(SubstationPosition.class); + if (substationPosition != null) { + Coordinate coordinate = substationPosition.getCoordinate(); + double latitude = coordinate.getLatitude(); + double longitude = coordinate.getLongitude(); + + List voltageLevelList = substation.getVoltageLevelStream().toList(); + int voltageLevelListSize = voltageLevelList.size(); + + if (voltageLevelListSize == 1) { + String voltageLevelId = voltageLevelList.get(0).getId(); + fixedNodePositionMap.put(voltageLevelId, new Point(longitude * scalingFactor, latitude * scalingFactor)); + } else if (voltageLevelListSize > 1) { + //Deal with voltage levels within the same substation (and thus with the same coordinates) + double angle = 2 * Math.PI / voltageLevelListSize; + int i = 0; + for (VoltageLevel voltageLevel : voltageLevelList) { + double angleVoltageLevel = angle * i; + fixedNodePositionMap.put(voltageLevel.getId(), new Point(longitude * scalingFactor + repulsionFactor * Math.cos(angleVoltageLevel), latitude * scalingFactor + repulsionFactor * Math.cos(angleVoltageLevel))); + i++; + } + } + } + } + ); + + return fixedNodePositionMap; + } +} diff --git a/network-area-diagram/src/test/java/com/powsybl/nad/layout/LayoutWithGeographicalPositionsTest.java b/network-area-diagram/src/test/java/com/powsybl/nad/layout/LayoutWithGeographicalPositionsTest.java new file mode 100644 index 000000000..08e9606fc --- /dev/null +++ b/network-area-diagram/src/test/java/com/powsybl/nad/layout/LayoutWithGeographicalPositionsTest.java @@ -0,0 +1,49 @@ +/** + * Copyright (c) 2024, 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.nad.layout; + +import com.powsybl.ieeecdf.converter.IeeeCdfNetworkFactory; +import com.powsybl.iidm.network.Network; +import com.powsybl.iidm.network.extensions.Coordinate; +import com.powsybl.iidm.network.impl.extensions.SubstationPositionAdderImplProvider; +import com.powsybl.nad.build.iidm.NetworkGraphBuilder; +import com.powsybl.nad.build.iidm.VoltageLevelFilter; +import com.powsybl.nad.model.Graph; +import com.powsybl.nad.model.Point; +import org.junit.jupiter.api.Test; + +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author Sophie Frasnedo {@literal } + */ +class LayoutWithGeographicalPositionsTest { + + @Test + void layoutWithGeographicalPositionsTest() { + Network network = IeeeCdfNetworkFactory.create9(); + new SubstationPositionAdderImplProvider().newAdder(network.getSubstation("S1")).withCoordinate(new Coordinate(2d, 3d)).add(); + new SubstationPositionAdderImplProvider().newAdder(network.getSubstation("S2")).withCoordinate(new Coordinate(4d, 5d)).add(); + new SubstationPositionAdderImplProvider().newAdder(network.getSubstation("S3")).withCoordinate(new Coordinate(6d, 7d)).add(); + new SubstationPositionAdderImplProvider().newAdder(network.getSubstation("S5")).withCoordinate(new Coordinate(8d, 9d)).add(); + new SubstationPositionAdderImplProvider().newAdder(network.getSubstation("S6")).withCoordinate(new Coordinate(10d, 11d)).add(); + + Graph graph = new NetworkGraphBuilder(network, VoltageLevelFilter.NO_FILTER).buildGraph(); + Layout forceLayout = new GeographicalLayoutFactory(network).create(); + forceLayout.run(graph, new LayoutParameters()); + Map actual = graph.getNodePositions(); + + assertEquals(300, actual.get("VL1").getX()); + assertEquals(200, actual.get("VL1").getY()); + assertEquals(500, actual.get("VL2").getX()); + assertEquals(400, actual.get("VL2").getY()); + } +} + From e48ab344fe05772428eddfeaf8257cabcd0b98c7 Mon Sep 17 00:00:00 2001 From: Sophie Frasnedo Date: Tue, 26 Mar 2024 21:49:59 +0100 Subject: [PATCH 02/14] Fix wrong formula Signed-off-by: Sophie Frasnedo --- .../java/com/powsybl/nad/layout/GeographicalLayoutFactory.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/network-area-diagram/src/main/java/com/powsybl/nad/layout/GeographicalLayoutFactory.java b/network-area-diagram/src/main/java/com/powsybl/nad/layout/GeographicalLayoutFactory.java index 049e834b8..25bb6205f 100644 --- a/network-area-diagram/src/main/java/com/powsybl/nad/layout/GeographicalLayoutFactory.java +++ b/network-area-diagram/src/main/java/com/powsybl/nad/layout/GeographicalLayoutFactory.java @@ -72,7 +72,7 @@ private Map getFixedNodePosition() { int i = 0; for (VoltageLevel voltageLevel : voltageLevelList) { double angleVoltageLevel = angle * i; - fixedNodePositionMap.put(voltageLevel.getId(), new Point(longitude * scalingFactor + repulsionFactor * Math.cos(angleVoltageLevel), latitude * scalingFactor + repulsionFactor * Math.cos(angleVoltageLevel))); + fixedNodePositionMap.put(voltageLevel.getId(), new Point(longitude * scalingFactor + repulsionFactor * Math.cos(angleVoltageLevel), latitude * scalingFactor + repulsionFactor * Math.sin(angleVoltageLevel))); i++; } } From 1e68a51c52f768e32ce5d13e39f4c49ab915b96a Mon Sep 17 00:00:00 2001 From: Sophie Frasnedo Date: Tue, 26 Mar 2024 21:50:42 +0100 Subject: [PATCH 03/14] Add tests Signed-off-by: Sophie Frasnedo --- diagram-test/pom.xml | 10 +++++ .../com/powsybl/diagram/test/Networks.java | 12 +++++ .../LayoutWithGeographicalPositionsTest.java | 45 +++++++++++++++---- 3 files changed, 58 insertions(+), 9 deletions(-) diff --git a/diagram-test/pom.xml b/diagram-test/pom.xml index ee82ea54b..e0cbcdd83 100644 --- a/diagram-test/pom.xml +++ b/diagram-test/pom.xml @@ -49,5 +49,15 @@ com.powsybl powsybl-iidm-extensions + + com.powsybl + powsybl-iidm-impl + compile + + + com.powsybl + powsybl-ieee-cdf-converter + compile + diff --git a/diagram-test/src/main/java/com/powsybl/diagram/test/Networks.java b/diagram-test/src/main/java/com/powsybl/diagram/test/Networks.java index dd1312a64..1fbbb711d 100644 --- a/diagram-test/src/main/java/com/powsybl/diagram/test/Networks.java +++ b/diagram-test/src/main/java/com/powsybl/diagram/test/Networks.java @@ -7,8 +7,10 @@ package com.powsybl.diagram.test; import com.powsybl.commons.extensions.Extendable; +import com.powsybl.ieeecdf.converter.IeeeCdfNetworkFactory; import com.powsybl.iidm.network.*; import com.powsybl.iidm.network.extensions.*; +import com.powsybl.iidm.network.impl.extensions.SubstationPositionAdderImplProvider; import java.time.ZonedDateTime; import java.util.Optional; @@ -1706,6 +1708,16 @@ public static Network createNetworkWithBatteries() { return network; } + public static Network createIeee9NetworkWithOneMissingSubstationPosition() { + Network network = IeeeCdfNetworkFactory.create9(); + new SubstationPositionAdderImplProvider().newAdder(network.getSubstation("S1")).withCoordinate(new Coordinate(2d, 3d)).add(); + new SubstationPositionAdderImplProvider().newAdder(network.getSubstation("S2")).withCoordinate(new Coordinate(4d, 5d)).add(); + new SubstationPositionAdderImplProvider().newAdder(network.getSubstation("S3")).withCoordinate(new Coordinate(6d, 7d)).add(); + new SubstationPositionAdderImplProvider().newAdder(network.getSubstation("S5")).withCoordinate(new Coordinate(8d, 9d)).add(); + new SubstationPositionAdderImplProvider().newAdder(network.getSubstation("S6")).withCoordinate(new Coordinate(10d, 11d)).add(); + return network; + } + public static Substation createSubstation(Network n, String id, String name, Country country) { return n.newSubstation() .setId(id) diff --git a/network-area-diagram/src/test/java/com/powsybl/nad/layout/LayoutWithGeographicalPositionsTest.java b/network-area-diagram/src/test/java/com/powsybl/nad/layout/LayoutWithGeographicalPositionsTest.java index 08e9606fc..e7bdff812 100644 --- a/network-area-diagram/src/test/java/com/powsybl/nad/layout/LayoutWithGeographicalPositionsTest.java +++ b/network-area-diagram/src/test/java/com/powsybl/nad/layout/LayoutWithGeographicalPositionsTest.java @@ -7,10 +7,9 @@ */ package com.powsybl.nad.layout; -import com.powsybl.ieeecdf.converter.IeeeCdfNetworkFactory; +import com.powsybl.diagram.test.Networks; import com.powsybl.iidm.network.Network; -import com.powsybl.iidm.network.extensions.Coordinate; -import com.powsybl.iidm.network.impl.extensions.SubstationPositionAdderImplProvider; +import com.powsybl.iidm.network.TopologyKind; import com.powsybl.nad.build.iidm.NetworkGraphBuilder; import com.powsybl.nad.build.iidm.VoltageLevelFilter; import com.powsybl.nad.model.Graph; @@ -28,12 +27,7 @@ class LayoutWithGeographicalPositionsTest { @Test void layoutWithGeographicalPositionsTest() { - Network network = IeeeCdfNetworkFactory.create9(); - new SubstationPositionAdderImplProvider().newAdder(network.getSubstation("S1")).withCoordinate(new Coordinate(2d, 3d)).add(); - new SubstationPositionAdderImplProvider().newAdder(network.getSubstation("S2")).withCoordinate(new Coordinate(4d, 5d)).add(); - new SubstationPositionAdderImplProvider().newAdder(network.getSubstation("S3")).withCoordinate(new Coordinate(6d, 7d)).add(); - new SubstationPositionAdderImplProvider().newAdder(network.getSubstation("S5")).withCoordinate(new Coordinate(8d, 9d)).add(); - new SubstationPositionAdderImplProvider().newAdder(network.getSubstation("S6")).withCoordinate(new Coordinate(10d, 11d)).add(); + Network network = Networks.createIeee9NetworkWithOneMissingSubstationPosition(); Graph graph = new NetworkGraphBuilder(network, VoltageLevelFilter.NO_FILTER).buildGraph(); Layout forceLayout = new GeographicalLayoutFactory(network).create(); @@ -45,5 +39,38 @@ void layoutWithGeographicalPositionsTest() { assertEquals(500, actual.get("VL2").getX()); assertEquals(400, actual.get("VL2").getY()); } + + @Test + void layoutWithGeographicalPositionsCustomisedParametersTest() { + Network network = Networks.createIeee9NetworkWithOneMissingSubstationPosition(); + + Graph graph = new NetworkGraphBuilder(network, VoltageLevelFilter.NO_FILTER).buildGraph(); + Layout forceLayout = new GeographicalLayoutFactory(network, 200, 50d).create(); + forceLayout.run(graph, new LayoutParameters()); + Map actual = graph.getNodePositions(); + + assertEquals(600, actual.get("VL1").getX()); + assertEquals(400, actual.get("VL1").getY()); + assertEquals(1000, actual.get("VL2").getX()); + assertEquals(800, actual.get("VL2").getY()); + } + + @Test + void layoutWithGeographicalPositionsTwoVoltageLevelsInSameSubstationTest() { + Network network = Networks.createIeee9NetworkWithOneMissingSubstationPosition(); + network.getSubstation("S1").newVoltageLevel().setNominalV(400d).setTopologyKind(TopologyKind.BUS_BREAKER).setId("VL1_1").add(); + network.getSubstation("S1").newVoltageLevel().setNominalV(400d).setTopologyKind(TopologyKind.BUS_BREAKER).setId("VL1_2").add(); + Graph graph = new NetworkGraphBuilder(network, VoltageLevelFilter.NO_FILTER).buildGraph(); + Layout forceLayout = new GeographicalLayoutFactory(network, 100, 50d).create(); + forceLayout.run(graph, new LayoutParameters()); + Map actual = graph.getNodePositions(); + + assertEquals(350, actual.get("VL1").getX()); + assertEquals(200, actual.get("VL1").getY()); + assertEquals(275, actual.get("VL1_1").getX()); + assertEquals(243.3d, actual.get("VL1_1").getY(), 0.1); + assertEquals(275, actual.get("VL1_2").getX()); + assertEquals(156.7, actual.get("VL1_2").getY(), 0.1); + } } From 4d812b3347d17c497af037f420e388ce16b103b1 Mon Sep 17 00:00:00 2001 From: Sophie Frasnedo Date: Tue, 2 Apr 2024 23:00:53 +0200 Subject: [PATCH 04/14] Fix checkstyle Signed-off-by: Sophie Frasnedo --- .../src/main/java/com/powsybl/diagram/test/Networks.java | 1 - 1 file changed, 1 deletion(-) diff --git a/diagram-test/src/main/java/com/powsybl/diagram/test/Networks.java b/diagram-test/src/main/java/com/powsybl/diagram/test/Networks.java index 2dd99fddc..da0280d25 100644 --- a/diagram-test/src/main/java/com/powsybl/diagram/test/Networks.java +++ b/diagram-test/src/main/java/com/powsybl/diagram/test/Networks.java @@ -1708,7 +1708,6 @@ public static Network createNetworkWithBatteries() { return network; } - public static Network createIeee9NetworkWithOneMissingSubstationPosition() { Network network = IeeeCdfNetworkFactory.create9(); new SubstationPositionAdderImplProvider().newAdder(network.getSubstation("S1")).withCoordinate(new Coordinate(2d, 3d)).add(); From a50cd45084c960cdf0e2233a3de4c3af0cdcf5c1 Mon Sep 17 00:00:00 2001 From: Sophie Frasnedo Date: Wed, 3 Apr 2024 15:52:11 +0200 Subject: [PATCH 05/14] Redesign GeographicalLayoutFactory after review Signed-off-by: Sophie Frasnedo --- .../nad/layout/GeographicalLayoutFactory.java | 81 ++++++++----------- .../LayoutWithGeographicalPositionsTest.java | 4 +- 2 files changed, 34 insertions(+), 51 deletions(-) diff --git a/network-area-diagram/src/main/java/com/powsybl/nad/layout/GeographicalLayoutFactory.java b/network-area-diagram/src/main/java/com/powsybl/nad/layout/GeographicalLayoutFactory.java index 25bb6205f..88a350eaf 100644 --- a/network-area-diagram/src/main/java/com/powsybl/nad/layout/GeographicalLayoutFactory.java +++ b/network-area-diagram/src/main/java/com/powsybl/nad/layout/GeographicalLayoutFactory.java @@ -8,6 +8,7 @@ package com.powsybl.nad.layout; import com.powsybl.iidm.network.Network; +import com.powsybl.iidm.network.Substation; import com.powsybl.iidm.network.VoltageLevel; import com.powsybl.iidm.network.extensions.Coordinate; import com.powsybl.iidm.network.extensions.SubstationPosition; @@ -16,70 +17,52 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Objects; /** * @author Sophie Frasnedo {@literal } */ -public class GeographicalLayoutFactory implements LayoutFactory { +public class GeographicalLayoutFactory extends FixedLayoutFactory implements LayoutFactory { - private Network network; - - private int scalingFactor = 100; - private double repulsionFactor = 50; + private static final int SCALING_FACTOR = 100; + private static final double RADIUS_FACTOR = 50; public GeographicalLayoutFactory(Network network) { - Objects.requireNonNull(network); - this.network = network; - } - - public GeographicalLayoutFactory(Network network, int scalingFactor, double repulsionFactor) { - Objects.requireNonNull(network); - Objects.requireNonNull(scalingFactor); - Objects.requireNonNull(repulsionFactor); - this.network = network; - this.scalingFactor = scalingFactor; - this.repulsionFactor = repulsionFactor; + this(network, SCALING_FACTOR, RADIUS_FACTOR, BasicForceLayout::new); } - @Override - public Layout create() { - AbstractLayout layout = new BasicForceLayout(); - layout.setFixedNodePositions(getFixedNodePosition()); - return layout; + public GeographicalLayoutFactory(Network network, int scalingFactor, double radiusFactor, LayoutFactory layoutFactory) { + super(getFixedNodePosition(network, scalingFactor, radiusFactor), layoutFactory); } - private Map getFixedNodePosition() { + private static Map getFixedNodePosition(Network network, int scalingFactor, double repulsionFactor) { Map fixedNodePositionMap = new HashMap<>(); + network.getSubstationStream().forEach(substation -> fillPositionMap(substation, fixedNodePositionMap, scalingFactor, repulsionFactor)); + return fixedNodePositionMap; + } - network.getSubstationStream().forEach( - substation -> { - SubstationPosition substationPosition = substation.getExtension(SubstationPosition.class); - if (substationPosition != null) { - Coordinate coordinate = substationPosition.getCoordinate(); - double latitude = coordinate.getLatitude(); - double longitude = coordinate.getLongitude(); + private static void fillPositionMap(Substation substation, Map fixedNodePositionMap, int scalingFactor, double repulsionFactor) { + SubstationPosition substationPosition = substation.getExtension(SubstationPosition.class); + if (substationPosition != null) { + Coordinate coordinate = substationPosition.getCoordinate(); + double latitude = coordinate.getLatitude(); + double longitude = coordinate.getLongitude(); - List voltageLevelList = substation.getVoltageLevelStream().toList(); - int voltageLevelListSize = voltageLevelList.size(); + List voltageLevelList = substation.getVoltageLevelStream().toList(); + int voltageLevelListSize = voltageLevelList.size(); - if (voltageLevelListSize == 1) { - String voltageLevelId = voltageLevelList.get(0).getId(); - fixedNodePositionMap.put(voltageLevelId, new Point(longitude * scalingFactor, latitude * scalingFactor)); - } else if (voltageLevelListSize > 1) { - //Deal with voltage levels within the same substation (and thus with the same coordinates) - double angle = 2 * Math.PI / voltageLevelListSize; - int i = 0; - for (VoltageLevel voltageLevel : voltageLevelList) { - double angleVoltageLevel = angle * i; - fixedNodePositionMap.put(voltageLevel.getId(), new Point(longitude * scalingFactor + repulsionFactor * Math.cos(angleVoltageLevel), latitude * scalingFactor + repulsionFactor * Math.sin(angleVoltageLevel))); - i++; - } - } - } + if (voltageLevelListSize == 1) { + String voltageLevelId = voltageLevelList.get(0).getId(); + fixedNodePositionMap.put(voltageLevelId, new Point(longitude * scalingFactor, latitude * scalingFactor)); + } else if (voltageLevelListSize > 1) { + //Deal with voltage levels within the same substation (and thus with the same coordinates) + double angle = 2 * Math.PI / voltageLevelListSize; + int i = 0; + for (VoltageLevel voltageLevel : voltageLevelList) { + double angleVoltageLevel = angle * i; + fixedNodePositionMap.put(voltageLevel.getId(), new Point(longitude * scalingFactor + repulsionFactor * Math.cos(angleVoltageLevel), latitude * scalingFactor + repulsionFactor * Math.sin(angleVoltageLevel))); + i++; } - ); - - return fixedNodePositionMap; + } + } } } diff --git a/network-area-diagram/src/test/java/com/powsybl/nad/layout/LayoutWithGeographicalPositionsTest.java b/network-area-diagram/src/test/java/com/powsybl/nad/layout/LayoutWithGeographicalPositionsTest.java index e7bdff812..1a9b84647 100644 --- a/network-area-diagram/src/test/java/com/powsybl/nad/layout/LayoutWithGeographicalPositionsTest.java +++ b/network-area-diagram/src/test/java/com/powsybl/nad/layout/LayoutWithGeographicalPositionsTest.java @@ -45,7 +45,7 @@ void layoutWithGeographicalPositionsCustomisedParametersTest() { Network network = Networks.createIeee9NetworkWithOneMissingSubstationPosition(); Graph graph = new NetworkGraphBuilder(network, VoltageLevelFilter.NO_FILTER).buildGraph(); - Layout forceLayout = new GeographicalLayoutFactory(network, 200, 50d).create(); + Layout forceLayout = new GeographicalLayoutFactory(network, 200, 50d, BasicForceLayout::new).create(); forceLayout.run(graph, new LayoutParameters()); Map actual = graph.getNodePositions(); @@ -61,7 +61,7 @@ void layoutWithGeographicalPositionsTwoVoltageLevelsInSameSubstationTest() { network.getSubstation("S1").newVoltageLevel().setNominalV(400d).setTopologyKind(TopologyKind.BUS_BREAKER).setId("VL1_1").add(); network.getSubstation("S1").newVoltageLevel().setNominalV(400d).setTopologyKind(TopologyKind.BUS_BREAKER).setId("VL1_2").add(); Graph graph = new NetworkGraphBuilder(network, VoltageLevelFilter.NO_FILTER).buildGraph(); - Layout forceLayout = new GeographicalLayoutFactory(network, 100, 50d).create(); + Layout forceLayout = new GeographicalLayoutFactory(network, 100, 50d, BasicForceLayout::new).create(); forceLayout.run(graph, new LayoutParameters()); Map actual = graph.getNodePositions(); From de74fe3a8be8fe6eff5d1332fb9b362a9fe0070d Mon Sep 17 00:00:00 2001 From: Sophie Frasnedo Date: Wed, 3 Apr 2024 16:03:36 +0200 Subject: [PATCH 06/14] Replace last occurences of repulsionFactor with radiusFactor Signed-off-by: Sophie Frasnedo --- .../com/powsybl/nad/layout/GeographicalLayoutFactory.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/network-area-diagram/src/main/java/com/powsybl/nad/layout/GeographicalLayoutFactory.java b/network-area-diagram/src/main/java/com/powsybl/nad/layout/GeographicalLayoutFactory.java index 88a350eaf..870c34e3d 100644 --- a/network-area-diagram/src/main/java/com/powsybl/nad/layout/GeographicalLayoutFactory.java +++ b/network-area-diagram/src/main/java/com/powsybl/nad/layout/GeographicalLayoutFactory.java @@ -34,13 +34,13 @@ public GeographicalLayoutFactory(Network network, int scalingFactor, double radi super(getFixedNodePosition(network, scalingFactor, radiusFactor), layoutFactory); } - private static Map getFixedNodePosition(Network network, int scalingFactor, double repulsionFactor) { + private static Map getFixedNodePosition(Network network, int scalingFactor, double radiusFactor) { Map fixedNodePositionMap = new HashMap<>(); - network.getSubstationStream().forEach(substation -> fillPositionMap(substation, fixedNodePositionMap, scalingFactor, repulsionFactor)); + network.getSubstationStream().forEach(substation -> fillPositionMap(substation, fixedNodePositionMap, scalingFactor, radiusFactor)); return fixedNodePositionMap; } - private static void fillPositionMap(Substation substation, Map fixedNodePositionMap, int scalingFactor, double repulsionFactor) { + private static void fillPositionMap(Substation substation, Map fixedNodePositionMap, int scalingFactor, double radiusFactor) { SubstationPosition substationPosition = substation.getExtension(SubstationPosition.class); if (substationPosition != null) { Coordinate coordinate = substationPosition.getCoordinate(); @@ -59,7 +59,7 @@ private static void fillPositionMap(Substation substation, Map fi int i = 0; for (VoltageLevel voltageLevel : voltageLevelList) { double angleVoltageLevel = angle * i; - fixedNodePositionMap.put(voltageLevel.getId(), new Point(longitude * scalingFactor + repulsionFactor * Math.cos(angleVoltageLevel), latitude * scalingFactor + repulsionFactor * Math.sin(angleVoltageLevel))); + fixedNodePositionMap.put(voltageLevel.getId(), new Point(longitude * scalingFactor + radiusFactor * Math.cos(angleVoltageLevel), latitude * scalingFactor + radiusFactor * Math.sin(angleVoltageLevel))); i++; } } From 9c0fab066075e49534807e861d578a3f5d06272b Mon Sep 17 00:00:00 2001 From: Sophie Frasnedo Date: Thu, 4 Apr 2024 13:17:36 +0200 Subject: [PATCH 07/14] First attempt at using Mercator projection Signed-off-by: Sophie Frasnedo --- .../nad/layout/GeographicalLayoutFactory.java | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/network-area-diagram/src/main/java/com/powsybl/nad/layout/GeographicalLayoutFactory.java b/network-area-diagram/src/main/java/com/powsybl/nad/layout/GeographicalLayoutFactory.java index 870c34e3d..4824a8840 100644 --- a/network-area-diagram/src/main/java/com/powsybl/nad/layout/GeographicalLayoutFactory.java +++ b/network-area-diagram/src/main/java/com/powsybl/nad/layout/GeographicalLayoutFactory.java @@ -13,6 +13,7 @@ import com.powsybl.iidm.network.extensions.Coordinate; import com.powsybl.iidm.network.extensions.SubstationPosition; import com.powsybl.nad.model.Point; +import org.jgrapht.alg.util.Pair; import java.util.HashMap; import java.util.List; @@ -47,22 +48,31 @@ private static void fillPositionMap(Substation substation, Map fi double latitude = coordinate.getLatitude(); double longitude = coordinate.getLongitude(); + Pair mercatorCoordinates = useMercatorLikeProjection(longitude, latitude); + List voltageLevelList = substation.getVoltageLevelStream().toList(); int voltageLevelListSize = voltageLevelList.size(); if (voltageLevelListSize == 1) { String voltageLevelId = voltageLevelList.get(0).getId(); - fixedNodePositionMap.put(voltageLevelId, new Point(longitude * scalingFactor, latitude * scalingFactor)); + fixedNodePositionMap.put(voltageLevelId, new Point(scalingFactor * mercatorCoordinates.getFirst(), scalingFactor * mercatorCoordinates.getSecond())); } else if (voltageLevelListSize > 1) { //Deal with voltage levels within the same substation (and thus with the same coordinates) double angle = 2 * Math.PI / voltageLevelListSize; int i = 0; for (VoltageLevel voltageLevel : voltageLevelList) { double angleVoltageLevel = angle * i; - fixedNodePositionMap.put(voltageLevel.getId(), new Point(longitude * scalingFactor + radiusFactor * Math.cos(angleVoltageLevel), latitude * scalingFactor + radiusFactor * Math.sin(angleVoltageLevel))); + fixedNodePositionMap.put(voltageLevel.getId(), new Point(scalingFactor * mercatorCoordinates.getFirst() + radiusFactor * Math.cos(angleVoltageLevel), scalingFactor * mercatorCoordinates.getSecond() + radiusFactor * Math.sin(angleVoltageLevel))); i++; } } } } + + private static Pair useMercatorLikeProjection(double longitude, double latitude) { + double x = longitude * Math.PI / 180; + double y = Math.log(Math.tan(Math.PI / 4 + latitude * Math.PI / 180 / 2)); + return new Pair(x, y); + } + } From f7b9fa4f12f1d722ffd91ead1fa9d1f955d9e261 Mon Sep 17 00:00:00 2001 From: Florian Dupuy Date: Thu, 4 Apr 2024 15:16:07 +0200 Subject: [PATCH 08/14] Force layout centred on the initialPoints center of gravity Signed-off-by: Florian Dupuy --- .../diagram/util/forcelayout/ForceLayout.java | 26 ++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/diagram-util/src/main/java/com/powsybl/diagram/util/forcelayout/ForceLayout.java b/diagram-util/src/main/java/com/powsybl/diagram/util/forcelayout/ForceLayout.java index c212110e2..359358e1f 100644 --- a/diagram-util/src/main/java/com/powsybl/diagram/util/forcelayout/ForceLayout.java +++ b/diagram-util/src/main/java/com/powsybl/diagram/util/forcelayout/ForceLayout.java @@ -82,6 +82,7 @@ public class ForceLayout { private final Set springs = new LinkedHashSet<>(); private boolean hasBeenExecuted = false; + private Vector center = new Vector(0, 0); public ForceLayout(Graph graph) { this.maxSteps = DEFAULT_MAX_STEPS; @@ -148,10 +149,22 @@ public ForceLayout setFixedNodes(Set fixedNodes) { private void initializePoints() { int nbUnknownPositions = graph.vertexSet().size() - initialPoints.size(); - // instead of generating initial points in the [0,1] interval apply a scale depending on the number of unknown positions + // Initialize the missing positions by use the default random number generator. + // Apply a scale depending on the number of unknown positions to have an expected mean distance remain around the same value. + // The positions are around the center of given initial positions. double scale = Math.sqrt(nbUnknownPositions) * 5; + Optional initialPointsCenter = initialPoints.values().stream() + .map(Point::getPosition) + .reduce(Vector::add) + .map(sum -> sum.divide(initialPoints.size())); + setCenter(initialPointsCenter.orElse(new Vector(0, 0))); + for (V vertex : graph.vertexSet()) { - points.put(vertex, initialPoints.getOrDefault(vertex, new Point(scale * random.nextDouble(), scale * random.nextDouble()))); + Point point = new Point( + center.getX() + scale * (random.nextDouble() - 0.5), + center.getY() + scale * (random.nextDouble() - 0.5) + ); + points.put(vertex, initialPoints.getOrDefault(vertex, point)); } } @@ -268,7 +281,7 @@ private void applyHookesLaw() { private void attractToCenter() { for (Point point : points.values()) { - Vector direction = point.getPosition().multiply(-1); + Vector direction = point.getPosition().multiply(-1).add(center); point.applyForce(direction.multiply(repulsion / 200.0)); } @@ -355,4 +368,11 @@ public void toSVG(Function tooltip, Writer writer) { printWriter.close(); } + public void setCenter(Vector center) { + this.center = center; + } + + public Vector getCenter() { + return center; + } } From 116e795ca776517ac4cfe99539d35098f2ead78c Mon Sep 17 00:00:00 2001 From: Sophie Frasnedo Date: Thu, 4 Apr 2024 16:24:22 +0200 Subject: [PATCH 09/14] Add a minus sign for projected y coordinate (y-axis oriented downwards) Signed-off-by: Sophie Frasnedo --- .../java/com/powsybl/nad/layout/GeographicalLayoutFactory.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/network-area-diagram/src/main/java/com/powsybl/nad/layout/GeographicalLayoutFactory.java b/network-area-diagram/src/main/java/com/powsybl/nad/layout/GeographicalLayoutFactory.java index 4824a8840..263f730d2 100644 --- a/network-area-diagram/src/main/java/com/powsybl/nad/layout/GeographicalLayoutFactory.java +++ b/network-area-diagram/src/main/java/com/powsybl/nad/layout/GeographicalLayoutFactory.java @@ -71,7 +71,7 @@ private static void fillPositionMap(Substation substation, Map fi private static Pair useMercatorLikeProjection(double longitude, double latitude) { double x = longitude * Math.PI / 180; - double y = Math.log(Math.tan(Math.PI / 4 + latitude * Math.PI / 180 / 2)); + double y = -Math.log(Math.tan(Math.PI / 4 + latitude * Math.PI / 180 / 2)); return new Pair(x, y); } From affc17cb8045be3176b28d4fb6285512832ef2b6 Mon Sep 17 00:00:00 2001 From: Sophie Frasnedo Date: Thu, 4 Apr 2024 16:27:05 +0200 Subject: [PATCH 10/14] Change default values for SCALING_FACTOR and RADIUS_FACTOR Signed-off-by: Sophie Frasnedo --- .../com/powsybl/nad/layout/GeographicalLayoutFactory.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/network-area-diagram/src/main/java/com/powsybl/nad/layout/GeographicalLayoutFactory.java b/network-area-diagram/src/main/java/com/powsybl/nad/layout/GeographicalLayoutFactory.java index 263f730d2..2f91ab363 100644 --- a/network-area-diagram/src/main/java/com/powsybl/nad/layout/GeographicalLayoutFactory.java +++ b/network-area-diagram/src/main/java/com/powsybl/nad/layout/GeographicalLayoutFactory.java @@ -24,8 +24,8 @@ */ public class GeographicalLayoutFactory extends FixedLayoutFactory implements LayoutFactory { - private static final int SCALING_FACTOR = 100; - private static final double RADIUS_FACTOR = 50; + private static final int SCALING_FACTOR = 150000; + private static final double RADIUS_FACTOR = 150; public GeographicalLayoutFactory(Network network) { this(network, SCALING_FACTOR, RADIUS_FACTOR, BasicForceLayout::new); From 7801761f55f202f408e7def828b49f43662fb9ab Mon Sep 17 00:00:00 2001 From: Sophie Frasnedo Date: Thu, 4 Apr 2024 16:45:08 +0200 Subject: [PATCH 11/14] Remove implementation specific code Signed-off-by: Sophie Frasnedo --- diagram-test/pom.xml | 5 ----- .../main/java/com/powsybl/diagram/test/Networks.java | 11 +++++------ 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/diagram-test/pom.xml b/diagram-test/pom.xml index 90621d57b..c6800e9e8 100644 --- a/diagram-test/pom.xml +++ b/diagram-test/pom.xml @@ -49,11 +49,6 @@ com.powsybl powsybl-iidm-extensions - - com.powsybl - powsybl-iidm-impl - compile - com.powsybl powsybl-ieee-cdf-converter diff --git a/diagram-test/src/main/java/com/powsybl/diagram/test/Networks.java b/diagram-test/src/main/java/com/powsybl/diagram/test/Networks.java index 522809edb..e33ba30ec 100644 --- a/diagram-test/src/main/java/com/powsybl/diagram/test/Networks.java +++ b/diagram-test/src/main/java/com/powsybl/diagram/test/Networks.java @@ -10,7 +10,6 @@ import com.powsybl.ieeecdf.converter.IeeeCdfNetworkFactory; import com.powsybl.iidm.network.*; import com.powsybl.iidm.network.extensions.*; -import com.powsybl.iidm.network.impl.extensions.SubstationPositionAdderImplProvider; import java.time.ZonedDateTime; import java.util.Optional; @@ -1710,11 +1709,11 @@ public static Network createNetworkWithBatteries() { public static Network createIeee9NetworkWithOneMissingSubstationPosition() { Network network = IeeeCdfNetworkFactory.create9(); - new SubstationPositionAdderImplProvider().newAdder(network.getSubstation("S1")).withCoordinate(new Coordinate(2d, 3d)).add(); - new SubstationPositionAdderImplProvider().newAdder(network.getSubstation("S2")).withCoordinate(new Coordinate(4d, 5d)).add(); - new SubstationPositionAdderImplProvider().newAdder(network.getSubstation("S3")).withCoordinate(new Coordinate(6d, 7d)).add(); - new SubstationPositionAdderImplProvider().newAdder(network.getSubstation("S5")).withCoordinate(new Coordinate(8d, 9d)).add(); - new SubstationPositionAdderImplProvider().newAdder(network.getSubstation("S6")).withCoordinate(new Coordinate(10d, 11d)).add(); + network.getSubstation("S1").newExtension(SubstationPositionAdder.class).withCoordinate(new Coordinate(2d, 3d)).add(); + network.getSubstation("S2").newExtension(SubstationPositionAdder.class).withCoordinate(new Coordinate(4d, 5d)).add(); + network.getSubstation("S3").newExtension(SubstationPositionAdder.class).withCoordinate(new Coordinate(6d, 7d)).add(); + network.getSubstation("S5").newExtension(SubstationPositionAdder.class).withCoordinate(new Coordinate(8d, 9d)).add(); + network.getSubstation("S6").newExtension(SubstationPositionAdder.class).withCoordinate(new Coordinate(10d, 11d)).add(); return network; } From f8a6439e64d3a6aabb43c9b0e7283243edc7e3bc Mon Sep 17 00:00:00 2001 From: Sophie Frasnedo Date: Thu, 4 Apr 2024 22:15:15 +0200 Subject: [PATCH 12/14] Update test references Signed-off-by: Sophie Frasnedo --- .../LayoutWithGeographicalPositionsTest.java | 28 +- .../src/test/resources/3wt.svg | 60 +- .../src/test/resources/3wt_disconnected.svg | 60 +- .../3wt_disconnected_topological.svg | 60 +- .../src/test/resources/3wt_partial.svg | 32 +- .../src/test/resources/IEEE_118_bus.svg | 2470 ++++++++--------- .../test/resources/IEEE_118_bus_partial.svg | 518 ++-- .../IEEE_118_bus_partial_non_connected.svg | 276 +- .../src/test/resources/IEEE_14_bus.svg | 428 +-- .../resources/IEEE_14_bus_disconnection.svg | 428 +-- .../test/resources/IEEE_14_bus_fictitious.svg | 446 +-- .../test/resources/IEEE_14_bus_text_nodes.svg | 286 +- .../resources/IEEE_14_bus_voltage_filter1.svg | 196 +- .../resources/IEEE_14_bus_voltage_filter2.svg | 240 +- .../resources/IEEE_14_bus_voltage_filter3.svg | 204 +- .../resources/IEEE_14_bus_voltage_filter4.svg | 274 +- .../resources/IEEE_14_bus_voltage_filter5.svg | 212 +- .../test/resources/IEEE_14_id_prefixed.svg | 446 +-- .../src/test/resources/IEEE_24_bus.svg | 522 ++-- .../src/test/resources/IEEE_30_bus.svg | 586 ++-- .../src/test/resources/IEEE_57_bus.svg | 1046 +++---- .../src/test/resources/current_limits.svg | 50 +- .../resources/dangling_line_connected.svg | 118 +- .../resources/dangling_line_disconnected.svg | 118 +- .../src/test/resources/detailed_text_node.svg | 50 +- .../detailed_text_node_no_legend.svg | 50 +- .../diamond-spring-repulsion-factor-0.0.svg | 266 +- .../diamond-spring-repulsion-factor-0.2.svg | 386 +-- .../src/test/resources/edge_info_current.svg | 50 +- .../resources/edge_info_double_labels.svg | 66 +- .../resources/edge_info_missing_label.svg | 48 +- .../edge_info_perpendicular_label.svg | 64 +- .../resources/edge_info_reactive_power.svg | 50 +- .../src/test/resources/edge_info_shift.svg | 118 +- .../src/test/resources/edge_with_id.svg | 134 +- .../src/test/resources/edge_without_id.svg | 118 +- .../src/test/resources/hvdc-vl-depth-1.svg | 28 +- .../src/test/resources/hvdc.svg | 132 +- .../test/resources/parallel_transformers.svg | 76 +- .../src/test/resources/simple-eu-loop100.svg | 386 +-- .../src/test/resources/simple-eu-loop80.svg | 386 +-- .../src/test/resources/simple-eu.svg | 386 +-- .../src/test/resources/tie_line.svg | 106 +- .../src/test/resources/tie_line_filtered.svg | 92 +- .../src/test/resources/vl_description_id.svg | 50 +- .../resources/vl_description_substation.svg | 50 +- .../vl_description_substation_id.svg | 50 +- .../src/test/resources/voltage_limits.svg | 68 +- 48 files changed, 6157 insertions(+), 6157 deletions(-) diff --git a/network-area-diagram/src/test/java/com/powsybl/nad/layout/LayoutWithGeographicalPositionsTest.java b/network-area-diagram/src/test/java/com/powsybl/nad/layout/LayoutWithGeographicalPositionsTest.java index 1a9b84647..1db313792 100644 --- a/network-area-diagram/src/test/java/com/powsybl/nad/layout/LayoutWithGeographicalPositionsTest.java +++ b/network-area-diagram/src/test/java/com/powsybl/nad/layout/LayoutWithGeographicalPositionsTest.java @@ -34,10 +34,10 @@ void layoutWithGeographicalPositionsTest() { forceLayout.run(graph, new LayoutParameters()); Map actual = graph.getNodePositions(); - assertEquals(300, actual.get("VL1").getX()); - assertEquals(200, actual.get("VL1").getY()); - assertEquals(500, actual.get("VL2").getX()); - assertEquals(400, actual.get("VL2").getY()); + assertEquals(7854.0, actual.get("VL1").getX(), 0.1); + assertEquals(-5237.1, actual.get("VL1").getY(), 0.1); + assertEquals(13090.0, actual.get("VL2").getX(), 0.1); + assertEquals(-10480.5, actual.get("VL2").getY(), 0.1); } @Test @@ -49,10 +49,10 @@ void layoutWithGeographicalPositionsCustomisedParametersTest() { forceLayout.run(graph, new LayoutParameters()); Map actual = graph.getNodePositions(); - assertEquals(600, actual.get("VL1").getX()); - assertEquals(400, actual.get("VL1").getY()); - assertEquals(1000, actual.get("VL2").getX()); - assertEquals(800, actual.get("VL2").getY()); + assertEquals(10.5, actual.get("VL1").getX(), 0.1); + assertEquals(-7.0, actual.get("VL1").getY(), 0.1); + assertEquals(17.5, actual.get("VL2").getX(), 0.1); + assertEquals(-14.0, actual.get("VL2").getY(), 0.1); } @Test @@ -65,12 +65,12 @@ void layoutWithGeographicalPositionsTwoVoltageLevelsInSameSubstationTest() { forceLayout.run(graph, new LayoutParameters()); Map actual = graph.getNodePositions(); - assertEquals(350, actual.get("VL1").getX()); - assertEquals(200, actual.get("VL1").getY()); - assertEquals(275, actual.get("VL1_1").getX()); - assertEquals(243.3d, actual.get("VL1_1").getY(), 0.1); - assertEquals(275, actual.get("VL1_2").getX()); - assertEquals(156.7, actual.get("VL1_2").getY(), 0.1); + assertEquals(55.2, actual.get("VL1").getX(), 0.1); + assertEquals(-3.5, actual.get("VL1").getY(), 0.1); + assertEquals(-19.8, actual.get("VL1_1").getX(), 0.1); + assertEquals(39.8, actual.get("VL1_1").getY(), 0.1); + assertEquals(-19.8, actual.get("VL1_2").getX(), 0.1); + assertEquals(-46.8, actual.get("VL1_2").getY(), 0.1); } } diff --git a/network-area-diagram/src/test/resources/3wt.svg b/network-area-diagram/src/test/resources/3wt.svg index 18ade1abf..3d2cb5fd6 100644 --- a/network-area-diagram/src/test/resources/3wt.svg +++ b/network-area-diagram/src/test/resources/3wt.svg @@ -1,5 +1,5 @@ - +