diff --git a/src/main/java/edu/ie3/netpad/exception/GridManipulationException.java b/src/main/java/edu/ie3/netpad/exception/GridManipulationException.java new file mode 100644 index 0000000..1f4bed6 --- /dev/null +++ b/src/main/java/edu/ie3/netpad/exception/GridManipulationException.java @@ -0,0 +1,16 @@ +/* + * © 2020. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation +*/ +package edu.ie3.netpad.exception; + +public class GridManipulationException extends Exception { + public GridManipulationException(String message) { + super(message); + } + + public GridManipulationException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/edu/ie3/netpad/grid/controller/GridController.java b/src/main/java/edu/ie3/netpad/grid/controller/GridController.java index ed9add4..0df65af 100644 --- a/src/main/java/edu/ie3/netpad/grid/controller/GridController.java +++ b/src/main/java/edu/ie3/netpad/grid/controller/GridController.java @@ -5,21 +5,25 @@ */ package edu.ie3.netpad.grid.controller; +import static java.lang.Math.*; + import edu.ie3.datamodel.graph.SubGridGate; import edu.ie3.datamodel.graph.SubGridTopologyGraph; import edu.ie3.datamodel.models.UniqueEntity; import edu.ie3.datamodel.models.input.InputEntity; +import edu.ie3.datamodel.models.input.MeasurementUnitInput; import edu.ie3.datamodel.models.input.NodeInput; -import edu.ie3.datamodel.models.input.connector.LineInput; -import edu.ie3.datamodel.models.input.connector.Transformer2WInput; -import edu.ie3.datamodel.models.input.container.GridContainer; -import edu.ie3.datamodel.models.input.container.JointGridContainer; -import edu.ie3.datamodel.models.input.container.SubGridContainer; +import edu.ie3.datamodel.models.input.connector.*; +import edu.ie3.datamodel.models.input.container.*; +import edu.ie3.datamodel.models.input.graphics.LineGraphicInput; +import edu.ie3.datamodel.models.input.graphics.NodeGraphicInput; import edu.ie3.datamodel.models.input.system.LoadInput; import edu.ie3.datamodel.models.input.system.PvInput; import edu.ie3.datamodel.models.input.system.StorageInput; import edu.ie3.datamodel.models.input.system.SystemParticipantInput; import edu.ie3.datamodel.utils.ContainerUtils; +import edu.ie3.datamodel.utils.GridAndGeoUtils; +import edu.ie3.netpad.exception.GridManipulationException; import edu.ie3.netpad.grid.GridModel; import edu.ie3.netpad.grid.GridModification; import edu.ie3.netpad.grid.ModifiedSubGridData; @@ -30,20 +34,33 @@ import edu.ie3.netpad.io.event.SaveGridEvent; import edu.ie3.netpad.map.event.MapEvent; import edu.ie3.netpad.tool.controller.ToolController; +import edu.ie3.netpad.tool.controller.ToolDialogs; import edu.ie3.netpad.tool.event.FixLineLengthRequestEvent; import edu.ie3.netpad.tool.event.LayoutGridRequestEvent; import edu.ie3.netpad.tool.event.LayoutGridResponse; import edu.ie3.netpad.tool.event.ToolEvent; import edu.ie3.netpad.tool.grid.LineLengthFixer; import edu.ie3.netpad.tool.grid.LineLengthResolutionMode; +import edu.ie3.util.geo.GeoUtils; +import edu.ie3.util.quantities.PowerSystemUnits; import java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.beans.value.ChangeListener; +import javax.measure.Quantity; +import javax.measure.quantity.Angle; +import javax.measure.quantity.Length; +import net.morbz.osmonaut.osm.LatLon; +import org.jgrapht.graph.DirectedAcyclicGraph; +import org.locationtech.jts.geom.Coordinate; +import org.locationtech.jts.geom.LineString; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import tech.units.indriya.ComparableQuantity; +import tech.units.indriya.quantity.Quantities; +import tech.units.indriya.unit.Units; /** * //ToDo: Class Description @@ -90,7 +107,6 @@ public Map getSubGrids() { } private void handleReadGridEvent(ReadGridEvent newValue) { - // clear subGrids subGrids.clear(); @@ -225,6 +241,547 @@ private ChangeListener toolEventListener() { }; } + /** + * Fix the line length discrepancy based on the user given {@link ToolDialogs.FixLineLengthData} + * + * @param resolutionMode Selected resolution mode + * @param selectedSubnets Subnets to apply adjustments to + */ + public void fixLineLength(LineLengthResolutionMode resolutionMode, Set selectedSubnets) { + JointGridContainer updatedGrid; + + /* Act depending on the chosen resolution mode */ + switch (resolutionMode) { + case GEOGRAPHICAL: + updatedGrid = setElectricalToGeographicalLineLength(selectedSubnets); + break; + case ELECTRICAL: + updatedGrid = setGeographicalToElectricalLineLength(selectedSubnets); + break; + default: + log.error("Unknown resolution mode '{}'", resolutionMode); + return; + } + + /* Build a new event and inform the listeners about the "new" / adapted grid model */ + handleReadGridEvent(new ReadGridEvent(updatedGrid)); + } + + /** + * Sets the electrical length of all lines within the selected sub nets to the length of their + * geographical line string if apparent. If not, it is set to the geographical distance between + * start and end node. + * + * @param selectedSubnets Subnets to apply adjustments to + * @return A {@link JointGridContainer} with updated line models + * @deprecated This better fits in {@link GridAndGeoUtils} + */ + @Deprecated + private JointGridContainer setElectricalToGeographicalLineLength(Set selectedSubnets) { + /* Adjust the electrical line length to be the same as the geographical distance */ + List subGridContainers = + subGrids.values().parallelStream() + .map(GridModel::getSubGridContainer) + .map( + subGridContainer -> { + if (!selectedSubnets.contains(subGridContainer.getSubnet())) { + /* If this grid isn't selected, hand it back, as it is */ + return subGridContainer; + } else { + /* Update all lines */ + Set lines = + subGridContainer.getRawGrid().getLines().parallelStream() + .map(GridController::setLineLengthToGeographicDistance) + .collect(Collectors.toSet()); + + /* Put together, what has been there before */ + RawGridElements rawGrid = + new RawGridElements( + subGridContainer.getRawGrid().getNodes(), + lines, + subGridContainer.getRawGrid().getTransformer2Ws(), + subGridContainer.getRawGrid().getTransformer3Ws(), + subGridContainer.getRawGrid().getSwitches(), + subGridContainer.getRawGrid().getMeasurementUnits()); + return new SubGridContainer( + subGridContainer.getGridName(), + subGridContainer.getSubnet(), + rawGrid, + subGridContainer.getSystemParticipants(), + subGridContainer.getGraphics()); + } + }) + .collect(Collectors.toList()); + + /* Assemble all sub grids to one container */ + return ContainerUtils.combineToJointGrid(subGridContainers); + } + + /** + * Adjusts the line length to the length of their geographical line string if apparent. If not, it + * is set to the geographical distance between start and end node. + * + * @param line line model to adjust + * @return The adjusted line model + * @deprecated This better fits in {@link GridAndGeoUtils} + */ + @Deprecated + private static LineInput setLineLengthToGeographicDistance(LineInput line) { + ComparableQuantity lineLength; + lineLength = + lengthOfLineString(line.getGeoPosition()) + .orElseGet( + () -> { + log.warn( + "Cannot determine the length of the line string of line '{}' as it only contains one coordinate." + + " Take distance between it's nodes instead.", + line); + return GridAndGeoUtils.distanceBetweenNodes(line.getNodeA(), line.getNodeB()); + }); + return line.copy().length(lineLength).build(); + } + + /** + * Calculate the length of a line string + * + * @param lineString The line string to calculate the length of + * @return An option to the length, if it can be determined + * @deprecated This method should be transferred to PowerSystemUtils + */ + @Deprecated + private static Optional> lengthOfLineString(LineString lineString) { + Coordinate[] coordinates = lineString.getCoordinates(); + + if (coordinates.length == 1) { + return Optional.empty(); + } + + /* Go over the line piecewise and sum up the distance */ + Coordinate a = coordinates[0]; + Coordinate b = coordinates[1]; + ComparableQuantity length = GeoUtils.calcHaversine(a.x, a.y, b.x, b.y); + for (int coordIndex = 2; coordIndex < coordinates.length; coordIndex++) { + a = b; + b = coordinates[coordIndex]; + length = length.add(GeoUtils.calcHaversine(a.x, a.y, b.x, b.y)); + } + + return Optional.of(length); + } + + /** + * Update the lines and nodes in the given joint grid container geo position + * + * @param selectedSubnets Set of selected sub grid numbers + * @return The updated grid container + * @deprecated This better fits in {@link GridAndGeoUtils} + */ + @Deprecated + private JointGridContainer setGeographicalToElectricalLineLength(Set selectedSubnets) { + /* + * TODO CK + * 1) Go through sub grid containers considering a "tree order" (from upper voltage level to lower) + * 2) Adapt the position of underlying grids according to the updated position of the upper grid + */ + + List subGridContainers = + subGrids.values().parallelStream() + .map(GridModel::getSubGridContainer) + .map( + subGridContainer -> { + if (selectedSubnets.contains(subGridContainer.getSubnet())) + try { + return setGeographicalToElectricalLineLength(subGridContainer); + } catch (GridManipulationException e) { + log.error( + "Cannot adapt sub grid container '" + + subGridContainer.getSubnet() + + "'. Return the original one.", + e); + return subGridContainer; + } + else return subGridContainer; + }) + .collect(Collectors.toList()); + + /* Assemble all sub grids to one container */ + return ContainerUtils.combineToJointGrid(subGridContainers); + } + + /** + * Go through each sub grid container and update the lines and nodes in geo position + * + * @param subGridContainer The sub grid container to adapt + * @return The updated grid container + * @throws GridManipulationException If the adaption of geographic position is not possible + * @deprecated This better fits in {@link GridAndGeoUtils} + */ + @Deprecated + private SubGridContainer setGeographicalToElectricalLineLength(SubGridContainer subGridContainer) + throws GridManipulationException { + /* Determine the slack node as the starting point of the traversal through the grid */ + Set transformerNodes = + subGridContainer.getRawGrid().getTransformer2Ws().stream() + .map(Transformer2WInput::getNodeB) + .collect(Collectors.toSet()); + transformerNodes.addAll( + subGridContainer.getRawGrid().getTransformer3Ws().stream() + .map(Transformer3WInput::getNodeB) + .collect(Collectors.toSet())); + transformerNodes.addAll( + subGridContainer.getRawGrid().getTransformer3Ws().stream() + .map(Transformer3WInput::getNodeC) + .collect(Collectors.toSet())); + if (transformerNodes.size() > 1) + throw new GridManipulationException( + "There is more than one starting node in subnet '" + + subGridContainer.getSubnet() + + "'. Currently only star topology is supported."); + NodeInput startNode = + transformerNodes.stream() + .findFirst() + .orElseThrow( + () -> + new GridManipulationException( + "Cannot determine the starting node of subnet '" + + subGridContainer.getSubnet() + + "'.")); + + /* Build a topology graph of lines and switches to ensure, that the sub grid is a acyclic star-type topology */ + DirectedAcyclicGraph topologyGraph = + new DirectedAcyclicGraph<>(ConnectorInput.class); + try { + subGridContainer.getRawGrid().allEntitiesAsList().stream() + .filter( + element -> + LineInput.class.isAssignableFrom(element.getClass()) + || SwitchInput.class.isAssignableFrom(element.getClass())) + .map(element -> (ConnectorInput) element) + .forEach( + connector -> { + /* Build the topology graph */ + topologyGraph.addVertex(connector.getNodeA()); + topologyGraph.addVertex(connector.getNodeB()); + topologyGraph.addEdge(connector.getNodeA(), connector.getNodeB(), connector); + }); + } catch (IllegalArgumentException e) { + throw new GridManipulationException( + "The given sub grid topology of sub net '" + + subGridContainer.getSubnet() + + "' is not suitable for this operation. It has to be acyclic.", + e); + } + + /* Start from the slack node and traverse downwards */ + Map nodeMapping = + new HashMap<>(subGridContainer.getRawGrid().getNodes().size()); + Set updatedLines = new HashSet<>(subGridContainer.getRawGrid().getLines().size()); + nodeMapping.put(startNode.getUuid(), startNode); + traverseAndUpdateLines(startNode, topologyGraph, nodeMapping, updatedLines); + + return update(subGridContainer, nodeMapping, updatedLines); + } + + /** + * Traverses through the topology graph with depth-first-search and update the positions of the + * nodes and lines. + * + * @param startNode Node, at which the traversal starts + * @param topologyGraph Graph, that depicts the sub grid containers topology + * @param nodeMapping Mapping from "old" node's uuid to new node model + * @param updatedLines Collection of updated line models + * @throws GridManipulationException If no updated version of the given start node can be + * determined + * @deprecated This better fits in {@link GridAndGeoUtils} + */ + @Deprecated + private void traverseAndUpdateLines( + NodeInput startNode, + DirectedAcyclicGraph topologyGraph, + Map nodeMapping, + Set updatedLines) + throws GridManipulationException { + Set descendantLines = + topologyGraph.edgesOf(startNode).stream() + .filter( + connector -> + LineInput.class.isAssignableFrom(connector.getClass()) + && updatedLines.stream() + .noneMatch(ul -> ul.getUuid().equals(connector.getUuid()))) + .map(connector -> (LineInput) connector) + .collect(Collectors.toSet()); + for (LineInput line : descendantLines) { + /* Determine the bearing (we are still operating on the "old" lines and nodes) */ + Coordinate coordinateA = startNode.getGeoPosition().getCoordinate(); + NodeInput nodeB = line.getNodeA().equals(startNode) ? line.getNodeB() : line.getNodeA(); + Coordinate coordinateB = nodeB.getGeoPosition().getCoordinate(); + ComparableQuantity bearing = + getBearing( + new LatLon(coordinateA.y, coordinateA.x), new LatLon(coordinateB.y, coordinateB.x)); + + /* Determine the new geo position */ + NodeInput updatedNodeA = nodeMapping.get(startNode.getUuid()); + if (Objects.isNull(updatedNodeA)) + throw new GridManipulationException( + "Cannot update grid, as there is an issue with updated nodes"); + Coordinate updatedCoordinateA = updatedNodeA.getGeoPosition().getCoordinate(); + LatLon latLonB = + secondCoordinateWithDistanceAndBearing( + new LatLon(updatedCoordinateA.y, updatedCoordinateA.x), line.getLength(), bearing); + + /* Copy node B */ + NodeInput updatedNodeB = nodeB.copy().geoPosition(GeoUtils.latlonToPoint(latLonB)).build(); + nodeMapping.put(nodeB.getUuid(), updatedNodeB); + + /* Adapt the line */ + LineInput updatedLine = + line.copy() + .nodeA(updatedNodeA) + .nodeB(updatedNodeB) + .geoPosition( + GridAndGeoUtils.buildSafeLineStringBetweenNodes(updatedNodeA, updatedNodeB)) + .build(); + updatedLines.add(updatedLine); + + /* Go deeper at the node, that we last have traveled */ + traverseAndUpdateLines(nodeB, topologyGraph, nodeMapping, updatedLines); + } + } + + /** + * Updates the whole container by replacing the nodes in each element and set the updated lines + * + * @param container The container to update + * @param nodeMapping Mapping from "old" node's uuid to new node model + * @param updatedLines Collection of updated line models + * @param Type of the container to update + * @return The updated container + */ + private C update( + C container, Map nodeMapping, Set updatedLines) { + /* Update the raw grid */ + RawGridElements rawGrid = container.getRawGrid(); + Set nodes = + rawGrid.getNodes().stream() + .map(node -> nodeMapping.getOrDefault(node.getUuid(), node)) + .collect(Collectors.toSet()); + /* If there is an updated line with the same uuid, take this one, otherwise take the existing one. */ + Set lines = + rawGrid.getLines().stream() + .map( + line -> + updatedLines.stream() + .filter(updatedLine -> updatedLine.getUuid().equals(line.getUuid())) + .findFirst() + .orElse(line)) + .map( + line -> + line.copy() + .nodeA(nodeMapping.getOrDefault(line.getNodeA().getUuid(), line.getNodeA())) + .nodeB(nodeMapping.getOrDefault(line.getNodeB().getUuid(), line.getNodeB())) + .build()) + .collect(Collectors.toSet()); + Set transformers2w = + rawGrid.getTransformer2Ws().stream() + .map( + transformer -> + transformer + .copy() + .nodeA( + nodeMapping.getOrDefault( + transformer.getNodeA().getUuid(), transformer.getNodeA())) + .nodeB( + nodeMapping.getOrDefault( + transformer.getNodeB().getUuid(), transformer.getNodeB())) + .build()) + .collect(Collectors.toSet()); + Set transformers3w = + rawGrid.getTransformer3Ws().stream() + .map( + transformer -> + transformer + .copy() + .nodeA( + nodeMapping.getOrDefault( + transformer.getNodeA().getUuid(), transformer.getNodeA())) + .nodeB( + nodeMapping.getOrDefault( + transformer.getNodeB().getUuid(), transformer.getNodeB())) + .nodeC( + nodeMapping.getOrDefault( + transformer.getNodeC().getUuid(), transformer.getNodeC())) + .build()) + .collect(Collectors.toSet()); + Set switches = + rawGrid.getSwitches().stream() + .map( + switcher -> + switcher + .copy() + .nodeA( + nodeMapping.getOrDefault( + switcher.getNodeA().getUuid(), switcher.getNodeA())) + .nodeB( + nodeMapping.getOrDefault( + switcher.getNodeB().getUuid(), switcher.getNodeB())) + .build()) + .collect(Collectors.toSet()); + Set measurements = + rawGrid.getMeasurementUnits().stream() + .map( + measurement -> + measurement + .copy() + .node( + nodeMapping.getOrDefault( + measurement.getNode().getUuid(), measurement.getNode())) + .build()) + .collect(Collectors.toSet()); + RawGridElements updatedRawGridElements = + new RawGridElements(nodes, lines, transformers2w, transformers3w, switches, measurements); + + /* Update system participants */ + List updatedElements = + container.getSystemParticipants().allEntitiesAsList().stream() + .map( + participant -> + participant + .copy() + .node( + nodeMapping.getOrDefault( + participant.getNode().getUuid(), participant.getNode())) + .build()) + .collect(Collectors.toList()); + SystemParticipants updatedParticipants = new SystemParticipants(updatedElements); + + /* Update graphic elements */ + GraphicElements graphics = container.getGraphics(); + Set nodeGraphics = + graphics.getNodeGraphics().stream() + .map( + graphic -> + graphic + .copy() + .node( + nodeMapping.getOrDefault( + graphic.getNode().getUuid(), graphic.getNode())) + .build()) + .collect(Collectors.toSet()); + Map lineMapping = + updatedLines.stream().collect(Collectors.toMap(UniqueEntity::getUuid, line -> line)); + Set lineGraphics = + graphics.getLineGraphics().stream() + .map( + graphic -> + graphic + .copy() + .line( + lineMapping.getOrDefault( + graphic.getLine().getUuid(), graphic.getLine())) + .build()) + .collect(Collectors.toSet()); + GraphicElements updatedGraphics = new GraphicElements(nodeGraphics, lineGraphics); + + if (JointGridContainer.class.isAssignableFrom(container.getClass())) + return (C) + new JointGridContainer( + container.getGridName(), + updatedRawGridElements, + updatedParticipants, + updatedGraphics); + else + return (C) + new SubGridContainer( + container.getGridName(), + ((SubGridContainer) container).getSubnet(), + updatedRawGridElements, + updatedParticipants, + updatedGraphics); + } + + /** + * Determine the bearing between two coordinates on the earth surface. + * + * @param latLon1 First coordinate + * @param latLon2 Second coordinate + * @return The bearing in {@link PowerSystemUnits#DEGREE_GEOM} + * @deprecated This better fits in {@link GeoUtils} + */ + @Deprecated + private static ComparableQuantity getBearing(LatLon latLon1, LatLon latLon2) { + double lat1Rad = toRadians(latLon1.getLat()); + double long1Rad = toRadians(latLon1.getLon()); + double lat2Rad = toRadians(latLon2.getLat()); + double long2Rad = toRadians(latLon2.getLon()); + + /* Determine distance between both points in km */ + double distance = + GeoUtils.calcHaversine( + latLon1.getLat(), latLon1.getLon(), latLon2.getLat(), latLon2.getLon()) + .getValue() + .doubleValue(); + + /* Calculate the portion of the given distance from a full turnaround around the earth */ + double portionOfFullTurnaround = + distance / GeoUtils.EARTH_RADIUS.to(PowerSystemUnits.KILOMETRE).getValue().doubleValue(); + + double bearing = + toDegrees( + asin( + (tan(long2Rad - long1Rad) + * (cos(portionOfFullTurnaround) - sin(lat1Rad) * sin(lat2Rad))) + / (sin(portionOfFullTurnaround) * cos(lat1Rad)))); + + /* Adapt the bearing (+/-90°) in order to meet our understanding of the bearing (0...360°) */ + if (lat2Rad <= lat1Rad) bearing = 180 - bearing; + else if (long2Rad < long1Rad) bearing = 360 + bearing; + + return Quantities.getQuantity(bearing, PowerSystemUnits.DEGREE_GEOM); + } + + /** + * Calculates a second coordinate in relation to a start coordinate, a bearing and a given + * distance. Many thanks to shayanjm for his very + * helpful Clojure-Gist, which + * served as a blue print for this implementation + * + * @param start Start position + * @param distanceQty Intended distance between start and target position + * @param bearingQty Intended bearing between start and target position (0° points northbound, + * increasing clockwise) + * @return The second coordinate with intended distance and bearing with reference to the start + * coordinate + * @deprecated This better fits in {@link GeoUtils} + */ + @Deprecated + private static LatLon secondCoordinateWithDistanceAndBearing( + LatLon start, Quantity distanceQty, Quantity bearingQty) { + /* Do the unit conversions, so that the input complies to specifications */ + double distance = distanceQty.to(PowerSystemUnits.KILOMETRE).getValue().doubleValue(); + double bearing = bearingQty.to(Units.RADIAN).getValue().doubleValue(); + double latStart = toRadians(start.getLat()); + double longStart = toRadians(start.getLon()); + + /* Calculate the portion of the given distance from a full turnaround around the earth */ + double portionOfFullTurnaround = + distance / GeoUtils.EARTH_RADIUS.to(PowerSystemUnits.KILOMETRE).getValue().doubleValue(); + + /* Calculate the target coordinate */ + double targetLatRad = + asin( + sin(latStart) * cos(portionOfFullTurnaround) + + cos(latStart) + * sin(portionOfFullTurnaround) + * cos(bearing)); // Do not convert to degrees. Is used in second formula. + double targetLongRad = + longStart + + atan2( + sin(bearing) * sin(portionOfFullTurnaround) * cos(latStart), + cos(portionOfFullTurnaround) - sin(latStart) * sin(targetLatRad)); + + return new LatLon(toDegrees(targetLatRad), toDegrees(targetLongRad)); + } + private ChangeListener ioEventListener() { return (observable, oldValue, newValue) -> { if (newValue instanceof ReadGridEvent) { diff --git a/src/main/java/edu/ie3/netpad/tool/controller/ToolDialogs.java b/src/main/java/edu/ie3/netpad/tool/controller/ToolDialogs.java index 0578493..379c47a 100644 --- a/src/main/java/edu/ie3/netpad/tool/controller/ToolDialogs.java +++ b/src/main/java/edu/ie3/netpad/tool/controller/ToolDialogs.java @@ -44,11 +44,10 @@ public static Dialog fixLineLengthDialog() { ToggleButton electricalBtn = new RadioButton("Electrical → Geographical"); electricalBtn.setUserData(ELECTRICAL); electricalBtn.setToggleGroup(tglGrp); - electricalBtn.setDisable(true); ToggleButton geographicalBtn = new RadioButton("Geographical → Electrical"); geographicalBtn.setUserData(GEOGRAPHICAL); geographicalBtn.setToggleGroup(tglGrp); - tglGrp.selectToggle(geographicalBtn); + tglGrp.selectToggle(electricalBtn); gridPane.addRow(0, modeLbl, electricalBtn); gridPane.add(geographicalBtn, 1, 1); diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml index 16fea71..5ea8494 100644 --- a/src/main/resources/log4j2.xml +++ b/src/main/resources/log4j2.xml @@ -42,6 +42,10 @@ + + + + diff --git a/src/test/groovy/edu/ie3/netpad/grid/controller/GridControllerTest.groovy b/src/test/groovy/edu/ie3/netpad/grid/controller/GridControllerTest.groovy index e42caee..bb54fe2 100644 --- a/src/test/groovy/edu/ie3/netpad/grid/controller/GridControllerTest.groovy +++ b/src/test/groovy/edu/ie3/netpad/grid/controller/GridControllerTest.groovy @@ -5,11 +5,89 @@ */ package edu.ie3.netpad.grid.controller -import edu.ie3.netpad.test.common.SampleData +import edu.ie3.datamodel.models.OperationTime +import edu.ie3.datamodel.models.StandardUnits +import edu.ie3.datamodel.models.input.NodeInput +import edu.ie3.datamodel.models.input.OperatorInput +import edu.ie3.datamodel.models.input.connector.LineInput +import edu.ie3.datamodel.models.input.connector.type.LineTypeInput +import edu.ie3.datamodel.models.input.container.GridContainer +import edu.ie3.datamodel.models.input.system.characteristic.OlmCharacteristicInput +import edu.ie3.datamodel.models.voltagelevels.GermanVoltageLevelUtils +import edu.ie3.datamodel.utils.GridAndGeoUtils +import edu.ie3.netpad.io.controller.IoController +import edu.ie3.netpad.io.event.ReadGridEvent import edu.ie3.netpad.util.SampleGridFactory +import edu.ie3.test.common.grids.LengthAdaptionTestGrid +import edu.ie3.util.quantities.PowerSystemUnits +import edu.ie3.util.quantities.QuantityUtil +import net.morbz.osmonaut.osm.LatLon +import org.locationtech.jts.geom.Coordinate +import spock.lang.Shared import spock.lang.Specification +import spock.lang.Unroll +import tech.units.indriya.quantity.Quantities -class GridControllerTest extends Specification implements SampleData { +import javax.measure.Quantity +import javax.measure.quantity.Length +import java.util.stream.Collectors + +import edu.ie3.util.geo.GeoUtils + +class GridControllerTest extends Specification implements LengthAdaptionTestGrid { + + @Shared + LineInput testLine + + def setupSpec() { + /* Build the test line */ + def nodeA = new NodeInput( + UUID.randomUUID(), + "nodeA", + OperatorInput.NO_OPERATOR_ASSIGNED, + OperationTime.notLimited(), + Quantities.getQuantity(1d, PowerSystemUnits.PU), + false, + GeoUtils.DEFAULT_GEOMETRY_FACTORY.createPoint(new Coordinate(51.49292, 7.41197)), + GermanVoltageLevelUtils.LV, + 1 + ) + + def nodeB = new NodeInput( + UUID.randomUUID(), + "nodeB", + OperatorInput.NO_OPERATOR_ASSIGNED, + OperationTime.notLimited(), + Quantities.getQuantity(1d, PowerSystemUnits.PU), + false, + GeoUtils.DEFAULT_GEOMETRY_FACTORY.createPoint(new Coordinate(51.49404, 7.41279)), + GermanVoltageLevelUtils.LV, + 1 + ) + + testLine = new LineInput( + UUID.randomUUID(), + "testLine", + OperatorInput.NO_OPERATOR_ASSIGNED, + OperationTime.notLimited(), + nodeA, + nodeB, + 1, + new LineTypeInput( + UUID.randomUUID(), + "testType", + Quantities.getQuantity(0d, StandardUnits.ADMITTANCE_PER_LENGTH), + Quantities.getQuantity(0d, StandardUnits.ADMITTANCE_PER_LENGTH), + Quantities.getQuantity(0d, StandardUnits.IMPEDANCE_PER_LENGTH), + Quantities.getQuantity(0d, StandardUnits.IMPEDANCE_PER_LENGTH), + Quantities.getQuantity(0d, StandardUnits.ELECTRIC_CURRENT_MAGNITUDE), + Quantities.getQuantity(0.4, StandardUnits.RATED_VOLTAGE_MAGNITUDE) + ), + Quantities.getQuantity(0d, PowerSystemUnits.KILOMETRE), + GridAndGeoUtils.buildSafeLineStringBetweenNodes(nodeA, nodeB), + OlmCharacteristicInput.CONSTANT_CHARACTERISTIC + ) + } def "A GridController should find the correct grid based from a valid mapping of grid id and a set of entity ids"() { given: @@ -47,7 +125,280 @@ class GridControllerTest extends Specification implements SampleData { def subGridUuid = GridController.instance.findSubGridUuid(validMapping, sampleGrid) - subGridUuid.isPresent() + subGridUuid.present subGridUuid == Optional.of(gridUuid) } + + def "A GridController is able to calculate the correct total length of a LineString"() { + given: + def coordinates = [ + new Coordinate(7.41197, 51.49292), + new Coordinate(7.41183, 51.49333), + new Coordinate(7.41189, 51.49341), + new Coordinate(7.41172, 51.49391), + new Coordinate(7.41279, 51.49404) + ] as Coordinate[] + + def lineString = GeoUtils.DEFAULT_GEOMETRY_FACTORY.createLineString(coordinates) + def expectedLength = Quantities.getQuantity(0.2372622519686716860237276, PowerSystemUnits.KILOMETRE) + + when: + def actualLength = GridController.lengthOfLineString(lineString) + + then: + actualLength.present + QuantityUtil.isEquivalentAbs(actualLength.get(), expectedLength) + } + + /* Remark: The emtpy Optional being returned when handing in a LineString with only one node cannot be tested, as + * a LineString cannot be created with one coordinate only */ + + def "A GridController is able to adjust the electrical line length to it's line string's total length"() { + given: + def coordinates = [ + new Coordinate(7.41197, 51.49292), + new Coordinate(7.41183, 51.49333), + new Coordinate(7.41189, 51.49341), + new Coordinate(7.41172, 51.49391), + new Coordinate(7.41279, 51.49404) + ] as Coordinate[] + def lineString = GeoUtils.DEFAULT_GEOMETRY_FACTORY.createLineString(coordinates) + def line = testLine.copy().geoPosition(lineString).build() + def expectedLength = Quantities.getQuantity(0.2372622519686716860237276, PowerSystemUnits.KILOMETRE) + + when: + def updateLine = GridController.setLineLengthToGeographicDistance(line) + + then: + QuantityUtil.isEquivalentAbs(updateLine.length, expectedLength) + } + + def "A GridController is capable of adjusting the electrical line length to actual length within a selected subnet"() { + given: + /* Load sample grid and announce it to the grid controller */ + def sampleGrid = SampleGridFactory.sampleJointGrid() + IoController.instance.notifyListener(new ReadGridEvent(sampleGrid)) + + def expectedLineLengths = [ + /* subnet 1 */ + (UUID.fromString("92ec3bcf-1777-4d38-af67-0bf7c9fa73c7")): Quantities.getQuantity(0.13570403123164909653797, PowerSystemUnits.KILOMETRE), + (UUID.fromString("4dd1bde7-0ec9-4540-ac9e-008bc5f883ba")): Quantities.getQuantity(0.065091844094861731826615, PowerSystemUnits.KILOMETRE), + (UUID.fromString("d0f36763-c11e-46a4-bf6b-e57fb06fd8d8")): Quantities.getQuantity(0.11430643088441233981695, PowerSystemUnits.KILOMETRE), + + /* subnet 2 (will be unchanged) */ + (UUID.fromString("b83b93ed-7468-47c2-aed9-48e554c428c7")): Quantities.getQuantity(1.11308358844200193288058, PowerSystemUnits.KILOMETRE), + (UUID.fromString("571e8b88-dd9d-4542-89ed-b7f37916d775")): Quantities.getQuantity(2.65621973769665467422535, PowerSystemUnits.KILOMETRE), + (UUID.fromString("7197e24f-97cd-4764-ae22-40cdc2f26dd2")): Quantities.getQuantity(1.82710747893781441715269, PowerSystemUnits.KILOMETRE), + ] + + def selectedSubnets = [1] as Set + + when: + def actual = GridController.instance.setElectricalToGeographicalLineLength(selectedSubnets) + def actualLineLengths = actual.rawGrid.lines.stream().collect(Collectors.toMap( + { k -> ((LineInput) k).uuid }, + { v -> ((LineInput) v).length })) + + then: + /* Check each line's length */ + expectedLineLengths.forEach { UUID uuid, Quantity expectedLength -> + def actualLength = actualLineLengths.get(uuid) + + assert Objects.nonNull(actualLength) + assert QuantityUtil.isEquivalentAbs(expectedLength, actualLength) + } + } + + @Unroll + def "A GridController is able to determine the bearing to #bearing degree between #latLon1 and #latLon2 correctly"() { + when: + def actual = GridController.getBearing(latLon1, latLon2) + + then: + actual.unit == PowerSystemUnits.DEGREE_GEOM + Math.abs(bearing - actual.value.doubleValue()) < 1E-2 + + where: + latLon1 | latLon2 || bearing + new LatLon(51.4843281, 7.4116482) | new LatLon(51.493311252841195, 7.4116482) || 0.0 + new LatLon(51.4843281, 7.4116482) | new LatLon(51.490679705804766, 7.421849967564311) || 45.0 + new LatLon(51.4843281, 7.4116482) | new LatLon(51.4843281, 7.426073668191069) || 90.0 + new LatLon(51.4843281, 7.4116482) | new LatLon(51.47797560937315, 7.421847125806461) || 135.0 + new LatLon(51.4843281, 7.4116482) | new LatLon(51.4753449471588, 7.4116482) || 180.0 + new LatLon(51.4843281, 7.4116482) | new LatLon(51.47797560937315, 7.401449274193539) || 225.0 + new LatLon(51.4843281, 7.4116482) | new LatLon(51.4843281, 7.397222731808931) || 270.0 + new LatLon(51.4843281, 7.4116482) | new LatLon(51.490679705804766, 7.401446432435688) || 315.0 + new LatLon(51.4843281, 7.4116482) | new LatLon(53.60884374694267, 7.4116482) || 0.0 + new LatLon(51.4843281, 7.4116482) | new LatLon(52.9608245636982, 9.90581664831644) || 45.0 + new LatLon(51.4843281, 7.4116482) | new LatLon(51.4348704962085, 10.820806952802956) || 90.0 + new LatLon(51.4843281, 7.4116482) | new LatLon(49.958281440399226, 9.746834735372158) || 135.0 + new LatLon(51.4843281, 7.4116482) | new LatLon(49.35981245305733, 7.4116482) || 180.0 + new LatLon(51.4843281, 7.4116482) | new LatLon(49.958281440399226, 5.076461664627842) || 225.0 + new LatLon(51.4843281, 7.4116482) | new LatLon(51.4348704962085, 4.0024894471970445) || 270.0 + new LatLon(51.4843281, 7.4116482) | new LatLon(52.9608245636982, 4.91747975168356) || 315.0 + } + + @Unroll + def "A GridController is able to determine a second position #distance km away from start with a bearing of #bearing degree."() { + when: + def actual = GridController.secondCoordinateWithDistanceAndBearing( + start, + Quantities.getQuantity(distance, PowerSystemUnits.KILOMETRE), + Quantities.getQuantity(bearing, PowerSystemUnits.DEGREE_GEOM) + ) + + then: + Math.abs(expectedPosition.lat - actual.lat) < 1E-6 + Math.abs(expectedPosition.lon - actual.lon) < 1E-6 + + /* The difference between targeted and actual distance is in the order of E-13 in this test. As the above + * mentioned snippet does not really point out, what the bearing is, an actual validation is quite hard. */ + + where: + start | bearing | distance || expectedPosition + new LatLon(51.4843281, 7.4116482) | 0.0 | 1.0 || new LatLon(51.493311252841195, 7.4116482) + new LatLon(51.4843281, 7.4116482) | 45.0 | 1.0 || new LatLon(51.490679705804766, 7.421849967564311) + new LatLon(51.4843281, 7.4116482) | 90.0 | 1.0 || new LatLon(51.4843281, 7.426073668191069) + new LatLon(51.4843281, 7.4116482) | 135.0 | 1.0 || new LatLon(51.47797560937315, 7.421847125806461) + new LatLon(51.4843281, 7.4116482) | 180.0 | 1.0 || new LatLon(51.4753449471588, 7.4116482) + new LatLon(51.4843281, 7.4116482) | 225.0 | 1.0 || new LatLon(51.47797560937315, 7.401449274193539) + new LatLon(51.4843281, 7.4116482) | 270.0 | 1.0 || new LatLon(51.4843281, 7.397222731808931) + new LatLon(51.4843281, 7.4116482) | 315.0 | 1.0 || new LatLon(51.490679705804766, 7.401446432435688) + new LatLon(51.4843281, 7.4116482) | 0.0 | 236.5 || new LatLon(53.60884374694267, 7.4116482) + new LatLon(51.4843281, 7.4116482) | 45.0 | 236.5 || new LatLon(52.9608245636982, 9.90581664831644) + new LatLon(51.4843281, 7.4116482) | 90.0 | 236.5 || new LatLon(51.4348704962085, 10.820806952802956) + new LatLon(51.4843281, 7.4116482) | 135.0 | 236.5 || new LatLon(49.958281440399226, 9.746834735372158) + new LatLon(51.4843281, 7.4116482) | 180.0 | 236.5 || new LatLon(49.35981245305733, 7.4116482) + new LatLon(51.4843281, 7.4116482) | 225.0 | 236.5 || new LatLon(49.958281440399226, 5.076461664627842) + new LatLon(51.4843281, 7.4116482) | 270.0 | 236.5 || new LatLon(51.4348704962085, 4.0024894471970445) + new LatLon(51.4843281, 7.4116482) | 315.0 | 236.5 || new LatLon(52.9608245636982, 4.91747975168356) + } + + def "A GridController is able to update nodes in a grid container"() { + given: + IoController.instance.notifyListener(new ReadGridEvent(testGrid)) + + def updatedNodeA = nodeA.copy().subnet(3).build() + def nodeMapping = new HashMap() + nodeMapping.put(updatedNodeA.uuid, updatedNodeA) + + def expectedNodes = [ + (nodeA.uuid): updatedNodeA, + (nodeB.uuid): nodeB, + (nodeC.uuid): nodeC, + (nodeD.uuid): nodeD, + (nodeE.uuid): nodeE, + (nodeF.uuid): nodeF, + (nodeG.uuid): nodeG + ] + + when: + def actual = GridController.instance.update(testGrid as GridContainer, nodeMapping, [] as Set) + def uuidToNode = actual + .rawGrid + .nodes + .stream() + .collect( + Collectors.toMap( + { node -> ((NodeInput) node).uuid }, { node -> + ((NodeInput) node) + } + ) + ) + + then: + uuidToNode.size() == expectedNodes.size() + uuidToNode.forEach { uuid, node -> + assert expectedNodes.get(uuid) == node + } + } + + def "A GridController is able to update lines in a grid container"() { + given: + def updatedLineBC = lineBC.copy().length(Quantities.getQuantity(3d, PowerSystemUnits.KILOMETRE)).build() + def updatedLineCD = lineCD.copy().length(Quantities.getQuantity(3d, PowerSystemUnits.KILOMETRE)).build() + def updatedLines = new HashSet() + updatedLines.add(updatedLineBC) + updatedLines.add(updatedLineCD) + + def expectedLines = new HashMap() + expectedLines.put(lineBC.uuid, updatedLineBC) + expectedLines.put(lineCD.uuid, updatedLineCD) + expectedLines.put(lineCE.uuid, lineCE) + expectedLines.put(lineEF.uuid, lineEF) + expectedLines.put(lineEG.uuid, lineEG) + + when: + def actual = GridController.instance.update(testGrid as GridContainer, [:] as Map, updatedLines) + def uuidToLine = actual + .rawGrid + .lines + .stream() + .collect( + Collectors.toMap( + { line -> ((LineInput) line).uuid }, { line -> + ((LineInput) line) + } + ) + ) + + then: + uuidToLine.size() == expectedLines.size() + uuidToLine.forEach { uuid, node -> + assert expectedLines.get(uuid) == node + } + } + + def "A GridController is able to traverse a sub grid and adjust the line length"() { + given: + def subGrid = testGrid.subGridTopologyGraph + .vertexSet() + .stream() + .filter({ subgrid -> subgrid.subnet == 2 }) + .findFirst() + .orElseThrow({ -> new RuntimeException("Someone has stolen subnet 2...") }) + + def expectedNodeLocations = [ + (UUID.fromString("36514e92-e6d9-4a7e-85b1-175ec6e27216")): GeoUtils.DEFAULT_GEOMETRY_FACTORY.createPoint(new Coordinate(7.411931, 51.493045)), + (UUID.fromString("78beb137-45e2-43e3-8baa-1c25f0c91616")): GeoUtils.DEFAULT_GEOMETRY_FACTORY.createPoint(new Coordinate(7.411931, 51.493045)), + (UUID.fromString("83586a39-8a55-4b10-a034-7ccfe6a451cb")): GeoUtils.DEFAULT_GEOMETRY_FACTORY.createPoint(new Coordinate(7.406760294855038, 51.51527105298355)), + (UUID.fromString("a0746324-3c74-4a03-9e45-41ae2880ad8d")): GeoUtils.DEFAULT_GEOMETRY_FACTORY.createPoint(new Coordinate(7.398784924455003, 51.53717392365705)), + (UUID.fromString("1e755e89-7bfb-4987-9bee-358ac1892313")): GeoUtils.DEFAULT_GEOMETRY_FACTORY.createPoint(new Coordinate(7.441181742043899, 51.5220222908142)), + (UUID.fromString("7dc3d14b-3536-43b8-96f4-0f43b26854f8")): GeoUtils.DEFAULT_GEOMETRY_FACTORY.createPoint(new Coordinate(7.476588680897974, 51.52638713859204)), + (UUID.fromString("292ea9b1-79f8-4615-bad5-c0d33b17527d")): GeoUtils.DEFAULT_GEOMETRY_FACTORY.createPoint(new Coordinate(7.45062367268736, 51.54369850948029)) + ] + + when: + def actual = GridController.instance.setGeographicalToElectricalLineLength(subGrid) + + then: + "all line length differ less than 1mm and line string as well as nodes do match" + actual.rawGrid.lines.forEach { + def pointA = it.nodeA.geoPosition + def pointB = it.nodeB.geoPosition + def geographicalDistance = GeoUtils.calcHaversine(pointA.y, pointA.x, pointB.y, pointB.x) + + assert QuantityUtil.isEquivalentAbs(geographicalDistance, it.length, 10E-6) + + def lineStringStart = it.geoPosition.startPoint + def lineStringEnd = it.geoPosition.endPoint + + assert ( + lineStringStart.equalsExact(pointA, 1E-6) + && lineStringEnd.equalsExact(pointB, 1E-6) + ) || ( + lineStringStart.equalsExact(pointB, 1E-6) + && lineStringEnd.equalsExact(pointA, 1E-6) + ) + } + + "all nodes are at it's foreseen place" + actual.rawGrid.nodes.forEach { + def expectedPosition = expectedNodeLocations.get(it.uuid) + if (Objects.isNull(expectedPosition)) + throw new RuntimeException("Somebody has stolen the expected position of '" + it + "'") + + assert expectedPosition.equalsExact(it.geoPosition, 1E-6) + } + } } diff --git a/src/test/groovy/edu/ie3/test/common/grids/LengthAdaptionTestGrid.groovy b/src/test/groovy/edu/ie3/test/common/grids/LengthAdaptionTestGrid.groovy new file mode 100644 index 0000000..f24dd63 --- /dev/null +++ b/src/test/groovy/edu/ie3/test/common/grids/LengthAdaptionTestGrid.groovy @@ -0,0 +1,259 @@ +/* + * © 2020. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ +package edu.ie3.test.common.grids + +import edu.ie3.datamodel.models.OperationTime +import edu.ie3.datamodel.models.input.NodeInput +import edu.ie3.datamodel.models.input.OperatorInput +import edu.ie3.datamodel.models.input.connector.LineInput +import edu.ie3.datamodel.models.input.connector.Transformer2WInput +import edu.ie3.datamodel.models.input.connector.type.LineTypeInput +import edu.ie3.datamodel.models.input.connector.type.Transformer2WTypeInput +import edu.ie3.datamodel.models.input.container.GraphicElements +import edu.ie3.datamodel.models.input.container.JointGridContainer +import edu.ie3.datamodel.models.input.container.RawGridElements +import edu.ie3.datamodel.models.input.container.SystemParticipants +import edu.ie3.datamodel.models.input.system.characteristic.OlmCharacteristicInput +import edu.ie3.datamodel.models.voltagelevels.GermanVoltageLevelUtils +import edu.ie3.util.quantities.PowerSystemUnits +import org.locationtech.jts.geom.LineString +import org.locationtech.jts.geom.Point +import org.locationtech.jts.io.geojson.GeoJsonReader +import tech.units.indriya.quantity.Quantities +import tech.units.indriya.unit.Units + +import javax.measure.MetricPrefix + +import static tech.units.indriya.unit.Units.SIEMENS + +/** + * This is a star-topology grid, that is at the first hand used to test the adaption of geo positions in order to meet + * the electrical line length + */ +trait LengthAdaptionTestGrid { + private static final GeoJsonReader geoJsonReader = new GeoJsonReader() + + /* === Nodes === */ + NodeInput nodeA = new NodeInput( + UUID.fromString("36514e92-e6d9-4a7e-85b1-175ec6e27216"), + "node_a", + OperatorInput.NO_OPERATOR_ASSIGNED, + OperationTime.notLimited(), + Quantities.getQuantity(1d, PowerSystemUnits.PU), + true, + geoJsonReader.read("{\"type\":\"Point\",\"coordinates\":[7.411931,51.493045],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4326\"}}}") as Point, + GermanVoltageLevelUtils.MV_20KV, + 1 + ) + NodeInput nodeB = new NodeInput( + UUID.fromString("78beb137-45e2-43e3-8baa-1c25f0c91616"), + "node_b", + OperatorInput.NO_OPERATOR_ASSIGNED, + OperationTime.notLimited(), + Quantities.getQuantity(1d, PowerSystemUnits.PU), + false, + geoJsonReader.read("{\"type\":\"Point\",\"coordinates\":[7.411931,51.493045],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4326\"}}}") as Point, + GermanVoltageLevelUtils.LV, + 2 + ) + NodeInput nodeC = new NodeInput( + UUID.fromString("83586a39-8a55-4b10-a034-7ccfe6a451cb"), + "node_c", + OperatorInput.NO_OPERATOR_ASSIGNED, + OperationTime.notLimited(), + Quantities.getQuantity(1d, PowerSystemUnits.PU), + false, + geoJsonReader.read("{\"type\":\"Point\",\"coordinates\":[7.411728,51.493918],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4326\"}}}") as Point, + GermanVoltageLevelUtils.LV, + 2 + ) + NodeInput nodeD = new NodeInput( + UUID.fromString("a0746324-3c74-4a03-9e45-41ae2880ad8d"), + "node_d", + OperatorInput.NO_OPERATOR_ASSIGNED, + OperationTime.notLimited(), + Quantities.getQuantity(1d, PowerSystemUnits.PU), + false, + geoJsonReader.read("{\"type\":\"Point\",\"coordinates\":[7.411497,51.494553],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4326\"}}}") as Point, + GermanVoltageLevelUtils.LV, + 2 + ) + NodeInput nodeE = new NodeInput( + UUID.fromString("1e755e89-7bfb-4987-9bee-358ac1892313"), + "node_e", + OperatorInput.NO_OPERATOR_ASSIGNED, + OperationTime.notLimited(), + Quantities.getQuantity(1d, PowerSystemUnits.PU), + false, + geoJsonReader.read("{\"type\":\"Point\",\"coordinates\":[7.412838,51.494136],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4326\"}}}") as Point, + GermanVoltageLevelUtils.LV, + 2 + ) + NodeInput nodeF = new NodeInput( + UUID.fromString("7dc3d14b-3536-43b8-96f4-0f43b26854f8"), + "node_f", + OperatorInput.NO_OPERATOR_ASSIGNED, + OperationTime.notLimited(), + Quantities.getQuantity(1d, PowerSystemUnits.PU), + false, + geoJsonReader.read("{\"type\":\"Point\",\"coordinates\":[7.413761,51.494250],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4326\"}}}") as Point, + GermanVoltageLevelUtils.LV, + 2 + ) + NodeInput nodeG = new NodeInput( + UUID.fromString("292ea9b1-79f8-4615-bad5-c0d33b17527d"), + "node_g", + OperatorInput.NO_OPERATOR_ASSIGNED, + OperationTime.notLimited(), + Quantities.getQuantity(1d, PowerSystemUnits.PU), + false, + geoJsonReader.read("{\"type\":\"Point\",\"coordinates\":[7.413123,51.494791],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4326\"}}}") as Point, + GermanVoltageLevelUtils.LV, + 2 + ) + + /* === Transformers === */ + def transformerType = new Transformer2WTypeInput( + UUID.fromString("08559390-d7c0-4427-a2dc-97ba312ae0ac"), + "MS-NS_1", + Quantities.getQuantity(10.078, Units.OHM), + Quantities.getQuantity(23.312, Units.OHM), + Quantities.getQuantity(630.0, PowerSystemUnits.KILOVOLTAMPERE), + Quantities.getQuantity(20.0, PowerSystemUnits.KILOVOLT), + Quantities.getQuantity(0.4, PowerSystemUnits.KILOVOLT), + Quantities.getQuantity(0.0, MetricPrefix.NANO(SIEMENS)), + Quantities.getQuantity(0.0, MetricPrefix.NANO(SIEMENS)), + Quantities.getQuantity(0.5, Units.PERCENT), + Quantities.getQuantity(0.0, PowerSystemUnits.DEGREE_GEOM), + false, + 0, + -10, + 10 + ) + Transformer2WInput transformer = new Transformer2WInput( + UUID.fromString("e691ebc3-fcd6-420d-8fc6-ecf45e88c86c"), + "transformer", + OperatorInput.NO_OPERATOR_ASSIGNED, + OperationTime.notLimited(), + nodeA, + nodeB, + 1, + transformerType, + 0, + false + ) + + /* === Lines === */ + def lineType = new LineTypeInput( + UUID.fromString("3bed3eb3-9790-4874-89b5-a5434d408088"), + "lineType", + Quantities.getQuantity(0.00322, PowerSystemUnits.MICRO_SIEMENS_PER_KILOMETRE), + Quantities.getQuantity(0d, PowerSystemUnits.MICRO_SIEMENS_PER_KILOMETRE), + Quantities.getQuantity(0.437, PowerSystemUnits.OHM_PER_KILOMETRE), + Quantities.getQuantity(0.356, PowerSystemUnits.OHM_PER_KILOMETRE), + Quantities.getQuantity(300d, Units.AMPERE), + Quantities.getQuantity(0.4, PowerSystemUnits.KILOVOLT) + ) + LineInput lineBC = new LineInput( + UUID.fromString("57be16a6-e55a-4177-915c-d44b5dc7c78a"), + "b_c", + OperatorInput.NO_OPERATOR_ASSIGNED, + OperationTime.notLimited(), + nodeB, + nodeC, + 1, + lineType, + Quantities.getQuantity(2.5, PowerSystemUnits.KILOMETRE), + geoJsonReader.read("{\"type\":\"LineString\",\"coordinates\":[[7.411931,51.493045],[7.411728,51.493918]],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4326\"}}}") as LineString, + OlmCharacteristicInput.CONSTANT_CHARACTERISTIC + ) + LineInput lineCD = new LineInput( + UUID.fromString("f7c1d0f8-4464-4243-ab0b-bfb048e8b1a9"), + "c_d", + OperatorInput.NO_OPERATOR_ASSIGNED, + OperationTime.notLimited(), + nodeC, + nodeD, + 1, + lineType, + Quantities.getQuantity(2.5, PowerSystemUnits.KILOMETRE), + geoJsonReader.read("{\"type\":\"LineString\",\"coordinates\":[[7.411728,51.493918],[7.411497,51.494553]],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4326\"}}}") as LineString, + OlmCharacteristicInput.CONSTANT_CHARACTERISTIC + ) + LineInput lineCE = new LineInput( + UUID.fromString("959d901a-8403-4b24-a99f-ea07a7c924a4"), + "c_e", + OperatorInput.NO_OPERATOR_ASSIGNED, + OperationTime.notLimited(), + nodeC, + nodeE, + 1, + lineType, + Quantities.getQuantity(2.5, PowerSystemUnits.KILOMETRE), + geoJsonReader.read("{\"type\":\"LineString\",\"coordinates\":[[7.411728,51.493918],[7.412838,51.494136]],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4326\"}}}") as LineString, + OlmCharacteristicInput.CONSTANT_CHARACTERISTIC + ) + LineInput lineEF = new LineInput( + UUID.fromString("c709c1da-cab2-4ad3-8211-1fa765ec7c45"), + "e_f", + OperatorInput.NO_OPERATOR_ASSIGNED, + OperationTime.notLimited(), + nodeE, + nodeF, + 1, + lineType, + Quantities.getQuantity(2.5, PowerSystemUnits.KILOMETRE), + geoJsonReader.read("{\"type\":\"LineString\",\"coordinates\":[[7.412838,51.494136],[7.413761,51.494250]],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4326\"}}}") as LineString, + OlmCharacteristicInput.CONSTANT_CHARACTERISTIC + ) + LineInput lineEG = new LineInput( + UUID.fromString("2fefea62-53c7-4923-97a0-f4e23b14143f"), + "e_g", + OperatorInput.NO_OPERATOR_ASSIGNED, + OperationTime.notLimited(), + nodeE, + nodeG, + 1, + lineType, + Quantities.getQuantity(2.5, PowerSystemUnits.KILOMETRE), + geoJsonReader.read("{\"type\":\"LineString\",\"coordinates\":[[7.412838,51.494136],[7.413123,51.494791]],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4326\"}}}") as LineString, + OlmCharacteristicInput.CONSTANT_CHARACTERISTIC + ) + + /* Put everything together */ + JointGridContainer testGrid = new JointGridContainer( + "lengthAdaptionTestGrid", + new RawGridElements( + [ + nodeA, + nodeB, + nodeC, + nodeD, + nodeE, + nodeF, + nodeG + ] as Set, + [ + lineBC, + lineCD, + lineCE, + lineEF, + lineEG + ] as Set, + [transformer] as Set, + [] as Set, + [] as Set, + [] as Set + ), + new SystemParticipants( + [] as Set + ), + new GraphicElements( + [] as Set, + [] as Set, + ) + ) +} \ No newline at end of file diff --git a/src/test/resources/grids/geographicalToElectricDistance/line_input.csv b/src/test/resources/grids/geographicalToElectricDistance/line_input.csv new file mode 100644 index 0000000..04046bb --- /dev/null +++ b/src/test/resources/grids/geographicalToElectricDistance/line_input.csv @@ -0,0 +1,6 @@ +uuid,geo_position,id,length,node_a,node_b,olm_characteristic,operates_from,operates_until,operator,parallel_devices,type +57be16a6-e55a-4177-915c-d44b5dc7c78a,"{""type"":""LineString"",""coordinates"":[[7.411931,51.493045],[7.411728,51.493918]],""crs"":{""type"":""name"",""properties"":{""name"":""EPSG:4326""}}}",b_c,2.5,78beb137-45e2-43e3-8baa-1c25f0c91616,83586a39-8a55-4b10-a034-7ccfe6a451cb,olm:{(0.00,1.00)},,,,1,3bed3eb3-9790-4874-89b5-a5434d408088 +f7c1d0f8-4464-4243-ab0b-bfb048e8b1a9,"{""type"":""LineString"",""coordinates"":[[7.411728,51.493918],[7.411497,51.494553]],""crs"":{""type"":""name"",""properties"":{""name"":""EPSG:4326""}}}",c_d,2.5,83586a39-8a55-4b10-a034-7ccfe6a451cb,a0746324-3c74-4a03-9e45-41ae2880ad8d,olm:{(0.00,1.00)},,,,1,3bed3eb3-9790-4874-89b5-a5434d408088 +959d901a-8403-4b24-a99f-ea07a7c924a4,"{""type"":""LineString"",""coordinates"":[[7.411728,51.493918],[7.412838,51.494136]],""crs"":{""type"":""name"",""properties"":{""name"":""EPSG:4326""}}}",c_e,2.5,83586a39-8a55-4b10-a034-7ccfe6a451cb,1e755e89-7bfb-4987-9bee-358ac1892313,olm:{(0.00,1.00)},,,,1,3bed3eb3-9790-4874-89b5-a5434d408088 +c709c1da-cab2-4ad3-8211-1fa765ec7c45,"{""type"":""LineString"",""coordinates"":[[7.412838,51.494136],[7.413761,51.494250]],""crs"":{""type"":""name"",""properties"":{""name"":""EPSG:4326""}}}",e_f,2.5,1e755e89-7bfb-4987-9bee-358ac1892313,7dc3d14b-3536-43b8-96f4-0f43b26854f8,olm:{(0.00,1.00)},,,,1,3bed3eb3-9790-4874-89b5-a5434d408088 +2fefea62-53c7-4923-97a0-f4e23b14143f,"{""type"":""LineString"",""coordinates"":[[7.412838,51.494136],[7.413123,51.494791]],""crs"":{""type"":""name"",""properties"":{""name"":""EPSG:4326""}}}",e_g,2.5,1e755e89-7bfb-4987-9bee-358ac1892313,292ea9b1-79f8-4615-bad5-c0d33b17527d,olm:{(0.00,1.00)},,,,1,3bed3eb3-9790-4874-89b5-a5434d408088 \ No newline at end of file diff --git a/src/test/resources/grids/geographicalToElectricDistance/line_type_input.csv b/src/test/resources/grids/geographicalToElectricDistance/line_type_input.csv new file mode 100644 index 0000000..1333d8b --- /dev/null +++ b/src/test/resources/grids/geographicalToElectricDistance/line_type_input.csv @@ -0,0 +1,2 @@ +uuid,b,g,i_max,id,r,v_rated,x +3bed3eb3-9790-4874-89b5-a5434d408088,0.00322,0.0,300.0,lineType,0.437,0.4,0.356 \ No newline at end of file diff --git a/src/test/resources/grids/geographicalToElectricDistance/node_input.csv b/src/test/resources/grids/geographicalToElectricDistance/node_input.csv new file mode 100644 index 0000000..2c26424 --- /dev/null +++ b/src/test/resources/grids/geographicalToElectricDistance/node_input.csv @@ -0,0 +1,8 @@ +uuid,geo_position,id,operates_from,operates_until,operator,slack,subnet,v_rated,v_target,volt_lvl +36514e92-e6d9-4a7e-85b1-175ec6e27216,"{""type"":""Point"",""coordinates"":[7.411931,51.493045],""crs"":{""type"":""name"",""properties"":{""name"":""EPSG:4326""}}}",node_a,"","","",true,1,20.0,1.0,MV +78beb137-45e2-43e3-8baa-1c25f0c91616,"{""type"":""Point"",""coordinates"":[7.411931,51.493045],""crs"":{""type"":""name"",""properties"":{""name"":""EPSG:4326""}}}",node_b,"","","",false,2,0.4,1.0,LV +83586a39-8a55-4b10-a034-7ccfe6a451cb,"{""type"":""Point"",""coordinates"":[7.411728,51.493918],""crs"":{""type"":""name"",""properties"":{""name"":""EPSG:4326""}}}",node_c,"","","",false,2,0.4,1.0,LV +a0746324-3c74-4a03-9e45-41ae2880ad8d,"{""type"":""Point"",""coordinates"":[7.411497,51.494553],""crs"":{""type"":""name"",""properties"":{""name"":""EPSG:4326""}}}",node_d,"","","",false,2,0.4,1.0,LV +1e755e89-7bfb-4987-9bee-358ac1892313,"{""type"":""Point"",""coordinates"":[7.412838,51.494136],""crs"":{""type"":""name"",""properties"":{""name"":""EPSG:4326""}}}",node_e,"","","",false,2,0.4,1.0,LV +7dc3d14b-3536-43b8-96f4-0f43b26854f8,"{""type"":""Point"",""coordinates"":[7.413761,51.494250],""crs"":{""type"":""name"",""properties"":{""name"":""EPSG:4326""}}}",node_f,"","","",false,2,0.4,1.0,LV +292ea9b1-79f8-4615-bad5-c0d33b17527d,"{""type"":""Point"",""coordinates"":[7.413123,51.494791],""crs"":{""type"":""name"",""properties"":{""name"":""EPSG:4326""}}}",node_g,"","","",false,2,0.4,1.0,LV \ No newline at end of file diff --git a/src/test/resources/grids/geographicalToElectricDistance/transformer_2_w_input.csv b/src/test/resources/grids/geographicalToElectricDistance/transformer_2_w_input.csv new file mode 100644 index 0000000..6c5a4b8 --- /dev/null +++ b/src/test/resources/grids/geographicalToElectricDistance/transformer_2_w_input.csv @@ -0,0 +1,2 @@ +uuid,auto_tap,id,node_a,node_b,operates_from,operates_until,operator,parallel_devices,tap_pos,type +e691ebc3-fcd6-420d-8fc6-ecf45e88c86c,false,transformer,36514e92-e6d9-4a7e-85b1-175ec6e27216,78beb137-45e2-43e3-8baa-1c25f0c91616,,,,1,0,08559390-d7c0-4427-a2dc-97ba312ae0ac \ No newline at end of file diff --git a/src/test/resources/grids/geographicalToElectricDistance/transformer_2_w_type_input.csv b/src/test/resources/grids/geographicalToElectricDistance/transformer_2_w_type_input.csv new file mode 100644 index 0000000..8bcdcac --- /dev/null +++ b/src/test/resources/grids/geographicalToElectricDistance/transformer_2_w_type_input.csv @@ -0,0 +1,2 @@ +uuid,b_m,d_phi,d_v,g_m,id,r_sc,s_rated,tap_max,tap_min,tap_neutr,tap_side,v_rated_a,v_rated_b,x_sc +08559390-d7c0-4427-a2dc-97ba312ae0ac,0.0,0.0,0.5,0.0,MS-NS_1,10.078,630.0,10,-10,0,false,20.0,0.4,23.312 \ No newline at end of file