Skip to content

Commit

Permalink
Add geographical coordinates to network for NAD drawing and use Geogr…
Browse files Browse the repository at this point in the history
…aphicalLayout (#102)

Signed-off-by: maissa SOUISSI <[email protected]>
  • Loading branch information
souissimai authored Jun 21, 2024
1 parent 05db252 commit aeff6e7
Show file tree
Hide file tree
Showing 13 changed files with 609 additions and 5 deletions.
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>com.powsybl</groupId>
<artifactId>powsybl-parent-ws</artifactId>
Expand Down
157 changes: 157 additions & 0 deletions src/main/java/com/powsybl/nad/build/iidm/VoltageLevelFilter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
/**
* Copyright (c) 2024, RTE (http://www.rte-france.com)
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package com.powsybl.nad.build.iidm;

import com.powsybl.commons.PowsyblException;
import com.powsybl.iidm.network.*;
import com.powsybl.nad.utils.iidm.IidmUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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

// FIXME: to be removed at next powsybl-diagram upgrade.
// Copied from https://github.com/powsybl/powsybl-diagram.

/**
* @author Maissa Souissi {<maissa.souissi at rte-france.com>}
*/

public class VoltageLevelFilter implements Predicate<VoltageLevel> {

protected static final Logger LOGGER = LoggerFactory.getLogger(VoltageLevelFilter.class);

public static final Predicate<VoltageLevel> NO_FILTER = voltageLevel -> true;

private static final String UNKNOWN_VOLTAGE_LEVEL = "Unknown voltage level id '";

private final Set<VoltageLevel> voltageLevels;

public VoltageLevelFilter(Set<VoltageLevel> voltageLevels) {
this.voltageLevels = voltageLevels;
}

public int getNbVoltageLevels() {
return voltageLevels.size();
}

public Set<VoltageLevel> getVoltageLevels() {
return voltageLevels;
}

@Override
public boolean test(VoltageLevel voltageLevel) {
return voltageLevels.contains(voltageLevel);
}

public static VoltageLevelFilter createVoltageLevelsDepthFilter(Network network, List<String> voltageLevelIds, int depth) {
return createVoltageLevelFilterWithPredicate(network, voltageLevelIds, depth, NO_FILTER);
}

public static VoltageLevelFilter createVoltageLevelFilterWithPredicate(Network network, List<String> voltageLevelIds, int depth, Predicate<VoltageLevel> voltageLevelPredicate) {
Objects.requireNonNull(network);
Objects.requireNonNull(voltageLevelIds);
Set<VoltageLevel> startingSet = new HashSet<>();

for (String voltageLevelId : voltageLevelIds) {
VoltageLevel vl = network.getVoltageLevel(voltageLevelId);
if (vl == null) {
throw new PowsyblException(UNKNOWN_VOLTAGE_LEVEL + voltageLevelId + "'");
}
if (!voltageLevelPredicate.test(vl)) {
LOGGER.warn("vl '{}' does not comply with the predicate", voltageLevelId);
}
startingSet.add(vl);
}
Set<VoltageLevel> voltageLevels = new HashSet<>();
VoltageLevelFilter.traverseVoltageLevels(startingSet, depth, voltageLevels, voltageLevelPredicate);
return new VoltageLevelFilter(voltageLevels);
}

public static Collection<VoltageLevel> getNextDepthVoltageLevels(Network network, List<VoltageLevel> voltageLevels) {
List<String> voltageLevelIds = voltageLevels.stream().map(VoltageLevel::getId).collect(Collectors.toList());
VoltageLevelFilter voltageLevelFilter = createVoltageLevelsDepthFilter(network, voltageLevelIds, 1);
Set<VoltageLevel> voltageLevelSet = new HashSet<>(voltageLevelFilter.getVoltageLevels());
voltageLevels.forEach(voltageLevelSet::remove);
return voltageLevelSet;
}

private static void traverseVoltageLevels(Set<VoltageLevel> voltageLevelsDepth, int depth, Set<VoltageLevel> visitedVoltageLevels, Predicate<VoltageLevel> predicate) {
if (depth < 0) {
return;
}
Set<VoltageLevel> nextDepthVoltageLevels = new HashSet<>();
for (VoltageLevel vl : voltageLevelsDepth) {
if (!visitedVoltageLevels.contains(vl)) {
visitedVoltageLevels.add(vl);
vl.visitEquipments(new VlVisitor(nextDepthVoltageLevels, visitedVoltageLevels, predicate));
}
}
traverseVoltageLevels(nextDepthVoltageLevels, depth - 1, visitedVoltageLevels, predicate);
}

private static class VlVisitor extends DefaultTopologyVisitor {
private final Set<VoltageLevel> nextDepthVoltageLevels;
private final Set<VoltageLevel> visitedVoltageLevels;
private final Predicate<VoltageLevel> voltageLevelPredicate;

public VlVisitor(Set<VoltageLevel> nextDepthVoltageLevels, Set<VoltageLevel> visitedVoltageLevels, Predicate<VoltageLevel> voltageLevelPredicate) {
this.nextDepthVoltageLevels = nextDepthVoltageLevels;
this.visitedVoltageLevels = visitedVoltageLevels;
this.voltageLevelPredicate = voltageLevelPredicate;
}

@Override
public void visitLine(Line line, TwoSides side) {
visitBranch(line, side);
}

@Override
public void visitTwoWindingsTransformer(TwoWindingsTransformer twt, TwoSides side) {
visitBranch(twt, side);
}

@Override
public void visitThreeWindingsTransformer(ThreeWindingsTransformer twt, ThreeSides side) {
if (side == ThreeSides.ONE) {
visitTerminal(twt.getTerminal(ThreeSides.TWO));
visitTerminal(twt.getTerminal(ThreeSides.THREE));
} else if (side == ThreeSides.TWO) {
visitTerminal(twt.getTerminal(ThreeSides.ONE));
visitTerminal(twt.getTerminal(ThreeSides.THREE));
} else {
visitTerminal(twt.getTerminal(ThreeSides.ONE));
visitTerminal(twt.getTerminal(ThreeSides.TWO));
}
}

@Override
public void visitHvdcConverterStation(HvdcConverterStation<?> converterStation) {
converterStation.getOtherConverterStation().ifPresent(c -> visitTerminal(c.getTerminal()));
}

private void visitBranch(Branch<?> branch, TwoSides side) {
visitTerminal(branch.getTerminal(IidmUtils.getOpposite(side)));
}

private void visitTerminal(Terminal terminal) {
VoltageLevel voltageLevel = terminal.getVoltageLevel();
if (!visitedVoltageLevels.contains(voltageLevel) && voltageLevelPredicate.test(voltageLevel)) {
nextDepthVoltageLevels.add(voltageLevel);
}
}

@Override
public void visitDanglingLine(DanglingLine danglingLine) {
if (danglingLine.isPaired()) {
danglingLine.getTieLine().ifPresent(tieline -> visitBranch(tieline, tieline.getSide(danglingLine.getTerminal())));
}
}
}
}
66 changes: 66 additions & 0 deletions src/main/java/com/powsybl/sld/server/GeoDataService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/**
* Copyright (c) 2024, RTE (http://www.rte-france.com)
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

package com.powsybl.sld.server;

/**
* @author Maissa SOUISSI <maissa.souissi at rte-france.com>
*/

import lombok.Setter;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;

import java.util.List;
import java.util.UUID;

@Service
public class GeoDataService {

public static final String QUERY_PARAM_SUBSTATION_ID = "substationId";
public static final String GEO_DATA_API_VERSION = "v1";
public static final String QUERY_PARAM_VARIANT_ID = "variantId";
public static final String NETWORK_UUID = "networkUuid";
static final String SUBSTATIONS = "substations";
private static final String DELIMITER = "/";
private final RestTemplate restTemplate;
@Setter
private String geoDataServerBaseUri;

public GeoDataService(@Value("${gridsuite.services.geo-data-server.base-uri:http://geo-data-server/}") String geoDataServerBaseUri,
RestTemplate restTemplate) {
this.geoDataServerBaseUri = geoDataServerBaseUri;
this.restTemplate = restTemplate;
}

private String getGeoDataServerURI() {
return this.geoDataServerBaseUri + DELIMITER + GEO_DATA_API_VERSION + DELIMITER;
}

public String getSubstationsGraphics(UUID networkUuid, String variantId, List<String> substationsIds) {
UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromHttpUrl(getGeoDataServerURI() + SUBSTATIONS)
.queryParam(NETWORK_UUID, networkUuid);

if (!StringUtils.isBlank(variantId)) {
uriComponentsBuilder.queryParam(QUERY_PARAM_VARIANT_ID, variantId);
}

if (substationsIds != null) {
uriComponentsBuilder.queryParam(QUERY_PARAM_SUBSTATION_ID, substationsIds);
}

var path = uriComponentsBuilder
.buildAndExpand()
.toUriString();

return restTemplate.getForObject(path, String.class);
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,27 @@
*/
package com.powsybl.sld.server;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.powsybl.iidm.network.Network;
import com.powsybl.iidm.network.Substation;
import com.powsybl.iidm.network.VoltageLevel;
import com.powsybl.iidm.network.extensions.SubstationPosition;
import com.powsybl.iidm.network.extensions.SubstationPositionAdder;
import com.powsybl.nad.NadParameters;
import com.powsybl.nad.NetworkAreaDiagram;
import com.powsybl.nad.build.iidm.VoltageLevelFilter;
import com.powsybl.nad.layout.GeographicalLayoutFactory;
import com.powsybl.nad.layout.LayoutParameters;
import com.powsybl.nad.svg.SvgParameters;
import com.powsybl.nad.svg.iidm.TopologicalStyleProvider;
import com.powsybl.network.store.client.NetworkStoreService;
import com.powsybl.network.store.client.PreloadingStrategy;
import com.powsybl.sld.server.dto.Coordinate;
import com.powsybl.sld.server.dto.SubstationGeoData;
import com.powsybl.sld.server.dto.SvgAndMetadata;
import com.powsybl.sld.server.dto.VoltageLevelInfos;
import com.powsybl.sld.server.utils.DiagramUtils;
import com.powsybl.sld.server.utils.GeoDataUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.http.HttpStatus;
Expand All @@ -39,24 +48,34 @@ class NetworkAreaDiagramService {
@Autowired
private NetworkStoreService networkStoreService;

@Autowired
private GeoDataService geoDataService;

public SvgAndMetadata generateNetworkAreaDiagramSvg(UUID networkUuid, String variantId, List<String> voltageLevelsIds, int depth) {
Network network = DiagramUtils.getNetwork(networkUuid, variantId, networkStoreService, PreloadingStrategy.COLLECTION);
List<String> existingVLIds = voltageLevelsIds.stream().filter(vl -> network.getVoltageLevel(vl) != null).collect(Collectors.toList());
List<String> existingVLIds = voltageLevelsIds.stream().filter(vl -> network.getVoltageLevel(vl) != null).toList();
if (existingVLIds.isEmpty()) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "no voltage level was found");
}
try (StringWriter svgWriter = new StringWriter()) {
SvgParameters svgParameters = new SvgParameters()
.setSvgWidthAndHeightAdded(true)
.setCssLocation(SvgParameters.CssLocation.EXTERNAL_NO_IMPORT);

//List of selected voltageLevels with depth
VoltageLevelFilter vlFilter = VoltageLevelFilter.createVoltageLevelsDepthFilter(network, existingVLIds, depth);
//get voltage levels' positions on depth+1 to be able to locate lines on depth
List<VoltageLevel> voltageLevels = VoltageLevelFilter.createVoltageLevelsDepthFilter(network, existingVLIds, depth + 1).getVoltageLevels().stream().toList();
assignGeoDataCoordinates(network, networkUuid, variantId, voltageLevels);

LayoutParameters layoutParameters = new LayoutParameters();
NadParameters nadParameters = new NadParameters();
nadParameters.setSvgParameters(svgParameters);
nadParameters.setLayoutParameters(layoutParameters);
nadParameters.setLayoutFactory(new GeographicalLayoutFactory(network));
nadParameters.setStyleProviderFactory(n -> new TopologicalStyleProvider(network));
var vlFilter = VoltageLevelFilter.createVoltageLevelsDepthFilter(network, existingVLIds, depth);
NetworkAreaDiagram.draw(network, svgWriter, nadParameters, vlFilter);

NetworkAreaDiagram.draw(network, svgWriter, nadParameters, vlFilter);
Map<String, Object> additionalMetadata = computeAdditionalMetadata(network, existingVLIds, depth);

return SvgAndMetadata.builder()
Expand All @@ -67,14 +86,40 @@ public SvgAndMetadata generateNetworkAreaDiagramSvg(UUID networkUuid, String var
}
}

public void assignGeoDataCoordinates(Network network, UUID networkUuid, String variantId, List<VoltageLevel> voltageLevels) {
// Geographical positions for substations related to voltageLevels
List<Substation> substations = voltageLevels.stream()
.map(VoltageLevel::getNullableSubstation)
.filter(Objects::nonNull)
.toList();

String substationsGeoDataString = geoDataService.getSubstationsGraphics(networkUuid, variantId, substations.stream().map(Substation::getId).toList());
List<SubstationGeoData> substationsGeoData = GeoDataUtils.fromStringToSubstationGeoData(substationsGeoDataString, new ObjectMapper());
Map<String, Coordinate> substationGeoDataMap = substationsGeoData.stream()
.collect(Collectors.toMap(SubstationGeoData::getId, SubstationGeoData::getCoordinate));

for (Substation substation : substations) {
if (network.getSubstation(substation.getId()).getExtension(SubstationPosition.class) == null) {
com.powsybl.sld.server.dto.Coordinate coordinate = substationGeoDataMap.get(substation.getId());
if (coordinate != null) {
network.getSubstation(substation.getId())
.newExtension(SubstationPositionAdder.class)
.withCoordinate(new com.powsybl.iidm.network.extensions.Coordinate(coordinate.getLat(), coordinate.getLon()))
.add();
}
}

}
}

private Map<String, Object> computeAdditionalMetadata(Network network, List<String> voltageLevelsIds, int depth) {

VoltageLevelFilter vlFilter = VoltageLevelFilter.createVoltageLevelsDepthFilter(network, voltageLevelsIds, depth);

List<VoltageLevelInfos> voltageLevelsInfos = voltageLevelsIds.stream()
.map(network::getVoltageLevel)
.map(VoltageLevelInfos::new)
.collect(Collectors.toList());
.toList();

Map<String, Object> metadata = new HashMap<>();
metadata.put("nbVoltageLevels", vlFilter.getNbVoltageLevels());
Expand Down
49 changes: 49 additions & 0 deletions src/main/java/com/powsybl/sld/server/RestTemplateConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/**
* Copyright (c) 2024, RTE (http://www.rte-france.com)
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

package com.powsybl.sld.server;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.client.RestTemplate;

/**
* @author Maissa SOUISSI <maissa.souissi at rte-france.com>
*/

@Configuration
public class RestTemplateConfig {

@Autowired
private ObjectMapper objectMapper;

@Bean
public RestTemplate restTemplate() {
final RestTemplate restTemplate = new RestTemplate();

//find and replace Jackson message converter with our own
for (int i = 0; i < restTemplate.getMessageConverters().size(); i++) {
final HttpMessageConverter<?> httpMessageConverter = restTemplate.getMessageConverters().get(i);
if (httpMessageConverter instanceof MappingJackson2HttpMessageConverter) {
restTemplate.getMessageConverters().set(i, mappingJackson2HttpMessageConverter());
}
}

return restTemplate;
}

private MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
converter.setObjectMapper(objectMapper);
return converter;
}

}
Loading

0 comments on commit aeff6e7

Please sign in to comment.