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 8e2d060..ed9add4 100644 --- a/src/main/java/edu/ie3/netpad/grid/controller/GridController.java +++ b/src/main/java/edu/ie3/netpad/grid/controller/GridController.java @@ -12,7 +12,9 @@ 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.*; +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.system.LoadInput; import edu.ie3.datamodel.models.input.system.PvInput; import edu.ie3.datamodel.models.input.system.StorageInput; @@ -27,10 +29,13 @@ import edu.ie3.netpad.io.event.ReadGridEvent; import edu.ie3.netpad.io.event.SaveGridEvent; import edu.ie3.netpad.map.event.MapEvent; -import edu.ie3.netpad.tool.ToolController; +import edu.ie3.netpad.tool.controller.ToolController; +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 java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -50,11 +55,19 @@ public class GridController { private static final Logger log = LoggerFactory.getLogger(GridController.class); + private static final class InstanceHolder { + static final GridController INSTANCE = new GridController(); + } + + public static GridController getInstance() { + return GridController.InstanceHolder.INSTANCE; + } + private final Map subGrids = new LinkedHashMap<>(); private final ObjectProperty gridUpdateEventProperty = new SimpleObjectProperty<>(); - public GridController() { + private GridController() { // register for updates from iOController IoController.getInstance().registerGridControllerListener(this.ioEventListener()); @@ -68,6 +81,14 @@ public GridController() { (observable, oldValue, gridContextEvent) -> handleGridModifications(gridContextEvent)); } + public boolean isGridLoaded() { + return subGrids.isEmpty(); + } + + public Map getSubGrids() { + return subGrids; + } + private void handleReadGridEvent(ReadGridEvent newValue) { // clear subGrids @@ -189,6 +210,12 @@ private ChangeListener toolEventListener() { } else if (newValue instanceof LayoutGridResponse) { handleReadGridEvent(new ReadGridEvent(((LayoutGridResponse) newValue).getGrid())); log.debug("Received Tool response event"); + } else if (newValue instanceof FixLineLengthRequestEvent) { + FixLineLengthRequestEvent event = (FixLineLengthRequestEvent) newValue; + LineLengthResolutionMode resolutionMode = event.getResolutionMode(); + Set selectedSubnets = event.getSelectedSubnets(); + LineLengthFixer.execute(resolutionMode, selectedSubnets, subGrids) + .ifPresent(this::handleReadGridEvent); } else { throw new RuntimeException("Invalid GridContainer provided!"); } diff --git a/src/main/java/edu/ie3/netpad/grid/info/GridInfoController.java b/src/main/java/edu/ie3/netpad/grid/info/GridInfoController.java index f726598..11aa0dc 100644 --- a/src/main/java/edu/ie3/netpad/grid/info/GridInfoController.java +++ b/src/main/java/edu/ie3/netpad/grid/info/GridInfoController.java @@ -7,6 +7,7 @@ import static javafx.scene.control.CheckBoxTreeItem.checkBoxSelectionChangedEvent; +import edu.ie3.datamodel.models.input.container.SubGridContainer; import edu.ie3.datamodel.models.voltagelevels.VoltageLevel; import edu.ie3.netpad.grid.event.GridEvent; import edu.ie3.netpad.grid.event.GridEventListener; @@ -40,7 +41,6 @@ public class GridInfoController implements GridEventListener { private final ChangeListener gridEventListener = ListenerUtil.createGridEventListener(this); - private final HashMap, UUID> subGridCheckBoxes = new HashMap<>(); private final ObjectProperty gridInfoEventProperty = new SimpleObjectProperty<>(); @@ -69,40 +69,60 @@ private void handleReplaceGridEvent(ReplaceGridEvent gridEvent) { selectedGridCheckTreeView.setRoot(root); - Map> voltageLvls = new HashMap<>(); - - gridEvent - .getSubGrids() - .forEach( - (uuid, subGrid) -> { - CheckBoxTreeItem voltageLvlChkBox = - Optional.ofNullable(voltageLvls.get(subGrid.getPredominantVoltageLevel())) - .orElseGet( - () -> - new CheckBoxTreeItem<>(subGrid.getPredominantVoltageLevel().getId())); - - voltageLvlChkBox.setSelected(true); - - CheckBoxTreeItem checkBoxTreeItem = - new CheckBoxTreeItem<>(Integer.toString(subGrid.getSubnet())); - - checkBoxTreeItem.setSelected(true); - checkBoxTreeItem.addEventHandler( - checkBoxSelectionChangedEvent(), - (EventHandler>) - event -> { - CheckBoxTreeItem chk = event.getTreeItem(); - UUID subGridUUID = subGridCheckBoxes.get(chk); - notifyListener(new GridInfoEvent(subGridUUID, chk.isSelected())); - }); - - voltageLvlChkBox.getChildren().add(checkBoxTreeItem); + addToRootTreeItem(root, gridEvent.getSubGrids()); + } - voltageLvls.put(subGrid.getPredominantVoltageLevel(), voltageLvlChkBox); - subGridCheckBoxes.put(checkBoxTreeItem, uuid); - }); + /** + * Adds {@link CheckBoxTreeItem}s for each voltage level + * + * @param root Root entry of the check box tree + * @param subGridContainerMap Mapping from uuid to sub grid container + */ + public void addToRootTreeItem( + CheckBoxTreeItem root, Map subGridContainerMap) { + Collection> treeItems = buildTreeItems(subGridContainerMap); + treeItems.forEach(voltageLvlChkBox -> root.getChildren().add(voltageLvlChkBox)); + } - voltageLvls.values().forEach(voltageLvlChkBox -> root.getChildren().add(voltageLvlChkBox)); + /** + * Builds all the {@link CheckBoxTreeItem}s for each voltage level + * + * @param subGridContainerMap Mapping from uuid to sub grid container + * @return A collection of nested tree items + */ + private Collection> buildTreeItems( + Map subGridContainerMap) { + Map> voltLvlToTreeItem = new HashMap<>(); + HashMap, UUID> subGridCheckBoxes = new HashMap<>(); + + subGridContainerMap.forEach( + (uuid, subGrid) -> { + CheckBoxTreeItem voltageLvlChkBox = + Optional.ofNullable(voltLvlToTreeItem.get(subGrid.getPredominantVoltageLevel())) + .orElseGet( + () -> new CheckBoxTreeItem<>(subGrid.getPredominantVoltageLevel().getId())); + + voltageLvlChkBox.setSelected(true); + + CheckBoxTreeItem checkBoxTreeItem = + new CheckBoxTreeItem<>(Integer.toString(subGrid.getSubnet())); + + checkBoxTreeItem.setSelected(true); + checkBoxTreeItem.addEventHandler( + checkBoxSelectionChangedEvent(), + (EventHandler>) + event -> { + CheckBoxTreeItem chk = event.getTreeItem(); + UUID subGridUUID = subGridCheckBoxes.get(chk); + notifyListener(new GridInfoEvent(subGridUUID, chk.isSelected())); + }); + + voltageLvlChkBox.getChildren().add(checkBoxTreeItem); + + voltLvlToTreeItem.put(subGrid.getPredominantVoltageLevel(), voltageLvlChkBox); + subGridCheckBoxes.put(checkBoxTreeItem, uuid); + }); + return voltLvlToTreeItem.values(); } public ObjectProperty gridInfoEvents() { diff --git a/src/main/java/edu/ie3/netpad/main/controller/MainController.java b/src/main/java/edu/ie3/netpad/main/controller/MainController.java index 948ba87..0cf8526 100644 --- a/src/main/java/edu/ie3/netpad/main/controller/MainController.java +++ b/src/main/java/edu/ie3/netpad/main/controller/MainController.java @@ -38,8 +38,6 @@ public class MainController implements GridEventListener { @FXML private MainMenuBarController mainMenuBarController; - private final GridController gridController; - private final ChangeListener gridEventListener; private boolean gridInfoActive = false; @@ -48,7 +46,6 @@ public class MainController implements GridEventListener { private final DoubleProperty gridInfoDividerPosition = new SimpleDoubleProperty(0.25); public MainController() { - this.gridController = new GridController(); this.gridEventListener = ListenerUtil.createGridEventListener(this); } @@ -109,10 +106,12 @@ public void postInitialization() { gridInfoController.gridEventListener(), mainMenuBarController.getToolMenuController().gridEventListener(), this.gridEventListener()) - .forEach(gridController.gridUpdateEvents()::addListener); + .forEach(GridController.getInstance().gridUpdateEvents()::addListener); /* register listener that receive updates from map controller (e.g. dragged nodes) */ - mapController.mapUpdateEvents().addListener(gridController.gridMapEventListener()); + mapController + .mapUpdateEvents() + .addListener(GridController.getInstance().gridMapEventListener()); /* register listener that receive updates from gridInfoController*/ gridInfoController.gridInfoEvents().addListener(mapController.gridInfoEventListener()); diff --git a/src/main/java/edu/ie3/netpad/menu/ToolMenuController.java b/src/main/java/edu/ie3/netpad/menu/ToolMenuController.java index 58eaf72..5858376 100644 --- a/src/main/java/edu/ie3/netpad/menu/ToolMenuController.java +++ b/src/main/java/edu/ie3/netpad/menu/ToolMenuController.java @@ -8,7 +8,8 @@ import edu.ie3.netpad.grid.event.GridEvent; import edu.ie3.netpad.grid.event.GridEventListener; import edu.ie3.netpad.grid.event.ReplaceGridEvent; -import edu.ie3.netpad.tool.ToolController; +import edu.ie3.netpad.tool.controller.ToolController; +import edu.ie3.netpad.tool.controller.ToolDialogs; import edu.ie3.netpad.util.ListenerUtil; import javafx.beans.value.ChangeListener; import javafx.fxml.FXML; @@ -30,10 +31,16 @@ public class ToolMenuController implements GridEventListener { ListenerUtil.createGridEventListener(this); @FXML public MenuItem layoutGridItem; + @FXML private MenuItem fixLineLengthItem; @FXML public void initialize() { layoutGridItem.setOnAction(event -> ToolController.getInstance().layoutGrid()); + fixLineLengthItem.setOnAction( + event -> + ToolDialogs.fixLineLengthDialog() + .showAndWait() + .ifPresent(data -> ToolController.getInstance().fixLineLength(data))); } @Override diff --git a/src/main/java/edu/ie3/netpad/tool/ToolController.java b/src/main/java/edu/ie3/netpad/tool/controller/ToolController.java similarity index 81% rename from src/main/java/edu/ie3/netpad/tool/ToolController.java rename to src/main/java/edu/ie3/netpad/tool/controller/ToolController.java index 20d0c43..7ad20dc 100644 --- a/src/main/java/edu/ie3/netpad/tool/ToolController.java +++ b/src/main/java/edu/ie3/netpad/tool/controller/ToolController.java @@ -3,10 +3,12 @@ * Institute of Energy Systems, Energy Efficiency and Energy Economics, * Research group Distribution grid planning and operation */ -package edu.ie3.netpad.tool; +package edu.ie3.netpad.tool.controller; import edu.ie3.datamodel.models.input.container.JointGridContainer; import edu.ie3.netpad.exception.GridControllerListenerException; +import edu.ie3.netpad.grid.controller.GridController; +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; @@ -22,12 +24,21 @@ * @since 04.06.20 */ public class ToolController { - private static final ObjectProperty toolEventProperty = new SimpleObjectProperty<>(); + + private static final class InstanceHolder { + static final ToolController INSTANCE = new ToolController(); + } + + public static ToolController getInstance() { + return ToolController.InstanceHolder.INSTANCE; + } + private boolean initialized; - public void layoutGrid() { + private ToolController() {} + public void layoutGrid() { // issue an event that we want to layout the grid // the listener provided is a one-shot instance which fires when the // gridController returns @@ -45,14 +56,15 @@ public void layoutGrid() { })); } - private static final class InstanceHolder { - static final ToolController INSTANCE = new ToolController(); - } - - private ToolController() {} - - public static ToolController getInstance() { - return ToolController.InstanceHolder.INSTANCE; + /** + * Ask the {@link GridController} to fix discrepancy between electrical and geographical line + * length + * + * @param data user preferences for the given operation + */ + public void fixLineLength(ToolDialogs.FixLineLengthData data) { + notifyListener( + new FixLineLengthRequestEvent(data.getResolutionMode(), data.getAffectedSubnets())); } public void registerGridControllerListener(ChangeListener listener) { diff --git a/src/main/java/edu/ie3/netpad/tool/controller/ToolDialogs.java b/src/main/java/edu/ie3/netpad/tool/controller/ToolDialogs.java new file mode 100644 index 0000000..0578493 --- /dev/null +++ b/src/main/java/edu/ie3/netpad/tool/controller/ToolDialogs.java @@ -0,0 +1,218 @@ +/* + * © 2020. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation +*/ +package edu.ie3.netpad.tool.controller; + +import static edu.ie3.netpad.tool.grid.LineLengthResolutionMode.ELECTRICAL; +import static edu.ie3.netpad.tool.grid.LineLengthResolutionMode.GEOGRAPHICAL; +import static javafx.scene.control.CheckBoxTreeItem.checkBoxSelectionChangedEvent; + +import edu.ie3.datamodel.models.input.container.SubGridContainer; +import edu.ie3.datamodel.models.voltagelevels.VoltageLevel; +import edu.ie3.netpad.exception.NetPadPlusPlusException; +import edu.ie3.netpad.grid.controller.GridController; +import edu.ie3.netpad.tool.grid.LineLengthResolutionMode; +import java.util.*; +import java.util.stream.Collectors; +import javafx.beans.binding.Bindings; +import javafx.geometry.Insets; +import javafx.scene.control.*; +import javafx.scene.layout.GridPane; +import javafx.scene.text.Text; +import org.controlsfx.control.CheckTreeView; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ToolDialogs { + private static final Logger logger = LoggerFactory.getLogger(ToolDialogs.class); + + protected ToolDialogs() { + throw new IllegalStateException("Don't instantiate a class with only static methods"); + } + + public static Dialog fixLineLengthDialog() { + GridPane gridPane = new GridPane(); + gridPane.setHgap(10); + gridPane.setVgap(10); + gridPane.setPadding(new Insets(20, 150, 10, 10)); + + /* Toggle between modes */ + Label modeLbl = new Label("Resolution mode: "); + ToggleGroup tglGrp = new ToggleGroup(); + 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); + gridPane.addRow(0, modeLbl, electricalBtn); + gridPane.add(geographicalBtn, 1, 1); + + /* Select a subnet first */ + Label subnetLbl = new Label("Subnets: "); + Set selectedSubnets = new HashSet<>(Collections.emptySet()); + if (GridController.getInstance().isGridLoaded()) { + /* There is no grid loaded. Only display a hint on what to do. */ + Text hintTxt = new Text("Load a grid first."); + gridPane.addRow(2, subnetLbl, hintTxt); + } else { + /* List all available sub grids */ + CheckBoxTreeItem root = new CheckBoxTreeItem<>("Subnets"); + root.setExpanded(true); + root.setSelected(true); + + addToRootTreeItem( + root, + GridController.getInstance().getSubGrids().entrySet().parallelStream() + .collect( + Collectors.toMap(Map.Entry::getKey, v -> v.getValue().getSubGridContainer())), + selectedSubnets); + + CheckTreeView treeView = new CheckTreeView<>(root); + ScrollPane scrollPane = new ScrollPane(treeView); + gridPane.addRow(2, subnetLbl, scrollPane); + } + + /* Setting up the dialog pane */ + DialogPane dialogPane = new DialogPane(); + dialogPane.setContent(gridPane); + dialogPane.getButtonTypes().addAll(ButtonType.OK, ButtonType.CANCEL); + + /* Disable the ok button, if there is no grid loaded */ + dialogPane + .lookupButton(ButtonType.OK) + .disableProperty() + .bind(Bindings.createBooleanBinding(() -> GridController.getInstance().isGridLoaded())); + + Dialog dialog = new Dialog<>(); + dialog.setTitle("Resolve line length discrepancy"); + dialog.setDialogPane(dialogPane); + + /* What will happen, if someone pushes a button? */ + dialog.setResultConverter( + buttonType -> { + if (buttonType == ButtonType.OK) { + /* Which mode is chosen? */ + LineLengthResolutionMode resolutionMode = + (LineLengthResolutionMode) tglGrp.getSelectedToggle().getUserData(); + + return new FixLineLengthData(resolutionMode, selectedSubnets); + } else if (buttonType == ButtonType.CANCEL) return null; + else { + throw new NetPadPlusPlusException( + "Invalid button type " + buttonType + " in csv I/O dialog."); + } + }); + + return dialog; + } + + /** + * Adds {@link CheckBoxTreeItem}s for each voltage level + * + * @param root Root entry of the check box tree + * @param subGridContainerMap Mapping from uuid to sub grid container + * @param selectedSubnets Set of selected subnet items + */ + public static void addToRootTreeItem( + CheckBoxTreeItem root, + Map subGridContainerMap, + Set selectedSubnets) { + Collection> treeItems = + buildTreeItems(subGridContainerMap, selectedSubnets); + treeItems.forEach(voltageLvlChkBox -> root.getChildren().add(voltageLvlChkBox)); + } + + /** + * Builds all the {@link CheckBoxTreeItem}s for each voltage level + * + * @param subGridContainerMap Mapping from uuid to sub grid container + * @param selectedSubnets Set of selected subnet items + * @return A collection of nested tree items + */ + private static Collection> buildTreeItems( + Map subGridContainerMap, Set selectedSubnets) { + Map> voltLvlToTreeItem = new HashMap<>(); + + subGridContainerMap.forEach( + (uuid, subGrid) -> { + CheckBoxTreeItem voltageLvlChkBox = + Optional.ofNullable(voltLvlToTreeItem.get(subGrid.getPredominantVoltageLevel())) + .orElseGet( + () -> new CheckBoxTreeItem<>(subGrid.getPredominantVoltageLevel().getId())); + + voltageLvlChkBox.setSelected(true); + + /* Create each single subnet's checkbox */ + CheckBoxTreeItem checkBoxTreeItem = + new CheckBoxTreeItem<>(Integer.toString(subGrid.getSubnet())); + checkBoxTreeItem.addEventHandler( + checkBoxSelectionChangedEvent(), + event -> { + CheckBoxTreeItem chk = event.getTreeItem(); + try { + int subnetNumber = Integer.parseInt((String) chk.getValue()); + if (chk.isSelected()) { + selectedSubnets.add(subnetNumber); + } else { + selectedSubnets.remove(subnetNumber); + } + } catch (NumberFormatException nfe) { + logger.error("Unable to parse '{}' to integer.", chk.getValue()); + } + }); + checkBoxTreeItem.setSelected(true); + + voltageLvlChkBox.getChildren().add(checkBoxTreeItem); + + voltLvlToTreeItem.put(subGrid.getPredominantVoltageLevel(), voltageLvlChkBox); + }); + return voltLvlToTreeItem.values(); + } + + public static class FixLineLengthData { + private final LineLengthResolutionMode resolutionMode; + private final Set affectedSubnets; + + public FixLineLengthData( + LineLengthResolutionMode resolutionMode, Set affectedSubnets) { + this.resolutionMode = resolutionMode; + this.affectedSubnets = affectedSubnets; + } + + public LineLengthResolutionMode getResolutionMode() { + return resolutionMode; + } + + public Set getAffectedSubnets() { + return affectedSubnets; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + FixLineLengthData that = (FixLineLengthData) o; + return resolutionMode == that.resolutionMode && affectedSubnets.equals(that.affectedSubnets); + } + + @Override + public int hashCode() { + return Objects.hash(resolutionMode, affectedSubnets); + } + + @Override + public String toString() { + return "FixLineLengthData{" + + "resolutionMode=" + + resolutionMode + + ", affectedSubnets=" + + affectedSubnets + + '}'; + } + } +} diff --git a/src/main/java/edu/ie3/netpad/tool/event/FixLineLengthRequestEvent.java b/src/main/java/edu/ie3/netpad/tool/event/FixLineLengthRequestEvent.java new file mode 100644 index 0000000..e195943 --- /dev/null +++ b/src/main/java/edu/ie3/netpad/tool/event/FixLineLengthRequestEvent.java @@ -0,0 +1,56 @@ +/* + * © 2020. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation +*/ +package edu.ie3.netpad.tool.event; + +import edu.ie3.netpad.tool.grid.LineLengthResolutionMode; +import java.util.Objects; +import java.util.Set; + +/** + * An event, that is sent from {@link edu.ie3.netpad.tool.controller.ToolController} to {@link + * edu.ie3.netpad.grid.controller.GridController} to request a fixing of line length discrepancy + */ +public class FixLineLengthRequestEvent implements ToolEvent { + private final LineLengthResolutionMode resolutionMode; + private final Set selectedSubnets; + + public FixLineLengthRequestEvent( + LineLengthResolutionMode resolutionMode, Set selectedSubnets) { + this.resolutionMode = resolutionMode; + this.selectedSubnets = selectedSubnets; + } + + public LineLengthResolutionMode getResolutionMode() { + return resolutionMode; + } + + public Set getSelectedSubnets() { + return selectedSubnets; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + FixLineLengthRequestEvent that = (FixLineLengthRequestEvent) o; + return resolutionMode == that.resolutionMode && selectedSubnets.equals(that.selectedSubnets); + } + + @Override + public int hashCode() { + return Objects.hash(resolutionMode, selectedSubnets); + } + + @Override + public String toString() { + return "FixLineLengthRequestEvent{" + + "mode=" + + resolutionMode + + ", selectedSubnets=" + + selectedSubnets + + '}'; + } +} diff --git a/src/main/java/edu/ie3/netpad/tool/grid/LineLengthFixer.java b/src/main/java/edu/ie3/netpad/tool/grid/LineLengthFixer.java new file mode 100644 index 0000000..c133ab8 --- /dev/null +++ b/src/main/java/edu/ie3/netpad/tool/grid/LineLengthFixer.java @@ -0,0 +1,163 @@ +/* + * © 2021. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation +*/ +package edu.ie3.netpad.tool.grid; + +import edu.ie3.datamodel.models.input.connector.LineInput; +import edu.ie3.datamodel.models.input.container.JointGridContainer; +import edu.ie3.datamodel.models.input.container.RawGridElements; +import edu.ie3.datamodel.models.input.container.SubGridContainer; +import edu.ie3.datamodel.utils.ContainerUtils; +import edu.ie3.datamodel.utils.GridAndGeoUtils; +import edu.ie3.netpad.grid.GridModel; +import edu.ie3.netpad.io.event.ReadGridEvent; +import edu.ie3.netpad.tool.controller.ToolDialogs; +import edu.ie3.util.geo.GeoUtils; +import java.util.*; +import java.util.stream.Collectors; +import javax.measure.quantity.Length; +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; + +public class LineLengthFixer { + + private static final Logger log = LoggerFactory.getLogger(LineLengthFixer.class); + + private LineLengthFixer() { + throw new IllegalStateException("Utility class"); + } + + /** + * 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 static Optional execute( + LineLengthResolutionMode resolutionMode, + Set selectedSubnets, + Map allSubGrids) { + JointGridContainer updatedGrid; + + /* Act depending on the chosen resolution mode */ + switch (resolutionMode) { + case GEOGRAPHICAL: + updatedGrid = setElectricalToGeographicalLineLength(allSubGrids, selectedSubnets); + break; + case ELECTRICAL: + /* TODO CK: Figure out, what to do here */ + default: + log.error("Unknown resolution mode '{}'", resolutionMode); + return Optional.empty(); + } + + /* Build a new event and inform the listeners about the "new" / adapted grid model */ + return Optional.of(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 + */ + private static JointGridContainer setElectricalToGeographicalLineLength( + Map subGrids, 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(LineLengthFixer::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 method should be transferred to PowerSystemDataModel + */ + @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); + } +} diff --git a/src/main/java/edu/ie3/netpad/tool/grid/LineLengthResolutionMode.java b/src/main/java/edu/ie3/netpad/tool/grid/LineLengthResolutionMode.java new file mode 100644 index 0000000..b26f13e --- /dev/null +++ b/src/main/java/edu/ie3/netpad/tool/grid/LineLengthResolutionMode.java @@ -0,0 +1,11 @@ +/* + * © 2020. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation +*/ +package edu.ie3.netpad.tool.grid; + +public enum LineLengthResolutionMode { + GEOGRAPHICAL, + ELECTRICAL +} diff --git a/src/main/java/edu/ie3/netpad/util/SampleGridFactory.java b/src/main/java/edu/ie3/netpad/util/SampleGridFactory.java index fad77da..01f845c 100644 --- a/src/main/java/edu/ie3/netpad/util/SampleGridFactory.java +++ b/src/main/java/edu/ie3/netpad/util/SampleGridFactory.java @@ -266,7 +266,7 @@ private static RawGridElements jointSampleRawGridElements() throws ParseExceptio GermanVoltageLevelUtils.LV, 1); - LineTypeInput lv_lineType = + LineTypeInput lvLineType = new LineTypeInput( UUID.fromString("3bed3eb3-9790-4874-89b5-a5434d408088"), "lineType_AtoB", @@ -286,35 +286,35 @@ private static RawGridElements jointSampleRawGridElements() throws ParseExceptio nodeA, nodeB, 1, - lv_lineType, + lvLineType, GridAndGeoUtils.distanceBetweenNodes(nodeA, nodeB), GridAndGeoUtils.buildSafeLineStringBetweenNodes(nodeA, nodeB), OlmCharacteristicInput.CONSTANT_CHARACTERISTIC); LineInput lineAC = new LineInput( - UUID.fromString("93ec3bcf-1777-4d38-af67-0bf7c9fa73c7"), + UUID.fromString("d0f36763-c11e-46a4-bf6b-e57fb06fd8d8"), "lineAtoC", OperatorInput.NO_OPERATOR_ASSIGNED, OperationTime.notLimited(), nodeA, nodeC, 1, - lv_lineType, + lvLineType, GridAndGeoUtils.distanceBetweenNodes(nodeA, nodeC), GridAndGeoUtils.buildSafeLineStringBetweenNodes(nodeA, nodeC), OlmCharacteristicInput.CONSTANT_CHARACTERISTIC); LineInput lineBC = new LineInput( - UUID.fromString("94ec3bcf-1777-4d38-af67-0bf7c9fa73c7"), + UUID.fromString("4dd1bde7-0ec9-4540-ac9e-008bc5f883ba"), "lineBtoC", OperatorInput.NO_OPERATOR_ASSIGNED, OperationTime.notLimited(), nodeB, nodeC, 1, - lv_lineType, + lvLineType, GridAndGeoUtils.distanceBetweenNodes(nodeB, nodeC), GridAndGeoUtils.buildSafeLineStringBetweenNodes(nodeB, nodeC), OlmCharacteristicInput.CONSTANT_CHARACTERISTIC); @@ -334,7 +334,7 @@ private static RawGridElements jointSampleRawGridElements() throws ParseExceptio NodeInput nodeE = new NodeInput( - UUID.fromString("10aec636-791b-45aa-b981-b14edf171c4c"), + UUID.fromString("35dc7348-2602-4c4a-99fb-1d1bbc76ec6a"), "nodeE", OperatorInput.NO_OPERATOR_ASSIGNED, OperationTime.notLimited(), @@ -348,7 +348,7 @@ private static RawGridElements jointSampleRawGridElements() throws ParseExceptio NodeInput nodeF = new NodeInput( - UUID.fromString("11aec636-791b-45aa-b981-b14edf171c4c"), + UUID.fromString("211ccf5b-58ee-4c3c-8165-852b0c9255ef"), "nodeF", OperatorInput.NO_OPERATOR_ASSIGNED, OperationTime.notLimited(), @@ -362,7 +362,7 @@ private static RawGridElements jointSampleRawGridElements() throws ParseExceptio NodeInput nodeG = new NodeInput( - UUID.fromString("12aec637-791b-45aa-b981-b14edf171c4c"), + UUID.fromString("30c48ca2-9cfe-423b-bf8c-3adbc6ab496b"), "nodeG", OperatorInput.NO_OPERATOR_ASSIGNED, OperationTime.notLimited(), @@ -372,7 +372,7 @@ private static RawGridElements jointSampleRawGridElements() throws ParseExceptio GermanVoltageLevelUtils.EHV_220KV, 4); - LineTypeInput mv_lineType = + LineTypeInput mvLineType = new LineTypeInput( UUID.fromString("4bed3eb3-9790-4874-89b5-a5434d408088"), "lineType_AtoB", @@ -385,49 +385,49 @@ private static RawGridElements jointSampleRawGridElements() throws ParseExceptio LineInput lineDE = new LineInput( - UUID.fromString("99ec3bcf-1777-4d38-af67-0bf7c9fa73c7"), + UUID.fromString("571e8b88-dd9d-4542-89ed-b7f37916d775"), "lineDtoE", OperatorInput.NO_OPERATOR_ASSIGNED, OperationTime.notLimited(), nodeD, nodeE, 1, - mv_lineType, + mvLineType, GridAndGeoUtils.distanceBetweenNodes(nodeD, nodeE), GridAndGeoUtils.buildSafeLineStringBetweenNodes(nodeD, nodeE), OlmCharacteristicInput.CONSTANT_CHARACTERISTIC); LineInput lineEF = new LineInput( - UUID.fromString("99fc3bcf-1777-4d38-af67-0bf7c9fa73c7"), + UUID.fromString("b83b93ed-7468-47c2-aed9-48e554c428c7"), "lineEtoF", OperatorInput.NO_OPERATOR_ASSIGNED, OperationTime.notLimited(), nodeE, nodeF, 1, - mv_lineType, + mvLineType, GridAndGeoUtils.distanceBetweenNodes(nodeE, nodeF), GridAndGeoUtils.buildSafeLineStringBetweenNodes(nodeE, nodeF), OlmCharacteristicInput.CONSTANT_CHARACTERISTIC); LineInput lineDF = new LineInput( - UUID.fromString("60ec3bcf-1777-4d38-af67-0bf7c9fa73c7"), + UUID.fromString("7197e24f-97cd-4764-ae22-40cdc2f26dd2"), "lineDtoF", OperatorInput.NO_OPERATOR_ASSIGNED, OperationTime.notLimited(), nodeD, nodeF, 1, - mv_lineType, + mvLineType, GridAndGeoUtils.distanceBetweenNodes(nodeD, nodeF), GridAndGeoUtils.buildSafeLineStringBetweenNodes(nodeD, nodeF), OlmCharacteristicInput.CONSTANT_CHARACTERISTIC); // transformers - Transformer2WTypeInput transformerType_LV_MV_10KV = + Transformer2WTypeInput transformerTypeLvMv10Kv = new Transformer2WTypeInput( UUID.fromString("08559390-d7c0-4427-a2dc-97ba312ae0ac"), "MS-NS_1", @@ -454,20 +454,20 @@ private static RawGridElements jointSampleRawGridElements() throws ParseExceptio nodeD, nodeA, 1, - transformerType_LV_MV_10KV, + transformerTypeLvMv10Kv, 0, false); Transformer2WInput transformerGtoD = new Transformer2WInput( - UUID.fromString("58257de7-f297-4d9b-a5e4-b662c058c655"), + UUID.fromString("d75b93d0-5d8d-43e5-81a1-8cef01aec56d"), "transformerAtoD", OperatorInput.NO_OPERATOR_ASSIGNED, OperationTime.notLimited(), nodeG, nodeD, 1, - transformerType_LV_MV_10KV, + transformerTypeLvMv10Kv, 0, false); diff --git a/src/main/resources/edu/ie3/netpad/menu/FileMenu.fxml b/src/main/resources/edu/ie3/netpad/menu/FileMenu.fxml index 747e2ed..9adf6fa 100644 --- a/src/main/resources/edu/ie3/netpad/menu/FileMenu.fxml +++ b/src/main/resources/edu/ie3/netpad/menu/FileMenu.fxml @@ -1,45 +1,37 @@ - - - - + - - + - + - + - + + + - - - + - + - + + + - - - + - + - - - - - - + + diff --git a/src/main/resources/edu/ie3/netpad/menu/ToolMenu.fxml b/src/main/resources/edu/ie3/netpad/menu/ToolMenu.fxml index 27f39c7..fc38878 100644 --- a/src/main/resources/edu/ie3/netpad/menu/ToolMenu.fxml +++ b/src/main/resources/edu/ie3/netpad/menu/ToolMenu.fxml @@ -1,11 +1,10 @@ - - + \ No newline at end of file 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 3e2e5aa..e42caee 100644 --- a/src/test/groovy/edu/ie3/netpad/grid/controller/GridControllerTest.groovy +++ b/src/test/groovy/edu/ie3/netpad/grid/controller/GridControllerTest.groovy @@ -5,48 +5,47 @@ */ package edu.ie3.netpad.grid.controller +import edu.ie3.netpad.test.common.SampleData import edu.ie3.netpad.util.SampleGridFactory import spock.lang.Specification - -class GridControllerTest extends Specification { +class GridControllerTest extends Specification implements SampleData { def "A GridController should find the correct grid based from a valid mapping of grid id and a set of entity ids"() { given: - def gridController = new GridController() - def validEntityList = [ - UUID.fromString("bd837a25-58f3-44ac-aa90-c6b6e3cd91b2"), - UUID.fromString("60ec3bcf-1777-4d38-af67-0bf7c9fa73c7"), - UUID.fromString("4ca90220-74c2-4369-9afa-a18bf068840d"), - UUID.fromString("47d29df0-ba2d-4d23-8e75-c82229c5c758"), - UUID.fromString("94ec3bcf-1777-4d38-af67-0bf7c9fa73c7"), - UUID.fromString("93ec3bcf-1777-4d38-af67-0bf7c9fa73c7"), - UUID.fromString("06b58276-8350-40fb-86c0-2414aa4a0452"), - UUID.fromString("58247de7-e297-4d9b-a5e4-b662c058c655"), - UUID.fromString("58257de7-f297-4d9b-a5e4-b662c058c655"), - UUID.fromString("99ec3bcf-1777-4d38-af67-0bf7c9fa73c7"), - UUID.fromString("11aec636-791b-45aa-b981-b14edf171c4c"), - UUID.fromString("09aec636-791b-45aa-b981-b14edf171c4c"), - UUID.fromString("99fc3bcf-1777-4d38-af67-0bf7c9fa73c7"), UUID.fromString("d56f15b7-8293-4b98-b5bd-58f6273ce229"), - UUID.fromString("92ec3bcf-1777-4d38-af67-0bf7c9fa73c7"), UUID.fromString("eaf77f7e-9001-479f-94ca-7fb657766f5f"), + UUID.fromString("d56f15b7-8293-4b98-b5bd-58f6273ce239"), UUID.fromString("eaf77f7e-9001-479f-94ca-7fb657766f6f"), - UUID.fromString("12aec637-791b-45aa-b981-b14edf171c4c"), - UUID.fromString("10aec636-791b-45aa-b981-b14edf171c4c"), - UUID.fromString("d56f15b7-8293-4b98-b5bd-58f6273ce239")] as Set + UUID.fromString("06b58276-8350-40fb-86c0-2414aa4a0452"), + UUID.fromString("4ca90220-74c2-4369-9afa-a18bf068840d"), + UUID.fromString("47d29df0-ba2d-4d23-8e75-c82229c5c758"), + UUID.fromString("bd837a25-58f3-44ac-aa90-c6b6e3cd91b2"), + UUID.fromString("92ec3bcf-1777-4d38-af67-0bf7c9fa73c7"), + UUID.fromString("d0f36763-c11e-46a4-bf6b-e57fb06fd8d8"), + UUID.fromString("4dd1bde7-0ec9-4540-ac9e-008bc5f883ba"), + UUID.fromString("09aec636-791b-45aa-b981-b14edf171c4c"), + UUID.fromString("35dc7348-2602-4c4a-99fb-1d1bbc76ec6a"), + UUID.fromString("211ccf5b-58ee-4c3c-8165-852b0c9255ef"), + UUID.fromString("30c48ca2-9cfe-423b-bf8c-3adbc6ab496b"), + UUID.fromString("571e8b88-dd9d-4542-89ed-b7f37916d775"), + UUID.fromString("b83b93ed-7468-47c2-aed9-48e554c428c7"), + UUID.fromString("7197e24f-97cd-4764-ae22-40cdc2f26dd2"), + UUID.fromString("58247de7-e297-4d9b-a5e4-b662c058c655"), + UUID.fromString("d75b93d0-5d8d-43e5-81a1-8cef01aec56d") + ] as Set def gridUuid = UUID.randomUUID() - def validMapping = [(gridUuid) : validEntityList] + def validMapping = [(gridUuid): validEntityList] def sampleGrid = SampleGridFactory.sampleJointGrid() expect: sampleGrid.allEntitiesAsList().size() == validEntityList.size() - def subGridUuid = gridController.findSubGridUuid(validMapping, sampleGrid) + def subGridUuid = GridController.instance.findSubGridUuid(validMapping, sampleGrid) subGridUuid.isPresent() subGridUuid == Optional.of(gridUuid) diff --git a/src/test/groovy/edu/ie3/netpad/test/common/SampleData.groovy b/src/test/groovy/edu/ie3/netpad/test/common/SampleData.groovy new file mode 100644 index 0000000..f5150f9 --- /dev/null +++ b/src/test/groovy/edu/ie3/netpad/test/common/SampleData.groovy @@ -0,0 +1,72 @@ +/* + * © 2021. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ +package edu.ie3.netpad.test.common + +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.system.characteristic.OlmCharacteristicInput +import edu.ie3.datamodel.models.voltagelevels.GermanVoltageLevelUtils +import edu.ie3.datamodel.utils.GridAndGeoUtils +import edu.ie3.util.geo.GeoUtils +import edu.ie3.util.quantities.PowerSystemUnits +import org.locationtech.jts.geom.Coordinate +import tech.units.indriya.quantity.Quantities + +trait SampleData { + + /* 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 + ) + + LineInput 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 + ) +} diff --git a/src/test/groovy/edu/ie3/netpad/tool/grid/LineLengthFixerTest.groovy b/src/test/groovy/edu/ie3/netpad/tool/grid/LineLengthFixerTest.groovy new file mode 100644 index 0000000..da3171b --- /dev/null +++ b/src/test/groovy/edu/ie3/netpad/tool/grid/LineLengthFixerTest.groovy @@ -0,0 +1,107 @@ +/* + * © 2021. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ +package edu.ie3.netpad.tool.grid + +import edu.ie3.datamodel.models.input.connector.LineInput +import edu.ie3.netpad.grid.GridModel +import edu.ie3.netpad.grid.controller.GridController +import edu.ie3.netpad.io.controller.IoController +import edu.ie3.netpad.io.event.ReadGridEvent +import edu.ie3.netpad.test.common.SampleData +import edu.ie3.netpad.util.SampleGridFactory +import edu.ie3.util.geo.GeoUtils +import edu.ie3.util.quantities.PowerSystemUnits +import edu.ie3.util.quantities.QuantityUtil +import org.locationtech.jts.geom.Coordinate +import spock.lang.Specification +import tech.units.indriya.quantity.Quantities + +import javax.measure.Quantity +import javax.measure.quantity.Length +import java.util.stream.Collectors + +class LineLengthFixerTest extends Specification implements SampleData{ + + def "A LineLengthFixer is able to calculate the correct total length of a LineString"() { + given: + def coordinates = [ + new Coordinate(51.49292, 7.41197), + new Coordinate(51.49333, 7.41183), + new Coordinate(51.49341, 7.41189), + new Coordinate(51.49391, 7.41172), + new Coordinate(51.49404, 7.41279) + ] as Coordinate[] + + def lineString = GeoUtils.DEFAULT_GEOMETRY_FACTORY.createLineString(coordinates) + def expectedLength = Quantities.getQuantity(0.188940297821461040910483, PowerSystemUnits.KILOMETRE) + + when: + def actualLength = LineLengthFixer.lengthOfLineString(lineString) + + then: + actualLength.isPresent() + 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 LineLengthFixer is able to adjust the electrical line length to it's line string's total length"() { + given: + def coordinates = [ + new Coordinate(51.49292, 7.41197), + new Coordinate(51.49333, 7.41183), + new Coordinate(51.49341, 7.41189), + new Coordinate(51.49391, 7.41172), + new Coordinate(51.49404, 7.41279) + ] as Coordinate[] + def lineString = GeoUtils.DEFAULT_GEOMETRY_FACTORY.createLineString(coordinates) + def line = testLine.copy().geoPosition(lineString).build() + def expectedLength = Quantities.getQuantity(0.188940297821461040910483, PowerSystemUnits.KILOMETRE) + + when: + def updateLine = LineLengthFixer.setLineLengthToGeographicDistance(line) + + then: + QuantityUtil.isEquivalentAbs(updateLine.getLength(), expectedLength) + } + + def "A LineLengthFixer 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 = new HashMap() + /* subnet 1 */ + expectedLineLengths.put(UUID.fromString("92ec3bcf-1777-4d38-af67-0bf7c9fa73c7"), Quantities.getQuantity(0.13570403123164909653797, PowerSystemUnits.KILOMETRE)) + expectedLineLengths.put(UUID.fromString("4dd1bde7-0ec9-4540-ac9e-008bc5f883ba"), Quantities.getQuantity(0.065091844094861731826615, PowerSystemUnits.KILOMETRE)) + expectedLineLengths.put(UUID.fromString("d0f36763-c11e-46a4-bf6b-e57fb06fd8d8"), Quantities.getQuantity(0.11430643088441233981695, PowerSystemUnits.KILOMETRE)) + + /* subnet 2 (will be unchanged) */ + expectedLineLengths.put(UUID.fromString("b83b93ed-7468-47c2-aed9-48e554c428c7"), Quantities.getQuantity(1.11308358844200193288058, PowerSystemUnits.KILOMETRE)) + expectedLineLengths.put(UUID.fromString("571e8b88-dd9d-4542-89ed-b7f37916d775"), Quantities.getQuantity(2.65621973769665467422535, PowerSystemUnits.KILOMETRE)) + expectedLineLengths.put(UUID.fromString("7197e24f-97cd-4764-ae22-40cdc2f26dd2"), Quantities.getQuantity(1.82710747893781441715269, PowerSystemUnits.KILOMETRE)) + + def selectedSubnets = [1] as Set + def allGrids = GridController.getInstance().gridContainerToGridModel(sampleGrid, Collections.emptyMap()); + + when: + def actual = LineLengthFixer.setElectricalToGeographicalLineLength(allGrids, 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) + } + } +}