diff --git a/pom.xml b/pom.xml
index 6b37aa4..21d3401 100644
--- a/pom.xml
+++ b/pom.xml
@@ -9,7 +9,7 @@
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
4.0.0
-
+
com.powsybl
powsybl-parent-ws
diff --git a/src/main/java/com/powsybl/nad/build/iidm/VoltageLevelFilter.java b/src/main/java/com/powsybl/nad/build/iidm/VoltageLevelFilter.java
new file mode 100644
index 0000000..597b521
--- /dev/null
+++ b/src/main/java/com/powsybl/nad/build/iidm/VoltageLevelFilter.java
@@ -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 {}
+ */
+
+public class VoltageLevelFilter implements Predicate {
+
+ protected static final Logger LOGGER = LoggerFactory.getLogger(VoltageLevelFilter.class);
+
+ public static final Predicate NO_FILTER = voltageLevel -> true;
+
+ private static final String UNKNOWN_VOLTAGE_LEVEL = "Unknown voltage level id '";
+
+ private final Set voltageLevels;
+
+ public VoltageLevelFilter(Set voltageLevels) {
+ this.voltageLevels = voltageLevels;
+ }
+
+ public int getNbVoltageLevels() {
+ return voltageLevels.size();
+ }
+
+ public Set getVoltageLevels() {
+ return voltageLevels;
+ }
+
+ @Override
+ public boolean test(VoltageLevel voltageLevel) {
+ return voltageLevels.contains(voltageLevel);
+ }
+
+ public static VoltageLevelFilter createVoltageLevelsDepthFilter(Network network, List voltageLevelIds, int depth) {
+ return createVoltageLevelFilterWithPredicate(network, voltageLevelIds, depth, NO_FILTER);
+ }
+
+ public static VoltageLevelFilter createVoltageLevelFilterWithPredicate(Network network, List voltageLevelIds, int depth, Predicate voltageLevelPredicate) {
+ Objects.requireNonNull(network);
+ Objects.requireNonNull(voltageLevelIds);
+ Set 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 voltageLevels = new HashSet<>();
+ VoltageLevelFilter.traverseVoltageLevels(startingSet, depth, voltageLevels, voltageLevelPredicate);
+ return new VoltageLevelFilter(voltageLevels);
+ }
+
+ public static Collection getNextDepthVoltageLevels(Network network, List voltageLevels) {
+ List voltageLevelIds = voltageLevels.stream().map(VoltageLevel::getId).collect(Collectors.toList());
+ VoltageLevelFilter voltageLevelFilter = createVoltageLevelsDepthFilter(network, voltageLevelIds, 1);
+ Set voltageLevelSet = new HashSet<>(voltageLevelFilter.getVoltageLevels());
+ voltageLevels.forEach(voltageLevelSet::remove);
+ return voltageLevelSet;
+ }
+
+ private static void traverseVoltageLevels(Set voltageLevelsDepth, int depth, Set visitedVoltageLevels, Predicate predicate) {
+ if (depth < 0) {
+ return;
+ }
+ Set 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 nextDepthVoltageLevels;
+ private final Set visitedVoltageLevels;
+ private final Predicate voltageLevelPredicate;
+
+ public VlVisitor(Set nextDepthVoltageLevels, Set visitedVoltageLevels, Predicate 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())));
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/powsybl/sld/server/GeoDataService.java b/src/main/java/com/powsybl/sld/server/GeoDataService.java
new file mode 100644
index 0000000..119d6db
--- /dev/null
+++ b/src/main/java/com/powsybl/sld/server/GeoDataService.java
@@ -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
+ */
+
+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 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);
+ }
+}
+
diff --git a/src/main/java/com/powsybl/sld/server/NetworkAreaDiagramService.java b/src/main/java/com/powsybl/sld/server/NetworkAreaDiagramService.java
index 4f962af..ec0c213 100644
--- a/src/main/java/com/powsybl/sld/server/NetworkAreaDiagramService.java
+++ b/src/main/java/com/powsybl/sld/server/NetworkAreaDiagramService.java
@@ -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;
@@ -39,9 +48,12 @@ class NetworkAreaDiagramService {
@Autowired
private NetworkStoreService networkStoreService;
+ @Autowired
+ private GeoDataService geoDataService;
+
public SvgAndMetadata generateNetworkAreaDiagramSvg(UUID networkUuid, String variantId, List voltageLevelsIds, int depth) {
Network network = DiagramUtils.getNetwork(networkUuid, variantId, networkStoreService, PreloadingStrategy.COLLECTION);
- List existingVLIds = voltageLevelsIds.stream().filter(vl -> network.getVoltageLevel(vl) != null).collect(Collectors.toList());
+ List existingVLIds = voltageLevelsIds.stream().filter(vl -> network.getVoltageLevel(vl) != null).toList();
if (existingVLIds.isEmpty()) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "no voltage level was found");
}
@@ -49,14 +61,21 @@ public SvgAndMetadata generateNetworkAreaDiagramSvg(UUID networkUuid, String var
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 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 additionalMetadata = computeAdditionalMetadata(network, existingVLIds, depth);
return SvgAndMetadata.builder()
@@ -67,6 +86,32 @@ public SvgAndMetadata generateNetworkAreaDiagramSvg(UUID networkUuid, String var
}
}
+ public void assignGeoDataCoordinates(Network network, UUID networkUuid, String variantId, List voltageLevels) {
+ // Geographical positions for substations related to voltageLevels
+ List substations = voltageLevels.stream()
+ .map(VoltageLevel::getNullableSubstation)
+ .filter(Objects::nonNull)
+ .toList();
+
+ String substationsGeoDataString = geoDataService.getSubstationsGraphics(networkUuid, variantId, substations.stream().map(Substation::getId).toList());
+ List substationsGeoData = GeoDataUtils.fromStringToSubstationGeoData(substationsGeoDataString, new ObjectMapper());
+ Map 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 computeAdditionalMetadata(Network network, List voltageLevelsIds, int depth) {
VoltageLevelFilter vlFilter = VoltageLevelFilter.createVoltageLevelsDepthFilter(network, voltageLevelsIds, depth);
@@ -74,7 +119,7 @@ private Map computeAdditionalMetadata(Network network, List voltageLevelsInfos = voltageLevelsIds.stream()
.map(network::getVoltageLevel)
.map(VoltageLevelInfos::new)
- .collect(Collectors.toList());
+ .toList();
Map metadata = new HashMap<>();
metadata.put("nbVoltageLevels", vlFilter.getNbVoltageLevels());
diff --git a/src/main/java/com/powsybl/sld/server/RestTemplateConfig.java b/src/main/java/com/powsybl/sld/server/RestTemplateConfig.java
new file mode 100644
index 0000000..e99c36c
--- /dev/null
+++ b/src/main/java/com/powsybl/sld/server/RestTemplateConfig.java
@@ -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
+ */
+
+@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;
+ }
+
+}
diff --git a/src/main/java/com/powsybl/sld/server/dto/Coordinate.java b/src/main/java/com/powsybl/sld/server/dto/Coordinate.java
new file mode 100644
index 0000000..0b15978
--- /dev/null
+++ b/src/main/java/com/powsybl/sld/server/dto/Coordinate.java
@@ -0,0 +1,25 @@
+/**
+ * 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.dto;
+
+import lombok.*;
+
+/**
+ * @author Maissa SOUISSI
+ */
+
+@Getter
+@Setter
+@NoArgsConstructor
+@AllArgsConstructor
+@ToString
+public class Coordinate {
+ private double lat;
+ private double lon;
+}
diff --git a/src/main/java/com/powsybl/sld/server/dto/SubstationGeoData.java b/src/main/java/com/powsybl/sld/server/dto/SubstationGeoData.java
new file mode 100644
index 0000000..b1ac270
--- /dev/null
+++ b/src/main/java/com/powsybl/sld/server/dto/SubstationGeoData.java
@@ -0,0 +1,28 @@
+/**
+ * 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.dto;
+
+import com.powsybl.iidm.network.Country;
+import lombok.*;
+
+/**
+ * @author Maissa SOUISSI
+ */
+@Getter
+@Setter
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+@ToString
+public class SubstationGeoData {
+
+ private String id;
+
+ private Country country;
+
+ private Coordinate coordinate;
+}
diff --git a/src/main/java/com/powsybl/sld/server/utils/GeoDataUtils.java b/src/main/java/com/powsybl/sld/server/utils/GeoDataUtils.java
new file mode 100644
index 0000000..4689214
--- /dev/null
+++ b/src/main/java/com/powsybl/sld/server/utils/GeoDataUtils.java
@@ -0,0 +1,38 @@
+/**
+ * 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.utils;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.powsybl.commons.PowsyblException;
+import com.powsybl.sld.server.dto.SubstationGeoData;
+
+import java.util.List;
+
+/**
+ * @author Maissa SOUISSI
+ */
+
+public final class GeoDataUtils {
+ private GeoDataUtils() {
+ throw new AssertionError("Utility class should not be instantiated");
+ }
+
+ public static List fromStringToSubstationGeoData(String jsonResponse, ObjectMapper objectMapper) {
+ try {
+ return objectMapper.readValue(jsonResponse, new TypeReference<>() {
+ });
+ } catch (JsonProcessingException e) {
+ throw new PowsyblException("Failed to parse JSON response", e);
+ }
+ }
+
+}
+
+
+
diff --git a/src/main/resources/application-local.yml b/src/main/resources/application-local.yml
index 4c640f6..84c7d41 100644
--- a/src/main/resources/application-local.yml
+++ b/src/main/resources/application-local.yml
@@ -5,3 +5,8 @@ powsybl:
services:
network-store-server:
base-uri: http://localhost:8080
+
+gridsuite:
+ services:
+ geo-data-server:
+ base-uri: http://localhost:8087
diff --git a/src/main/resources/config/application.yaml b/src/main/resources/config/application.yaml
index b7adaf7..7ad3e36 100644
--- a/src/main/resources/config/application.yaml
+++ b/src/main/resources/config/application.yaml
@@ -1,3 +1,9 @@
spring:
application:
name: single-line-diagram-server
+
+gridsuite:
+ services:
+ -
+ name: geo-data-server
+ base-uri: http://geo-data-server/
\ No newline at end of file
diff --git a/src/test/java/com/powsybl/sld/server/GeoDataServiceTest.java b/src/test/java/com/powsybl/sld/server/GeoDataServiceTest.java
new file mode 100644
index 0000000..f22ba5c
--- /dev/null
+++ b/src/test/java/com/powsybl/sld/server/GeoDataServiceTest.java
@@ -0,0 +1,131 @@
+package com.powsybl.sld.server;
+
+/*
+ * 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/.
+ */
+
+import com.powsybl.iidm.network.*;
+import com.powsybl.network.store.client.NetworkStoreService;
+import com.powsybl.sld.server.dto.Coordinate;
+import com.powsybl.sld.server.dto.SubstationGeoData;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentMatchers;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.springframework.web.client.RestTemplate;
+
+import java.util.List;
+import java.util.UUID;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.when;
+
+public class GeoDataServiceTest {
+
+ private static final String VARIANT_1_ID = "variant_1";
+ private static final String VARIANT_2_ID = "variant_2";
+ private final String baseUri = "http://geo-data-server/";
+ @Mock
+ private RestTemplate restTemplate;
+ @InjectMocks
+ private GeoDataService geoDataService;
+ @Mock
+ private NetworkStoreService networkStoreService;
+
+ public static Network createNetwork() {
+ Network network = Network.create("test", "test");
+ Substation substationFr1 = network.newSubstation()
+ .setId("subFr1")
+ .setCountry(Country.FR)
+ .setTso("RTE")
+ .add();
+ VoltageLevel voltageLevelFr1A = substationFr1.newVoltageLevel()
+ .setId("vlFr1A")
+ .setName("vlFr1A")
+ .setNominalV(440.0)
+ .setHighVoltageLimit(400.0)
+ .setLowVoltageLimit(200.0)
+ .setTopologyKind(TopologyKind.BUS_BREAKER)
+ .add();
+ voltageLevelFr1A.getBusBreakerView().newBus()
+ .setId("busFr1A")
+ .setName("busFr1A")
+ .add();
+ VoltageLevel voltageLevelFr1B = substationFr1.newVoltageLevel()
+ .setId("vlFr1B").setName("vlFr1B")
+ .setNominalV(200.0)
+ .setHighVoltageLimit(400.0)
+ .setLowVoltageLimit(200.0)
+ .setTopologyKind(TopologyKind.BUS_BREAKER)
+ .add();
+ voltageLevelFr1B.getBusBreakerView().newBus()
+ .setId("busFr1B")
+ .setName("busFr1B")
+ .add();
+
+ Substation substationFr2 = network.newSubstation()
+ .setId("subFr2")
+ .setCountry(Country.FR)
+ .setTso("RTE")
+ .add();
+ VoltageLevel voltageLevelFr2A = substationFr2.newVoltageLevel()
+ .setId("vlFr2A")
+ .setName("vlFr2A")
+ .setNominalV(440.0)
+ .setHighVoltageLimit(400.0)
+ .setLowVoltageLimit(200.0)
+ .setTopologyKind(TopologyKind.BUS_BREAKER)
+ .add();
+ voltageLevelFr2A.getBusBreakerView().newBus()
+ .setId("busFr2A")
+ .setName("busFr2A")
+ .add();
+ network.getVariantManager().cloneVariant(VariantManagerConstants.INITIAL_VARIANT_ID, VARIANT_1_ID);
+ network.getVariantManager().cloneVariant(VariantManagerConstants.INITIAL_VARIANT_ID, VARIANT_2_ID);
+
+ return network;
+ }
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ geoDataService.setGeoDataServerBaseUri(baseUri);
+ }
+
+ @Test
+ public void testGetSubstationsGraphics() {
+ UUID networkUuid = UUID.randomUUID();
+ String variantId = "variant2";
+ List substationsIds = List.of("subFr1", "subFr2");
+
+ String expectedResponse = "Substations graphics data";
+ when(restTemplate.getForObject(ArgumentMatchers.anyString(), ArgumentMatchers.eq(String.class)))
+ .thenReturn(expectedResponse);
+
+ String response = geoDataService.getSubstationsGraphics(networkUuid, variantId, substationsIds);
+
+ assertEquals(expectedResponse, response);
+ }
+
+ @Test
+ public void testGetSubstationsGraphicsWithoutVariantId() {
+ UUID networkUuid = UUID.randomUUID();
+ List substationsIds = List.of("subFr1");
+ SubstationGeoData substationGeoData = new SubstationGeoData();
+ substationGeoData.setId("subFr1");
+ substationGeoData.setCoordinate(new Coordinate(48.8588443, 2.2943506));
+ substationGeoData.setCountry(Country.FR);
+ String expectedResponse = substationGeoData.toString();
+ when(restTemplate.getForObject(ArgumentMatchers.anyString(), ArgumentMatchers.eq(String.class)))
+ .thenReturn(expectedResponse);
+
+ String response = geoDataService.getSubstationsGraphics(networkUuid, null, substationsIds);
+
+ assertEquals(expectedResponse, response);
+ }
+}
diff --git a/src/test/java/com/powsybl/sld/server/SingleLineDiagramTest.java b/src/test/java/com/powsybl/sld/server/SingleLineDiagramTest.java
index 7935325..94139da 100644
--- a/src/test/java/com/powsybl/sld/server/SingleLineDiagramTest.java
+++ b/src/test/java/com/powsybl/sld/server/SingleLineDiagramTest.java
@@ -8,6 +8,7 @@
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.io.ByteStreams;
import com.google.common.jimfs.Configuration;
import com.google.common.jimfs.Jimfs;
import com.powsybl.commons.PowsyblException;
@@ -16,6 +17,7 @@
import com.powsybl.iidm.network.extensions.BusbarSectionPositionAdder;
import com.powsybl.iidm.network.extensions.ConnectablePosition;
import com.powsybl.iidm.network.extensions.ConnectablePositionAdder;
+import com.powsybl.iidm.network.extensions.SubstationPosition;
import com.powsybl.network.store.client.NetworkStoreService;
import com.powsybl.network.store.client.PreloadingStrategy;
import com.powsybl.sld.SingleLineDiagram;
@@ -54,6 +56,7 @@
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.UUID;
import static com.powsybl.sld.library.ComponentTypeName.ARROW_ACTIVE;
@@ -87,6 +90,8 @@ public class SingleLineDiagramTest {
@MockBean
private NetworkStoreService networkStoreService;
+ @MockBean
+ private GeoDataService geoDataService;
private ObjectMapper objectMapper = new ObjectMapper();
@@ -281,11 +286,39 @@ public void testComponentLibraries() throws Exception {
assertEquals("[\"GridSuiteAndConvergence\",\"Convergence\",\"FlatDesign\"]", result.getResponse().getContentAsString());
}
+ private static final String GEO_DATA_SUBSTATIONS = "/geo_data_substations.json";
+
+ @Test
+ public void testAssignSubstationGeoData() throws Exception {
+ UUID testNetworkId = UUID.fromString("7928181c-7977-4592-ba19-88027e4254e4");
+ given(networkStoreService.getNetwork(testNetworkId, PreloadingStrategy.COLLECTION)).willReturn(createNetwork());
+ Network network = networkStoreService.getNetwork(testNetworkId, PreloadingStrategy.COLLECTION);
+
+ String substationGeoDataJson = "[{\"id\":\"subFr1\",\"coordinate\":{\"lat\":48.8588443,\"lon\":2.2943506}},{\"id\":\"subFr2\",\"coordinate\":{\"lat\":51.507351,\"lon\":1.127758}}]";
+ given(geoDataService.getSubstationsGraphics(testNetworkId, VARIANT_1_ID, List.of("subFr2"))).willReturn(substationGeoDataJson);
+
+ networkAreaDiagramService.assignGeoDataCoordinates(network, testNetworkId, VARIANT_1_ID, List.of(network.getVoltageLevel("vlFr2A")));
+ assertEquals(network.getSubstation("subFr2").getExtension(SubstationPosition.class).getCoordinate(), new com.powsybl.iidm.network.extensions.Coordinate(51.507351, 1.127758));
+ assertEquals(network.getSubstation("subFr1").getExtension(SubstationPosition.class), null);
+
+ String faultSubstationGeoDataJson = "[{\"id\":\"subFr1\",\"coordinate\":{\"lat\":48.8588443,\"long\":2.2943506}}]";
+ given(geoDataService.getSubstationsGraphics(testNetworkId, VARIANT_1_ID, List.of("subFr1"))).willReturn(faultSubstationGeoDataJson);
+ PowsyblException exception = assertThrows(PowsyblException.class, () -> {
+ networkAreaDiagramService.assignGeoDataCoordinates(network, testNetworkId, VARIANT_1_ID, List.of(network.getVoltageLevel("vlFr1A")));
+ });
+
+ // Assert the exception message
+ assertEquals("Failed to parse JSON response", exception.getMessage());
+ assertEquals(network.getSubstation("subFr1").getExtension(SubstationPosition.class), null);
+ }
+
@Test
public void testNetworkAreaDiagram() throws Exception {
UUID testNetworkId = UUID.fromString("7928181c-7977-4592-ba19-88027e4254e4");
UUID notFoundNetworkId = UUID.fromString("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa");
+ given(geoDataService.getSubstationsGraphics(testNetworkId, VARIANT_2_ID, List.of("subFr1"))).willReturn(toString(GEO_DATA_SUBSTATIONS));
+ given(networkStoreService.getNetwork(testNetworkId, PreloadingStrategy.COLLECTION)).willReturn(createNetwork());
given(networkStoreService.getNetwork(testNetworkId, PreloadingStrategy.COLLECTION)).willReturn(createNetwork());
given(networkStoreService.getNetwork(notFoundNetworkId, PreloadingStrategy.COLLECTION)).willThrow(new PowsyblException());
@@ -318,6 +351,7 @@ public void testNetworkAreaDiagramAdditionalMetadata() {
UUID testNetworkId = UUID.fromString("7928181c-7977-4592-ba19-88027e4254e4");
given(networkStoreService.getNetwork(testNetworkId, PreloadingStrategy.COLLECTION)).willReturn(createNetwork());
+ given(geoDataService.getSubstationsGraphics(testNetworkId, VARIANT_2_ID, List.of("subFr1"))).willReturn(toString(GEO_DATA_SUBSTATIONS));
SvgAndMetadata svgAndMetadata = networkAreaDiagramService.generateNetworkAreaDiagramSvg(testNetworkId, VARIANT_2_ID, List.of("vlFr1A"), 2);
Object additionalMetadata = svgAndMetadata.getAdditionalMetadata();
@@ -518,6 +552,8 @@ public void testPosisionDiagramLabelProvider() throws IOException {
public void testNetworkAreaDiagramWithMissingVoltageLevel() throws Exception {
UUID testNetworkId = UUID.fromString("7928181c-7977-4592-ba19-88027e4254e4");
given(networkStoreService.getNetwork(testNetworkId, PreloadingStrategy.COLLECTION)).willReturn(createNetwork());
+ given(geoDataService.getSubstationsGraphics(testNetworkId, VARIANT_2_ID, List.of("subFr1"))).willReturn(toString(GEO_DATA_SUBSTATIONS));
+
SvgAndMetadata svgAndMetadata = networkAreaDiagramService.generateNetworkAreaDiagramSvg(testNetworkId, VARIANT_2_ID, List.of("vlFr1A", "vlNotFound1"), 0);
Object additionalMetadata = svgAndMetadata.getAdditionalMetadata();
assertNotNull(additionalMetadata);
@@ -542,6 +578,14 @@ public static String toString(Path outPath) {
return content;
}
+ public String toString(String resourceName) {
+ try {
+ return new String(ByteStreams.toByteArray(Objects.requireNonNull(getClass().getResourceAsStream(resourceName))), StandardCharsets.UTF_8);
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+
/*
#TODO replace it with already configured FourSubstationsNodeBreakerWithExtensionsFactory when migrating to next powsybl release
*/
diff --git a/src/test/resources/geo_data_substations.json b/src/test/resources/geo_data_substations.json
new file mode 100644
index 0000000..390a20f
--- /dev/null
+++ b/src/test/resources/geo_data_substations.json
@@ -0,0 +1,10 @@
+[
+ {
+ "id": "subFr1",
+ "country": "FR",
+ "coordinate": {
+ "lat": 53.19624,
+ "lon": 6.60964
+ }
+ }
+]