Skip to content
Open
10 changes: 10 additions & 0 deletions src/main/java/org/gridsuite/network/map/NetworkMapController.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import lombok.AllArgsConstructor;
import org.gridsuite.network.map.dto.*;
import org.gridsuite.network.map.dto.definition.hvdc.HvdcShuntCompensatorsInfos;
import org.gridsuite.network.map.dto.definition.topology.TopologyInfos;
import org.gridsuite.network.map.services.NetworkMapService;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.web.bind.annotation.*;
Expand Down Expand Up @@ -98,6 +99,15 @@ public List<SwitchInfos> getVoltageLevelSwitches(@Parameter(description = "Netwo
return networkMapService.getVoltageLevelSwitches(networkUuid, voltageLevelId, variantId);
}

@GetMapping(value = "/networks/{networkUuid}/voltage-levels/{voltageLevelId}/topology", produces = APPLICATION_JSON_VALUE)
@Operation(summary = "Get the voltage level topology description")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Network topology information retrieved")})
public TopologyInfos getVoltageLevelTopology(@Parameter(description = "Network UUID") @PathVariable("networkUuid") UUID networkUuid,
@Parameter(description = "Voltage level id") @PathVariable("voltageLevelId") String voltageLevelId,
@Parameter(description = "Variant Id") @RequestParam(name = "variantId", required = false) String variantId) {
return networkMapService.getVoltageLevelTopology(networkUuid, voltageLevelId, variantId);
}

@GetMapping(value = "/networks/{networkUuid}/voltage-levels/{voltageLevelId}/substation-id", produces = APPLICATION_JSON_VALUE)
@Operation(summary = "Get substation ID for a voltage level")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "substation ID for a voltage level")})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.gridsuite.network.map.dto.definition.voltagelevel;
package org.gridsuite.network.map.dto.definition.topology;

