diff --git a/single-line-diagram/single-line-diagram-core/src/main/java/com/powsybl/sld/layout/AbstractZoneLayout.java b/single-line-diagram/single-line-diagram-core/src/main/java/com/powsybl/sld/layout/AbstractZoneLayout.java index 2d690c92d..fa01bebb5 100644 --- a/single-line-diagram/single-line-diagram-core/src/main/java/com/powsybl/sld/layout/AbstractZoneLayout.java +++ b/single-line-diagram/single-line-diagram-core/src/main/java/com/powsybl/sld/layout/AbstractZoneLayout.java @@ -7,9 +7,8 @@ */ package com.powsybl.sld.layout; -import com.powsybl.sld.model.graphs.SubstationGraph; -import com.powsybl.sld.model.graphs.VoltageLevelGraph; -import com.powsybl.sld.model.graphs.ZoneGraph; +import com.powsybl.sld.layout.pathfinding.*; +import com.powsybl.sld.model.graphs.*; import com.powsybl.sld.model.nodes.*; import java.util.*; @@ -20,7 +19,8 @@ public abstract class AbstractZoneLayout extends AbstractBaseLayout { protected SubstationLayoutFactory sLayoutFactory; protected VoltageLevelLayoutFactory vLayoutFactory; - protected Map> layoutBySubstation; + protected Map> layoutBySubstation; + protected PathFinder pathFinder; protected AbstractZoneLayout(ZoneGraph graph, SubstationLayoutFactory sLayoutFactory, VoltageLevelLayoutFactory vLayoutFactory) { super(graph); @@ -35,6 +35,8 @@ protected AbstractZoneLayout(ZoneGraph graph, SubstationLayoutFactory sLayoutFac @Override public void run(LayoutParameters layoutParameters) { + pathFinder = new PathFinderFactory().create(layoutParameters.getZoneLayoutPathFinder()); + // Calculate all the coordinates for the substation graphs in the zone graph calculateCoordSubstations(layoutParameters); @@ -44,7 +46,7 @@ public void run(LayoutParameters layoutParameters) { protected abstract void calculateCoordSubstations(LayoutParameters layoutParameters); - protected void move(SubstationGraph subGraph, double dx, double dy) { + protected void move(BaseGraph subGraph, double dx, double dy) { for (VoltageLevelGraph vlGraph : subGraph.getVoltageLevels()) { vlGraph.setCoord(vlGraph.getX() + dx, vlGraph.getY() + dy); vlGraph.getLineEdges().forEach(s -> s.shiftSnakeLine(dx, dy)); diff --git a/single-line-diagram/single-line-diagram-core/src/main/java/com/powsybl/sld/layout/MatrixZoneLayout.java b/single-line-diagram/single-line-diagram-core/src/main/java/com/powsybl/sld/layout/MatrixZoneLayout.java index 57472a479..0ea089e78 100644 --- a/single-line-diagram/single-line-diagram-core/src/main/java/com/powsybl/sld/layout/MatrixZoneLayout.java +++ b/single-line-diagram/single-line-diagram-core/src/main/java/com/powsybl/sld/layout/MatrixZoneLayout.java @@ -8,7 +8,6 @@ package com.powsybl.sld.layout; import com.powsybl.commons.*; -import com.powsybl.sld.layout.pathfinding.*; import com.powsybl.sld.layout.zonebygrid.*; import com.powsybl.sld.model.coordinate.*; import com.powsybl.sld.model.coordinate.Point; @@ -23,55 +22,57 @@ */ public class MatrixZoneLayout extends AbstractZoneLayout { private final MatrixZoneLayoutModel model; - private final String[][] matrix; protected MatrixZoneLayout(ZoneGraph graph, String[][] matrix, SubstationLayoutFactory sLayoutFactory, VoltageLevelLayoutFactory vLayoutFactory) { super(graph, sLayoutFactory, vLayoutFactory); - this.model = new MatrixZoneLayoutModel(); - this.matrix = matrix; - } - - /** - * Calculate relative coordinate of substations in the zone - */ - @Override - protected void calculateCoordSubstations(LayoutParameters layoutParameters) { + this.model = new MatrixZoneLayoutModel(Objects.requireNonNull(matrix)); for (int row = 0; row < matrix.length; row++) { for (int col = 0; col < matrix[row].length; col++) { String id = matrix[row][col]; - SubstationGraph graph = getGraph().getSubstationGraph(id); - if (graph != null) { - // Display substations - layoutBySubstation.get(graph).run(layoutParameters); - } else if (!id.isEmpty()) { + SubstationGraph sGraph = graph.getSubstationGraph(id); + if (sGraph == null && !id.isEmpty()) { throw new PowsyblException("Substation '" + id + "' was not found in zone graph '" + getGraph().getId() + "'"); } - model.addSubstationSubgraph(graph, col, row); + model.addSubstationGraph(sGraph, row, col); } } + } + + /** + * Calculate relative coordinate of substations in the zone + */ + @Override + protected void calculateCoordSubstations(LayoutParameters layoutParameters) { + Matrix matrix = model.getMatrix(); + // Display substations on not empty Matrix cell + matrix.stream().filter(c -> !c.isEmpty()).map(MatrixCell::graph).forEach(graph -> layoutBySubstation.get(graph).run(layoutParameters)); // Height by rows - int maxHeightRow = model.getMatrixCellHeight(); + int maxHeightRow = 0; // Width by col - int maxWidthCol = model.getMatrixCellWidth(); + int maxWidthCol = 0; // Snakeline hallway (horizontal & vertical) int snakelineMargin = (int) layoutParameters.getZoneLayoutSnakeLinePadding(); // Zone size - int nbRows = matrix.length; - int nbCols = matrix[0].length; + int nbRows = matrix.rowCount(); + int nbCols = matrix.columnCount(); // Move each substation into its matrix position for (int row = 0; row < nbRows; row++) { + maxWidthCol = 0; for (int col = 0; col < nbCols; col++) { - String id = matrix[row][col]; - SubstationGraph graph = getGraph().getSubstationGraph(id); + MatrixCell cell = matrix.get(row, col); + BaseGraph graph = cell.graph(); if (graph != null) { - double dx = col * maxWidthCol + (col + 1.0) * snakelineMargin; - double dy = row * maxHeightRow + (row + 1.0) * snakelineMargin; + double dx = maxWidthCol + (col + 1.0) * snakelineMargin; + double dy = maxHeightRow + (row + 1.0) * snakelineMargin; move(graph, dx, dy); } + maxWidthCol += matrix.getMatrixCellWidth(col); } + double tmp = matrix.getMatrixCellHeight(row); + maxHeightRow += tmp; } - double zoneWidth = nbCols * maxWidthCol + (nbCols + 1.0) * snakelineMargin; - double zoneHeight = nbRows * maxHeightRow + (nbRows + 1.0) * snakelineMargin; + double zoneWidth = maxWidthCol + (nbCols + 1.0) * snakelineMargin; + double zoneHeight = maxHeightRow + (nbRows + 1.0) * snakelineMargin; getGraph().setSize(zoneWidth, zoneHeight); } diff --git a/single-line-diagram/single-line-diagram-core/src/main/java/com/powsybl/sld/layout/zonebygrid/Matrix.java b/single-line-diagram/single-line-diagram-core/src/main/java/com/powsybl/sld/layout/zonebygrid/Matrix.java new file mode 100644 index 000000000..3aaa7d648 --- /dev/null +++ b/single-line-diagram/single-line-diagram-core/src/main/java/com/powsybl/sld/layout/zonebygrid/Matrix.java @@ -0,0 +1,61 @@ +/** + * 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.layout.zonebygrid; + +import com.powsybl.sld.model.graphs.*; + +import java.util.*; +import java.util.stream.*; + +/** + * @author Thomas Adam {@literal } + */ +public class Matrix { + // [row] [col] + private final MatrixCell[][] cells; + + public Matrix(int rows, int cols) { + cells = new MatrixCell[rows][cols]; + } + + public Matrix set(int row, int col, MatrixCell cell) { + cells[row][col] = cell; + return this; + } + + public MatrixCell get(int row, int col) { + return cells[row][col]; + } + + public Stream stream() { + return Arrays.stream(cells).flatMap(Arrays::stream); + } + + public double getMatrixCellHeight(int row) { + return Stream.of(cells[row]).filter(cell -> !cell.isEmpty()).mapToDouble(cell -> cell.graph().getHeight()).max().orElse(0.0); + } + + public double getMatrixCellWidth(int col) { + double maxWidth = 0.0; + for (int row = 0; row < rowCount(); row++) { + BaseGraph graph = cells[row][col].graph(); + if (graph != null) { + maxWidth = Math.max(maxWidth, graph.getWidth()); + } + } + return maxWidth; + } + + public int rowCount() { + return cells.length; + } + + public int columnCount() { + return cells[0].length; + } +} diff --git a/single-line-diagram/single-line-diagram-core/src/main/java/com/powsybl/sld/layout/zonebygrid/MatrixCell.java b/single-line-diagram/single-line-diagram-core/src/main/java/com/powsybl/sld/layout/zonebygrid/MatrixCell.java index 7fc0673ad..cd6c9dfed 100644 --- a/single-line-diagram/single-line-diagram-core/src/main/java/com/powsybl/sld/layout/zonebygrid/MatrixCell.java +++ b/single-line-diagram/single-line-diagram-core/src/main/java/com/powsybl/sld/layout/zonebygrid/MatrixCell.java @@ -12,5 +12,12 @@ /** * @author Thomas Adam {@literal } */ -public record MatrixCell(BaseGraph graph, int col, int row) { +public record MatrixCell(BaseGraph graph, int row, int col) { + public boolean isEmpty() { + return graph() == null; + } + + public String getId() { + return graph == null ? "" : graph.getId(); + } } diff --git a/single-line-diagram/single-line-diagram-core/src/main/java/com/powsybl/sld/layout/zonebygrid/MatrixZoneLayoutModel.java b/single-line-diagram/single-line-diagram-core/src/main/java/com/powsybl/sld/layout/zonebygrid/MatrixZoneLayoutModel.java index b8d8d2695..b055bc651 100644 --- a/single-line-diagram/single-line-diagram-core/src/main/java/com/powsybl/sld/layout/zonebygrid/MatrixZoneLayoutModel.java +++ b/single-line-diagram/single-line-diagram-core/src/main/java/com/powsybl/sld/layout/zonebygrid/MatrixZoneLayoutModel.java @@ -20,45 +20,32 @@ */ public class MatrixZoneLayoutModel { - private final Map cellsById = new HashMap<>(); - private final List emptyCells = new ArrayList<>(); - - private int matrixNbRow = 0; - - private int matrixNbCol = 0; - - private int matrixCellHeight = -1; - - private int matrixCellWidth = -1; + private final Matrix matrix; private Grid pathFinderGrid; - public void addSubstationSubgraph(SubstationGraph graph, int col, int row) { - if (graph != null) { - cellsById.put(graph.getId(), new MatrixCell(graph, col, row)); - matrixCellHeight = Math.max(matrixCellHeight, (int) graph.getHeight()); - matrixCellWidth = Math.max(matrixCellWidth, (int) graph.getWidth()); - } else { - emptyCells.add(new MatrixCell(null, col, row)); - } - matrixNbRow = Math.max(matrixNbRow, row + 1); - matrixNbCol = Math.max(matrixNbCol, col + 1); + public MatrixZoneLayoutModel(String[][] ids) { + this.matrix = new Matrix(ids.length, ids[0].length); } - public int getMatrixCellWidth() { - return matrixCellWidth; - } - - public int getMatrixCellHeight() { - return matrixCellHeight; + public void addSubstationGraph(SubstationGraph graph, int row, int col) { + this.matrix.set(row, col, new MatrixCell(graph, row, col)); } private int getX(int col, double snakelineMargin) { - return ((col + 1) * (int) snakelineMargin) + (col * matrixCellWidth); + int matrixCellWidth = 0; + for (int c = 0; c < col; c++) { + matrixCellWidth += matrix.getMatrixCellWidth(c); + } + return ((col + 1) * (int) snakelineMargin) + matrixCellWidth; } private int getY(int row, double snakelineMargin) { - return (row + 1) * (int) snakelineMargin + row * matrixCellHeight; + int matrixCellHeight = 0; + for (int r = 0; r < row; r++) { + matrixCellHeight += matrix.getMatrixCellHeight(r); + } + return (row + 1) * (int) snakelineMargin + matrixCellHeight; } public List buildSnakeline(PathFinder pathfinder, @@ -92,21 +79,22 @@ public void computePathFindingGrid(ZoneGraph graph, LayoutParameters layoutParam pathFinderGrid = new Grid(width, height); - // Matrix cells grid lines - computeMatrixCellsAvailability(layoutParameters); - // Horizontal hallways lines computeHorizontalHallwaysAvailability(width, height, layoutParameters); // Vertical hallways lines computeVerticalHallwaysAvailability(width, height, layoutParameters); - // Substations are not available + // Make available all matrix cells + computeMatrixCellsAvailability(layoutParameters); + + // Make unavailable all voltagelevels computeSubstationsAvailability(layoutParameters); } private void computeSubstationsAvailability(LayoutParameters layoutParameters) { - cellsById.values().forEach(cell -> { + // For each not empty cells + matrix.stream().filter(c -> !c.isEmpty()).forEach(cell -> { BaseGraph graph = cell.graph(); graph.getVoltageLevelStream().forEach(vlGraph -> { double elementaryWidth = layoutParameters.getCellWidth() / 2; // the elementary step within a voltageLevel Graph is half a cell width @@ -126,19 +114,21 @@ private void computeSubstationsAvailability(LayoutParameters layoutParameters) { private void computeMatrixCellsAvailability(LayoutParameters layoutParameters) { int snakelineMargin = (int) layoutParameters.getZoneLayoutSnakeLinePadding(); - List allCells = new ArrayList<>(cellsById.values().stream().toList()); // Make empty cells available for snakeline computation - allCells.addAll(emptyCells); + List allCells = matrix.stream().toList(); allCells.forEach(cell -> { + double matrixCellWidth = matrix.getMatrixCellWidth(cell.col()); + double matrixCellHeight = matrix.getMatrixCellHeight(cell.row()); int ssX = getX(cell.col(), snakelineMargin); int ssY = getY(cell.row(), snakelineMargin); - for (int x = ssX - snakelineMargin; x < ssX - snakelineMargin + matrixCellWidth + snakelineMargin; x++) { - for (int y = ssY - snakelineMargin; y < ssY - snakelineMargin + matrixCellHeight + snakelineMargin; y += layoutParameters.getHorizontalSnakeLinePadding()) { + + for (int x = ssX - snakelineMargin; x < ssX + matrixCellWidth + snakelineMargin; x++) { + for (int y = ssY - snakelineMargin; y < ssY + matrixCellHeight + snakelineMargin; y += layoutParameters.getHorizontalSnakeLinePadding()) { pathFinderGrid.setAvailability(x, y, true); } } - for (int x = ssX - snakelineMargin; x < ssX - snakelineMargin + matrixCellWidth + snakelineMargin; x += layoutParameters.getVerticalSnakeLinePadding()) { - for (int y = ssY - snakelineMargin; y < ssY - snakelineMargin + matrixCellHeight + snakelineMargin; y++) { + for (int x = ssX - snakelineMargin; x < ssX + matrixCellWidth + snakelineMargin; x += layoutParameters.getVerticalSnakeLinePadding()) { + for (int y = ssY - snakelineMargin; y < ssY + matrixCellHeight + snakelineMargin; y++) { pathFinderGrid.setAvailability(x, y, true); } } @@ -147,20 +137,28 @@ private void computeMatrixCellsAvailability(LayoutParameters layoutParameters) { private void computeHorizontalHallwaysAvailability(int width, int height, LayoutParameters layoutParameters) { int snakelineMargin = (int) layoutParameters.getZoneLayoutSnakeLinePadding(); - for (int r = 0; r < matrixNbRow; r++) { + int nextY = 0; + for (int r = 0; r < matrix.rowCount(); r++) { + double matrixCellHeight = matrix.getMatrixCellHeight(r); for (int x = 0; x < width; x++) { - for (int hy = 0; hy < height; hy += snakelineMargin + matrixCellHeight) { - for (int y = hy; y < hy + snakelineMargin; y += layoutParameters.getHorizontalSnakeLinePadding()) { - pathFinderGrid.setAvailability(x, y, true); - } + for (int y = nextY; y < nextY + snakelineMargin; y += layoutParameters.getHorizontalSnakeLinePadding()) { + pathFinderGrid.setAvailability(x, y, true); } } + nextY += snakelineMargin + matrixCellHeight; + } + // Last snakelineMargin + for (int x = 0; x < width; x++) { + for (int y = nextY; y < nextY + snakelineMargin; y += layoutParameters.getHorizontalSnakeLinePadding()) { + pathFinderGrid.setAvailability(x, y, true); + } } } private void computeVerticalHallwaysAvailability(int width, int height, LayoutParameters layoutParameters) { int snakelineMargin = (int) layoutParameters.getZoneLayoutSnakeLinePadding(); - for (int c = 0; c < matrixNbCol; c++) { + for (int c = 0; c < matrix.columnCount(); c++) { + double matrixCellWidth = matrix.getMatrixCellWidth(c); for (int y = 0; y < height; y++) { for (int hx = 0; hx < width; hx += snakelineMargin + matrixCellWidth) { for (int x = hx; x < hx + snakelineMargin; x += layoutParameters.getVerticalSnakeLinePadding()) { @@ -171,8 +169,12 @@ private void computeVerticalHallwaysAvailability(int width, int height, LayoutPa } } - public boolean contains(String id) { - Objects.requireNonNull(id); - return cellsById.containsKey(id); + public boolean contains(String otherId) { + Objects.requireNonNull(otherId); + return matrix.stream().map(MatrixCell::getId).anyMatch(id -> id.equals(otherId)); + } + + public Matrix getMatrix() { + return matrix; } } diff --git a/single-line-diagram/single-line-diagram-core/src/test/java/com/powsybl/sld/iidm/TestCase13ZoneGraph.java b/single-line-diagram/single-line-diagram-core/src/test/java/com/powsybl/sld/iidm/TestCase13ZoneGraph.java index 08f25dfe6..d1f944a53 100644 --- a/single-line-diagram/single-line-diagram-core/src/test/java/com/powsybl/sld/iidm/TestCase13ZoneGraph.java +++ b/single-line-diagram/single-line-diagram-core/src/test/java/com/powsybl/sld/iidm/TestCase13ZoneGraph.java @@ -175,9 +175,8 @@ void testInvalidZoneGraphMatrix() { // Run matrix zone layout String[][] substationsIds = {{"B", "A"}}; - Layout layout = new MatrixZoneLayoutFactory().create(g, substationsIds, new HorizontalSubstationLayoutFactory(), new PositionVoltageLevelLayoutFactory()); - PowsyblException e = assertThrows(PowsyblException.class, () -> layout.run(layoutParameters)); + PowsyblException e = assertThrows(PowsyblException.class, () -> new MatrixZoneLayoutFactory().create(g, substationsIds, new HorizontalSubstationLayoutFactory(), new PositionVoltageLevelLayoutFactory())); assertEquals("Substation 'A' was not found in zone graph 'B_C'", e.getMessage()); } } diff --git a/single-line-diagram/single-line-diagram-core/src/test/resources/TestCase12ZoneGraphHHRaw.json b/single-line-diagram/single-line-diagram-core/src/test/resources/TestCase12ZoneGraphHHRaw.json index dbf474f99..e328e3df5 100644 --- a/single-line-diagram/single-line-diagram-core/src/test/resources/TestCase12ZoneGraphHHRaw.json +++ b/single-line-diagram/single-line-diagram-core/src/test/resources/TestCase12ZoneGraphHHRaw.json @@ -6128,20 +6128,20 @@ "node1" : "line1_ONE", "node2" : "line1_TWO" } ], - "snakeLine" : [ 586.0, 331.0, 586.0, 331.0, 586.0, 240.0, 2050.0, 240.0, 2050.0, 150.0, 2196.0, 150.0, 2196.0, 151.0, 2196.0, 151.0 ] + "snakeLine" : [ 586.0, 331.0, 586.0, 331.0, 586.0, 330.0, 2040.0, 330.0, 2040.0, 150.0, 2196.0, 150.0, 2196.0, 151.0, 2196.0, 151.0 ] }, { "id" : "hvdc_lcc", "nodes" : [ { "node1" : "hvdc_lcc_ONE", "node2" : "hvdc_lcc_TWO" } ], - "snakeLine" : [ 136.0, 331.0, 136.0, 331.0, 136.0, 330.0, 2020.0, 330.0, 2020.0, 120.0, 2096.0, 120.0, 2096.0, 151.0, 2096.0, 151.0 ] + "snakeLine" : [ 136.0, 331.0, 136.0, 331.0, 136.0, 300.0, 2050.0, 300.0, 2050.0, 150.0, 2096.0, 150.0, 2096.0, 151.0, 2096.0, 151.0 ] }, { "id" : "hvdc_vsc", "nodes" : [ { "node1" : "hvdc_vsc_ONE", "node2" : "hvdc_vsc_TWO" } ], - "snakeLine" : [ 186.0, 331.0, 186.0, 331.0, 186.0, 330.0, 1990.0, 330.0, 1990.0, 90.0, 2146.0, 90.0, 2146.0, 151.0, 2146.0, 151.0 ] + "snakeLine" : [ 186.0, 331.0, 186.0, 331.0, 186.0, 300.0, 2020.0, 300.0, 2020.0, 120.0, 2146.0, 120.0, 2146.0, 151.0, 2146.0, 151.0 ] } ] } \ No newline at end of file diff --git a/single-line-diagram/single-line-diagram-core/src/test/resources/TestCase12ZoneGraphHVRaw.json b/single-line-diagram/single-line-diagram-core/src/test/resources/TestCase12ZoneGraphHVRaw.json index 7c2d0db2d..f71d9867c 100644 --- a/single-line-diagram/single-line-diagram-core/src/test/resources/TestCase12ZoneGraphHVRaw.json +++ b/single-line-diagram/single-line-diagram-core/src/test/resources/TestCase12ZoneGraphHVRaw.json @@ -6129,20 +6129,20 @@ "node1" : "line1_ONE", "node2" : "line1_TWO" } ], - "snakeLine" : [ 736.0, 271.0, 736.0, 271.0, 736.0, 180.0, 1200.0, 180.0, 1200.0, 150.0, 1346.0, 150.0, 1346.0, 151.0, 1346.0, 151.0 ] + "snakeLine" : [ 736.0, 271.0, 736.0, 271.0, 736.0, 240.0, 1200.0, 240.0, 1200.0, 150.0, 1346.0, 150.0, 1346.0, 151.0, 1346.0, 151.0 ] }, { "id" : "hvdc_lcc", "nodes" : [ { "node1" : "hvdc_lcc_ONE", "node2" : "hvdc_lcc_TWO" } ], - "snakeLine" : [ 286.0, 271.0, 286.0, 271.0, 286.0, 210.0, 1170.0, 210.0, 1170.0, 120.0, 1246.0, 120.0, 1246.0, 151.0, 1246.0, 151.0 ] + "snakeLine" : [ 286.0, 271.0, 286.0, 271.0, 286.0, 270.0, 1180.0, 270.0, 1180.0, 120.0, 1246.0, 120.0, 1246.0, 151.0, 1246.0, 151.0 ] }, { "id" : "hvdc_vsc", "nodes" : [ { "node1" : "hvdc_vsc_ONE", "node2" : "hvdc_vsc_TWO" } ], - "snakeLine" : [ 336.0, 271.0, 336.0, 271.0, 336.0, 270.0, 1140.0, 270.0, 1140.0, 90.0, 1296.0, 90.0, 1296.0, 151.0, 1296.0, 151.0 ] + "snakeLine" : [ 336.0, 271.0, 336.0, 271.0, 336.0, 270.0, 1170.0, 270.0, 1170.0, 90.0, 1296.0, 90.0, 1296.0, 151.0, 1296.0, 151.0 ] } ] } \ No newline at end of file diff --git a/single-line-diagram/single-line-diagram-core/src/test/resources/TestCase13ZoneGraphHH.svg b/single-line-diagram/single-line-diagram-core/src/test/resources/TestCase13ZoneGraphHH.svg index 9a3fa3d59..0616c5d98 100644 --- a/single-line-diagram/single-line-diagram-core/src/test/resources/TestCase13ZoneGraphHH.svg +++ b/single-line-diagram/single-line-diagram-core/src/test/resources/TestCase13ZoneGraphHH.svg @@ -1,5 +1,5 @@ - +