Skip to content

Commit

Permalink
CGMES GL profile support (#741)
Browse files Browse the repository at this point in the history
Signed-off-by: Geoffroy Jamgotchian <[email protected]>
  • Loading branch information
geofjamg authored May 3, 2024
1 parent bffd5f8 commit fff42fb
Show file tree
Hide file tree
Showing 22 changed files with 1,791 additions and 11 deletions.
9 changes: 8 additions & 1 deletion cpp/src/bindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,10 @@ PYBIND11_MODULE(_pypowsybl, m) {
.def_readwrite("topological_coloring", &pypowsybl::SldParameters::topological_coloring)
.def_readwrite("component_library", &pypowsybl::SldParameters::component_library);

py::enum_<pypowsybl::NadLayoutType>(m, "NadLayoutType")
.value("FORCE_LAYOUT", pypowsybl::NadLayoutType::FORCE_LAYOUT)
.value("GEOGRAPHICAL", pypowsybl::NadLayoutType::GEOGRAPHICAL);

py::class_<pypowsybl::NadParameters>(m, "NadParameters")
.def(py::init(&pypowsybl::createNadParameters))
.def_readwrite("edge_name_displayed", &pypowsybl::NadParameters::edge_name_displayed)
Expand All @@ -496,7 +500,10 @@ PYBIND11_MODULE(_pypowsybl, m) {
.def_readwrite("voltage_value_precision", &pypowsybl::NadParameters::voltage_value_precision)
.def_readwrite("id_displayed", &pypowsybl::NadParameters::id_displayed)
.def_readwrite("bus_legend", &pypowsybl::NadParameters::bus_legend)
.def_readwrite("substation_description_displayed", &pypowsybl::NadParameters::substation_description_displayed);
.def_readwrite("substation_description_displayed", &pypowsybl::NadParameters::substation_description_displayed)
.def_readwrite("layout_type", &pypowsybl::NadParameters::layout_type)
.def_readwrite("scaling_factor", &pypowsybl::NadParameters::scaling_factor)
.def_readwrite("radius_factor", &pypowsybl::NadParameters::radius_factor);

m.def("write_single_line_diagram_svg", &pypowsybl::writeSingleLineDiagramSvg, "Write single line diagram SVG",
py::arg("network"), py::arg("container_id"), py::arg("svg_file"), py::arg("metadata_file"), py::arg("sld_parameters"));
Expand Down
3 changes: 3 additions & 0 deletions cpp/src/pypowsybl-api.h
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,9 @@ typedef struct nad_parameters_struct {
int voltage_value_precision;
unsigned char substation_description_displayed;
unsigned char bus_legend;
int layout_type;
int scaling_factor;
double radius_factor;
} nad_parameters;

typedef enum {
Expand Down
6 changes: 6 additions & 0 deletions cpp/src/pypowsybl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1333,6 +1333,9 @@ NadParameters::NadParameters(nad_parameters* src) {
voltage_value_precision = src->voltage_value_precision;
substation_description_displayed = src->substation_description_displayed;
bus_legend = src->bus_legend;
layout_type = static_cast<NadLayoutType>(src->layout_type);
scaling_factor = src->scaling_factor;
radius_factor = src->radius_factor;
}

void SldParameters::sld_to_c_struct(sld_parameters& res) const {
Expand All @@ -1355,6 +1358,9 @@ void NadParameters::nad_to_c_struct(nad_parameters& res) const {
res.voltage_value_precision = voltage_value_precision;
res.substation_description_displayed = substation_description_displayed;
res.bus_legend = bus_legend;
res.layout_type = (int) layout_type;
res.scaling_factor = scaling_factor;
res.radius_factor = radius_factor;
}

std::shared_ptr<sld_parameters> SldParameters::to_c_struct() const {
Expand Down
8 changes: 8 additions & 0 deletions cpp/src/pypowsybl.h
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,11 @@ class SldParameters {
std::string component_library;
};

enum class NadLayoutType {
FORCE_LAYOUT = 0,
GEOGRAPHICAL
};

class NadParameters {
public:
NadParameters(nad_parameters* src);
Expand All @@ -296,6 +301,9 @@ class NadParameters {
int voltage_value_precision;
bool bus_legend;
bool substation_description_displayed;
NadLayoutType layout_type;
int scaling_factor;
double radius_factor;
};

char* copyStringToCharPtr(const std::string& str);
Expand Down
Binary file not shown.
605 changes: 605 additions & 0 deletions docs/_static/images/nad_microgridbe_force_layout.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
605 changes: 605 additions & 0 deletions docs/_static/images/nad_microgridbe_geo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
60 changes: 59 additions & 1 deletion docs/user_guide/network_visualization.rst
Original file line number Diff line number Diff line change
Expand Up @@ -119,4 +119,62 @@ In order to get a list of the displayed voltage levels from an input voltage lev
.. code-block:: python
>>> network = pp.network.create_ieee300()
>>> list_vl = network.get_network_area_diagram_displayed_voltage_levels('VL1', 1)
>>> list_vl = network.get_network_area_diagram_displayed_voltage_levels('VL1', 1)
Network area diagram using geographical data
--------------------------------------------
We can load a network with geographical data (in WGS84 coordinates system) for substations and lines (in that case,
the geographical positions represent the line path). One way to do that is to load a CGMES file containing
a GL profile (Graphical Layout). By default this profile is not read. To activate GL profile loading and
creation of substations ans lines geographical positions in the PowSyBl network model we have to pass an
additional parameter to the load function.
.. code-block:: python
>>> network = pp.network.load('MicroGridTestConfiguration_T4_BE_BB_Complete_v2.zip', {'iidm.import.cgmes.post-processors': 'cgmesGLImport'})
We can now check loaded position by displaying `SubstationPosition` and `LinePosition` extensions.
.. code-block:: python
>>> n.get_extension('substationPosition')
latitude longitude
id
87f7002b-056f-4a6a-a872-1744eea757e3 51.3251 4.25926
37e14a0f-5e34-4647-a062-8bfd9305fa9d 50.8038 4.30089
.. code-block:: python
>>> n.get_extension('linePosition')
latitude longitude
id num
b58bf21a-096a-4dae-9a01-3f03b60c24c7 0 50.8035 4.30113
1 50.9169 4.34509
2 51.0448 4.29565
3 51.1570 4.38354
ffbabc27-1ccd-4fdc-b037-e341706c8d29 0 50.8035 4.30113
1 50.9169 4.34509
2 51.0448 4.29565
3 51.1570 4.38354
When we generate a network area diagram, an automatic force layout is performed by default.
The diagram looks like this:
.. code-block:: python
>>> n.write_network_area_diagram('be.svg')
.. image:: ../_static/images/nad_microgridbe_force_layout.svg
:class: forced-white-background
Now that we have geographical positions in our data model, we can change the layout to render the diagram with
the geographical layout:
.. code-block:: python
>>> parameter = pp.network.NadParameters(layout_type=pp.network.NadLayoutType.GEOGRAPHICAL)
>>> n.write_network_area_diagram('be.svg', nad_parameters=parameter)
.. image:: ../_static/images/nad_microgridbe_geo.svg
:class: forced-white-background
5 changes: 5 additions & 0 deletions java/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,11 @@
<artifactId>powsybl-cgmes-conversion</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.powsybl</groupId>
<artifactId>powsybl-cgmes-gl</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.powsybl</groupId>
<artifactId>powsybl-config-classic</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/**
* 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.dataframe.network.extensions;

import com.powsybl.commons.PowsyblException;
import com.powsybl.dataframe.SeriesMetadata;
import com.powsybl.dataframe.network.adders.AbstractSimpleAdder;
import com.powsybl.dataframe.update.DoubleSeries;
import com.powsybl.dataframe.update.IntSeries;
import com.powsybl.dataframe.update.StringSeries;
import com.powsybl.dataframe.update.UpdatingDataframe;
import com.powsybl.iidm.network.Line;
import com.powsybl.iidm.network.Network;
import com.powsybl.iidm.network.extensions.Coordinate;
import com.powsybl.iidm.network.extensions.LinePositionAdder;

import java.util.*;

/**
* @author Geoffroy Jamgotchian <geoffroy.jamgotchian at rte-france.com>
*/
public class LinePositionDataframeAdder extends AbstractSimpleAdder {

private static final List<SeriesMetadata> METADATA = List.of(
SeriesMetadata.stringIndex("id"),
SeriesMetadata.intIndex("num"),
SeriesMetadata.doubles("latitude"),
SeriesMetadata.doubles("longitude")
);

@Override
public List<List<SeriesMetadata>> getMetadata() {
return Collections.singletonList(METADATA);
}

private static Map<String, List<Coordinate>> readCoordinates(UpdatingDataframe dataframe) {
StringSeries idCol = dataframe.getStrings("id");
IntSeries numCol = dataframe.getInts("num");
DoubleSeries latitudeCol = dataframe.getDoubles("latitude");
DoubleSeries longitudeCol = dataframe.getDoubles("longitude");
Map<String, List<Coordinate>> coordinatesByLineId = new HashMap<>();
if (numCol != null && latitudeCol != null && longitudeCol != null) {
for (int row = 0; row < dataframe.getRowCount(); row++) {
String id = idCol.get(row);
int num = numCol.get(row);
double latitude = latitudeCol.get(row);
double longitude = longitudeCol.get(row);
List<Coordinate> coordinates = coordinatesByLineId.computeIfAbsent(id, k -> new ArrayList<>());
// ensure list can store coordinate at num index
if (num > coordinates.size()) {
for (int i = 0; i < num - coordinates.size(); i++) {
coordinates.add(null);
}
}
coordinates.set(num, new Coordinate(latitude, longitude));
}
}
return coordinatesByLineId;
}

@Override
public void addElements(Network network, UpdatingDataframe dataframe) {
Map<String, List<Coordinate>> coordinatesByLineId = readCoordinates(dataframe);
for (var e : coordinatesByLineId.entrySet()) {
String id = e.getKey();
List<Coordinate> coordinates = e.getValue();
Line l = network.getLine(id);
if (l == null) {
throw new PowsyblException("Line '" + id + "' not found");
}
// check there is no hole in the coordinate list
for (int num = 0; num < coordinates.size(); num++) {
Coordinate coordinate = coordinates.get(num);
if (coordinate == null) {
throw new PowsyblException("Missing coordinate at " + num + " for line '" + id + "'");
}
}
l.newExtension(LinePositionAdder.class)
.withCoordinates(coordinates)
.add();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/**
* 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.dataframe.network.extensions;

import com.google.auto.service.AutoService;
import com.powsybl.commons.PowsyblException;
import com.powsybl.dataframe.BaseDataframeMapperBuilder;
import com.powsybl.dataframe.network.ExtensionInformation;
import com.powsybl.dataframe.network.NetworkDataframeMapper;
import com.powsybl.dataframe.network.NetworkDataframeMapperBuilder;
import com.powsybl.dataframe.network.adders.NetworkElementAdder;
import com.powsybl.dataframe.update.UpdatingDataframe;
import com.powsybl.iidm.network.Identifiable;
import com.powsybl.iidm.network.Line;
import com.powsybl.iidm.network.Network;
import com.powsybl.iidm.network.extensions.Coordinate;
import com.powsybl.iidm.network.extensions.LinePosition;

import java.util.List;
import java.util.Objects;
import java.util.stream.IntStream;
import java.util.stream.Stream;

/**
* @author Geoffroy Jamgotchian <geoffroy.jamgotchian at rte-france.com>
*/
@AutoService(NetworkExtensionDataframeProvider.class)
public class LinePositionDataframeProvider extends AbstractSingleDataframeNetworkExtension {

@Override
public String getExtensionName() {
return LinePosition.NAME;
}

@Override
public ExtensionInformation getExtensionInformation() {
return new ExtensionInformation(LinePosition.NAME,
"Provides information about the line geographical coordinates",
"index : id (str), num (int), latitude (float), longitude (float)");
}

record LineCoordinate(LinePosition linePosition, Integer num) {
}

private Stream<LineCoordinate> itemsStream(Network network) {
return network.getLineStream()
.map(g -> (LinePosition) g.getExtension(LinePosition.class))
.filter(Objects::nonNull)
.flatMap(lp -> IntStream.range(0, lp.getCoordinates().size() - 1).mapToObj(num -> new LineCoordinate(lp, num)));
}

private static class LineCoordinateGetter implements BaseDataframeMapperBuilder.ItemGetter<Network, LineCoordinate> {

@Override
public LineCoordinate getItem(Network network, UpdatingDataframe updatingDataframe, int row) {
String id = updatingDataframe.getStringValue("id", row).orElseThrow();
Line l = network.getLine(id);
if (l == null) {
throw new PowsyblException("Line '" + id + "' not found");
}
LinePosition lp = l.getExtension(LinePosition.class);
if (lp == null) {
throw new PowsyblException("Line '" + id + "' has no LinePosition extension");
}
int num = updatingDataframe.getIntValue("id", row).orElseThrow();
return new LineCoordinate(lp, num);
}
}

@Override
public NetworkDataframeMapper createMapper() {
return NetworkDataframeMapperBuilder.ofStream(this::itemsStream, new LineCoordinateGetter())
.stringsIndex("id", lc -> ((Identifiable<?>) lc.linePosition().getExtendable()).getId())
.intsIndex("num", lc -> lc.num)
.doubles("latitude", lc -> ((Coordinate) lc.linePosition.getCoordinates().get(lc.num)).getLatitude())
.doubles("longitude", lc -> ((Coordinate) lc.linePosition.getCoordinates().get(lc.num)).getLongitude())
.build();
}

@Override
public void removeExtensions(Network network, List<String> ids) {
ids.stream().filter(Objects::nonNull)
.map(network::getIdentifiable)
.filter(Objects::nonNull)
.forEach(g -> g.removeExtension(LinePosition.class));
}

@Override
public NetworkElementAdder createAdder() {
return new LinePositionDataframeAdder();
}
}
Loading

0 comments on commit fff42fb

Please sign in to comment.