import com.powsybl.iidm.network.TwoSides;
import org.gridsuite.network.map.dto.definition.extension.ConnectablePositionInfos;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/**
* Copyright (c) 2025, RTE (http://www.rte-france.com)
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.gridsuite.network.map.dto.definition.topology;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.powsybl.iidm.network.SwitchKind;
import com.powsybl.iidm.network.TopologyKind;
import lombok.Getter;
import lombok.experimental.SuperBuilder;

import java.util.List;
import java.util.Map;

/**
* @author Etienne Lesot <etienne.lesot at rte-france.com>
*/
@SuperBuilder
@Getter
public class TopologyInfos {
@JsonInclude(JsonInclude.Include.NON_NULL)
private TopologyKind topologyKind;

@JsonInclude(JsonInclude.Include.NON_NULL)
private Integer busbarCount;

@JsonInclude(JsonInclude.Include.NON_NULL)
private Integer sectionCount;

@JsonInclude(JsonInclude.Include.NON_NULL)
private List<SwitchKind> switchKinds;

@JsonInclude(JsonInclude.Include.NON_NULL)
private Boolean isSymmetrical;

@JsonInclude(JsonInclude.Include.NON_NULL)
private Boolean isBusbarSectionPositionExtensionFound;

@JsonInclude(JsonInclude.Include.NON_NULL)
private Map<String, List<String>> busBarSectionsInfos;

@JsonInclude(JsonInclude.Include.NON_NULL)
private Map<String, List<FeederBayInfos>> feederBaysInfos;
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,12 @@
package org.gridsuite.network.map.dto.definition.voltagelevel;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.powsybl.iidm.network.SwitchKind;
import com.powsybl.iidm.network.TopologyKind;
import lombok.Getter;
import lombok.experimental.SuperBuilder;
import org.gridsuite.network.map.dto.ElementInfosWithProperties;
import org.gridsuite.network.map.dto.definition.extension.IdentifiableShortCircuitInfos;

import java.util.List;
import java.util.Map;
import java.util.Optional;

/**
Expand All @@ -41,25 +38,4 @@ public class VoltageLevelFormInfos extends ElementInfosWithProperties {

@JsonInclude(JsonInclude.Include.NON_ABSENT)
private Optional<IdentifiableShortCircuitInfos> identifiableShortCircuit;

@JsonInclude(JsonInclude.Include.NON_NULL)
private Integer busbarCount;

@JsonInclude(JsonInclude.Include.NON_NULL)
private Integer sectionCount;

@JsonInclude(JsonInclude.Include.NON_NULL)
private List<SwitchKind> switchKinds;

@JsonInclude(JsonInclude.Include.NON_NULL)
private Boolean isRetrievedBusbarSections;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to keep the attributes

@JsonInclude(JsonInclude.Include.NON_NULL)
private Integer busbarCount;

@JsonInclude(JsonInclude.Include.NON_NULL)
private Integer sectionCount;

@JsonInclude(JsonInclude.Include.NON_NULL)
private List<SwitchKind> switchKinds;

@JsonInclude(JsonInclude.Include.NON_NULL)
private Boolean isSymmetrical;

in the VoltageLevelFormInfos class, otherwise we break the voltage level creation and modification forms


@JsonInclude(JsonInclude.Include.NON_NULL)
private Boolean isBusbarSectionPositionFound;

@JsonInclude(JsonInclude.Include.NON_NULL)
private Map<String, List<String>> busBarSectionInfos;

@JsonInclude(JsonInclude.Include.NON_NULL)
private Map<String, List<FeederBayInfos>> feederBaysInfos;
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,17 @@
*/
package org.gridsuite.network.map.dto.mapper;

import com.powsybl.iidm.network.*;
import com.powsybl.iidm.network.extensions.BusbarSectionPosition;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import com.powsybl.iidm.network.Identifiable;
import com.powsybl.iidm.network.Substation;
import com.powsybl.iidm.network.VoltageLevel;
import org.gridsuite.network.map.dto.ElementInfos;
import org.gridsuite.network.map.dto.InfoTypeParameters;
import org.gridsuite.network.map.dto.definition.busbarsection.BusBarSectionFormInfos;
import org.gridsuite.network.map.dto.definition.voltagelevel.FeederBayInfos;
import org.gridsuite.network.map.dto.definition.voltagelevel.VoltageLevelFormInfos;
import org.gridsuite.network.map.dto.definition.voltagelevel.VoltageLevelMapInfos;
import org.gridsuite.network.map.dto.definition.voltagelevel.VoltageLevelTabInfos;
import org.gridsuite.network.map.dto.utils.ElementUtils;
import org.gridsuite.network.map.dto.utils.ExtensionUtils;

import java.util.*;
import java.util.stream.Collectors;

import static com.powsybl.iidm.network.Terminal.getConnectableSide;
import static org.gridsuite.network.map.dto.utils.ElementUtils.*;

/**
Expand All @@ -44,98 +36,19 @@ public static ElementInfos toData(Identifiable<?> identifiable, InfoTypeParamete
};
}

private static VoltageLevelTopologyInfos getTopologyInfos(VoltageLevel voltageLevel) {
Map<Integer, Integer> nbSectionsPerBusbar = new HashMap<>();
List<BusBarSectionFormInfos> busbarSectionInfos = new ArrayList<>();
int maxBusbarIndex = 1;
int maxSectionIndex = 1;
boolean busbarSectionPositionFound = true;
for (BusbarSection bbs : voltageLevel.getNodeBreakerView().getBusbarSections()) {
var extension = bbs.getExtension(BusbarSectionPosition.class);
if (extension == null) {
busbarSectionPositionFound = false;
break;
}
int busbarIndex = extension.getBusbarIndex();
int sectionIndex = extension.getSectionIndex();
maxBusbarIndex = Math.max(maxBusbarIndex, busbarIndex);
maxSectionIndex = Math.max(maxSectionIndex, sectionIndex);
nbSectionsPerBusbar.merge(busbarIndex, 1, Integer::sum);
busbarSectionInfos.add(BusBarSectionFormInfos.builder()
.id(bbs.getId())
.vertPos(sectionIndex)
.horizPos(busbarIndex)
.build());
}
VoltageLevelTopologyInfos voltageLevelTopologyInfos = createDefaultTopologyInfosBuilder().build();
if (!busbarSectionPositionFound) {
return voltageLevelTopologyInfos;
}

voltageLevelTopologyInfos.setBusbarSections(busbarSectionInfos);
voltageLevelTopologyInfos.setBusbarSectionPositionFound(true);

boolean isSymmetrical = maxBusbarIndex == 1 ||
nbSectionsPerBusbar.values().stream().distinct().count() == 1
&& nbSectionsPerBusbar.values().stream().findFirst().orElse(0).equals(maxSectionIndex);

if (isSymmetrical) {
voltageLevelTopologyInfos.setBusbarCount(maxBusbarIndex);
voltageLevelTopologyInfos.setSectionCount(maxSectionIndex);
voltageLevelTopologyInfos.setRetrievedBusbarSections(true);
voltageLevelTopologyInfos.setSwitchKinds(Collections.nCopies(maxSectionIndex - 1, SwitchKind.DISCONNECTOR));
}
return voltageLevelTopologyInfos;
}

static VoltageLevelFormInfos toFormInfos(Identifiable<?> identifiable) {
VoltageLevel voltageLevel = (VoltageLevel) identifiable;
VoltageLevelFormInfos.VoltageLevelFormInfosBuilder<?, ?> builder = VoltageLevelFormInfos.builder()
return VoltageLevelFormInfos.builder()
.name(voltageLevel.getOptionalName().orElse(null))
.id(voltageLevel.getId())
.topologyKind(voltageLevel.getTopologyKind())
.substationId(voltageLevel.getSubstation().map(Substation::getId).orElse(null))
.nominalV(voltageLevel.getNominalV())
.lowVoltageLimit(Double.isNaN(voltageLevel.getLowVoltageLimit()) ? null : voltageLevel.getLowVoltageLimit())
.highVoltageLimit(Double.isNaN(voltageLevel.getHighVoltageLimit()) ? null : voltageLevel.getHighVoltageLimit())
.properties(getProperties(voltageLevel));

if (voltageLevel.getTopologyKind().equals(TopologyKind.NODE_BREAKER)) {
VoltageLevelTopologyInfos vlTopologyInfos = getTopologyInfos(voltageLevel);
builder.busbarCount(vlTopologyInfos.getBusbarCount());
builder.sectionCount(vlTopologyInfos.getSectionCount());
builder.switchKinds(vlTopologyInfos.getSwitchKinds());
builder.isRetrievedBusbarSections(vlTopologyInfos.isRetrievedBusbarSections());
builder.isBusbarSectionPositionFound(vlTopologyInfos.isBusbarSectionPositionFound());
builder.busBarSectionInfos(vlTopologyInfos.getBusBarSectionInfosGrouped());
builder.feederBaysInfos(getFeederBaysInfos(voltageLevel));
}

builder.identifiableShortCircuit(ExtensionUtils.toIdentifiableShortCircuit(voltageLevel));

return builder.build();
}

private static Map<String, List<FeederBayInfos>> getFeederBaysInfos(VoltageLevel voltageLevel) {
Map<String, List<FeederBayInfos>> feederBayInfos = new HashMap<>();
String currentVoltageLevelId = voltageLevel.getId();
voltageLevel.getConnectableStream()
.filter(connectable -> !(connectable instanceof BusbarSection))
.forEach(connectable -> {
List<FeederBayInfos> connections = new ArrayList<>();
for (Object obj : connectable.getTerminals()) {
Terminal terminal = (Terminal) obj;
if (terminal.getVoltageLevel().getId().equals(currentVoltageLevelId)) {
connections.add(new FeederBayInfos(
getBusOrBusbarSection(terminal),
getConnectablePosition(connectable, FeederSide.from(getConnectableSide(terminal))),
getConnectableSide(terminal).map(ThreeSides::toTwoSides).orElse(null)
));
}
}
feederBayInfos.put(connectable.getId(), connections);
});
return feederBayInfos;
.properties(getProperties(voltageLevel))
.identifiableShortCircuit(ExtensionUtils.toIdentifiableShortCircuit(voltageLevel))
.build();
}

static VoltageLevelMapInfos toMapInfos(Identifiable<?> identifiable) {
Expand Down Expand Up @@ -165,36 +78,4 @@ static VoltageLevelTabInfos toTabInfos(Identifiable<?> identifiable) {

return builder.build();
}

private static VoltageLevelTopologyInfos.VoltageLevelTopologyInfosBuilder createDefaultTopologyInfosBuilder() {
return VoltageLevelTopologyInfos.builder()
.busbarCount(1).sectionCount(1).isRetrievedBusbarSections(false)
.switchKinds(List.of()).busbarSections(List.of()).isBusbarSectionPositionFound(false);
}

@Builder
@Getter
@Setter
public static class VoltageLevelTopologyInfos {
private List<BusBarSectionFormInfos> busbarSections;
private boolean isRetrievedBusbarSections; // true if busbar sections are symmetrical
private boolean isBusbarSectionPositionFound;
private int busbarCount;
private int sectionCount;
private List<SwitchKind> switchKinds;

public Map<String, List<String>> getBusBarSectionInfosGrouped() {
return busbarSections.stream()
.collect(Collectors.groupingBy(
section -> String.valueOf(section.getHorizPos()),
Collectors.collectingAndThen(
Collectors.toList(),
list -> list.stream()
.sorted(Comparator.comparing(BusBarSectionFormInfos::getVertPos))
.map(BusBarSectionFormInfos::getId)
.toList()
)
));
}
}
}
Loading