Skip to content

Commit

Permalink
Merge pull request #2212 from KS-HTK/feature/geojson
Browse files Browse the repository at this point in the history
Feature/geojson
  • Loading branch information
vogt31337 authored Apr 24, 2024
2 parents 95b41bf + 07cab41 commit ab6d9ee
Show file tree
Hide file tree
Showing 107 changed files with 37,727 additions and 34,460 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,13 @@ Change Log
- [ADDED] add kwargs passing of get_equivalent() to runpp_fct()
- [ADDED] auxiliary functions ets_to_element_types() and element_types_to_ets() as well as toolbox function get_connected_buses_at_switches() and extension to get_connected_switches()
- [FIXED] in function :code:`toolbox.replace_zero_branches_with_switches`, use absolute for the parameters of impedance elements in case they are negative nonzero values
- [FIXED] minor issues in geojson exporter
- [ADDED] geodata to geojson converter
- [CHANGED] dump_to_geojson now requires the geodata to be present in the geo column of a network (see :code:`convert_geodata_to_geojson`)
- [CHANGED] updated simple_plot to use geojson data instead of bus_geo and line_geo
- [CHANGED] minor changes to the example networks (mainly added names to identify them in tests)
- [CHANGED] use of bus_geodata and line_geodata tables to geo column in bus and line table
- [CHANGED] update most geodata dependant functions to use geo column
- [FIXED] in :code:`reindex_elements`: fixed index error when reindexing line_geodata
- [FIXED] bug in :code:`cim2pp`: Changed zero prioritized generators with voltage controller to sgens (like PowerFactory does)
- [ADDED] cim2pp: added description fields for each asset and added BusbarSection information to nodes
Expand Down
3 changes: 2 additions & 1 deletion doc/converter/cgmes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ In the resulting pandapower-network, the following should be noted:
- Each component-table (bus, line, trafo, etc.) will get an "origin_id" column which points to the original CIM CGMES UUIDs.
- If the CGMES model is bus-branch, the pandapower buses will be created from the TopologicalNodes.
- If the CGMES model is node-breaker, the pandapower buses will be created from the ConnectivityNodes.
- If the CGMES model has geo-coordinates (in the GL profile) they will be translated to bus_geodata and line_geodata respectively.
- If the CGMES model has geo-coordinates (in the GL profile) they will be translated to bus.geo and line.geo respectively.
- If the CGMES model has diagram coordinates (in the DL profile) they will be translated to bus.diagram and line.diagram respectively.
- If the CGMES model includes measurements, they will be written to the pandapower measurement-table.


Expand Down
1 change: 1 addition & 0 deletions doc/plotting.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ and `Plotly <https://plot.ly/python/>`_ .
plotting/matplotlib/matplotlib
plotting/plotly/plotly
plotting/html
plotting/geo

21 changes: 21 additions & 0 deletions doc/plotting/geo.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
====================================
Transforming network geodata formats
====================================

Geodata in pandapower can be stored in different formats.


.. autofunction:: pandapower.plotting.geo.convert_gis_to_geodata

.. autofunction:: pandapower.plotting.geo.convert_geodata_to_gis

.. autofunction:: pandapower.plotting.geo.convert_gis_to_geojson

.. autofunction:: pandapower.plotting.geo.convert_epsg_bus_geodata

.. autofunction:: pandapower.plotting.geo.convert_crs

All bus and lines of a network can be dumped to geojson,
including all of their properties from `bus`,`res_bus`, `line` and `res_line` tables.

.. autofunction:: pandapower.plotting.geo.dump_to_geojson
43 changes: 43 additions & 0 deletions format.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{"type": "FeatureCollection", "features": [{
"type": "Feature",
"id": "line-0",
"geometry": {
"type": "LineString",
"coordinates": [[1, 2], [3, 4]]
},
"properties": {
"pp_type": "line",
"pp_index": 0,
"name": "line1",
"std_type": "None",
"from_bus": 1.0,
"to_bus": 7.0,
"length_km": 1.0,
"r_ohm_per_km": 0.2067,
"x_ohm_per_km": 0.1897522,
"c_nf_per_km": 720.0,
"g_us_per_km": 0.0,
"max_i_ka": 0.328,
"df": 1.0,
"parallel": 1.0,
"type": "None",
"in_service": 1.0,
"ices": 0.389985}
},
{
"type": "Feature",
"id": "bus-1",
"geometry": {
"type": "Point",
"coordinates": [1.0, 2.0]
},
"properties": {
"pp_type": "bus",
"pp_index": 1,
"name": "bus2",
"vn_kv": 0.4,
"type": "b",
"zone": "None",
"in_service": 1.0
}
}]}
2 changes: 1 addition & 1 deletion pandapower/_version.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import importlib.metadata

__version__ = importlib.metadata.version("pandapower")
__format_version__ = "2.15.0"
__format_version__ = "3.0.0"
15 changes: 7 additions & 8 deletions pandapower/auxiliary.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import warnings
from importlib.metadata import version as version_str
from importlib.metadata import PackageNotFoundError
from typing_extensions import deprecated

import numpy as np
import pandas as pd
Expand Down Expand Up @@ -304,6 +305,7 @@ def __init__(self, *args, **kwargs):
self[key] = pd.DataFrame(np.zeros(0, dtype=self[key]), index=pd.Index([],
dtype=np.int64))

@deprecated("Use copy.deepcopy(net) instead of net.deepcopy()")
def deepcopy(self):
return copy.deepcopy(self)

Expand All @@ -319,13 +321,12 @@ def __repr__(self): # pragma: no cover
if not et.startswith("_") and isinstance(self[et], pd.DataFrame) and len(self[et]) > 0:
n_rows = self[et].shape[0]
if 'res_' in et:
res.append(" - %s (%i %s)" % (et, n_rows, "element" + plural_s(n_rows)))
res.append(f" - {et} ({n_rows} element{plural_s(n_rows)})")
elif et == 'group':
n_groups = len(set(self[et].index))
par.append(' - %s (%i %s, %i %s)' % (
et, n_groups, "group" + plural_s(n_groups), n_rows, "row" + plural_s(n_rows)))
par.append(f" - {et} ({n_groups} group{plural_s(n_groups)}, {n_rows} row{plural_s(n_rows)})")
else:
par.append(" - %s (%i %s)" % (et, n_rows, "element" + plural_s(n_rows)))
par.append(f" - {et} ({n_rows} element{plural_s(n_rows)})")
res_cost = [" and the following result values:",
" - %s" % "res_cost"] if "res_cost" in self.keys() else []
if not len(par) + len(res):
Expand All @@ -338,10 +339,8 @@ def __repr__(self): # pragma: no cover


def plural_s(number):
if number > 1:
return "s"
else:
return ""
return "" if number == 1 else "s"



def ets_to_element_types(ets=None):
Expand Down
20 changes: 12 additions & 8 deletions pandapower/convert_format.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

from pandapower._version import __version__, __format_version__
from pandapower.create import create_empty_network, create_poly_cost
import pandapower.plotting.geo as geo
from pandapower.results import reset_results

try:
Expand Down Expand Up @@ -36,7 +37,7 @@ def convert_format(net, elements_to_deserialize=None):
_create_seperate_cost_tables(net, elements_to_deserialize)
if Version(str(net.format_version)) < Version("2.4.0"):
_convert_bus_pq_meas_to_load_reference(net, elements_to_deserialize)
if isinstance(net.format_version, float) and net.format_version < 2:
if isinstance(net.format_version, float) and net.format_version < 2: # Why only run if net.format_version is float?
_convert_to_generation_system(net, elements_to_deserialize)
_convert_costs(net)
_convert_to_mw(net)
Expand Down Expand Up @@ -70,7 +71,7 @@ def _restore_index_names(net):

def correct_dtypes(net, error):
"""
Corrects all dtypes of pp element tables if possile. If not and error is True, an Error is
Corrects all dtypes of pp element tables if possible. If not and error is True, an Error is
raised.
"""
empty_net = create_empty_network()
Expand Down Expand Up @@ -266,9 +267,10 @@ def _add_missing_columns(net, elements_to_deserialize):
if "df" not in net[element]:
net[element]["df"] = 1.0

if _check_elements_to_deserialize('bus_geodata', elements_to_deserialize) \
and "coords" not in net.bus_geodata:
net.bus_geodata["coords"] = None
if _check_elements_to_deserialize('bus', elements_to_deserialize) \
and _check_elements_to_deserialize('bus_geodata', elements_to_deserialize) \
and "geo" not in net.bus:
net.bus["geo"] = np.nan
if _check_elements_to_deserialize('trafo3w', elements_to_deserialize) and \
"tap_at_star_point" not in net.trafo3w:
net.trafo3w["tap_at_star_point"] = False
Expand Down Expand Up @@ -298,9 +300,11 @@ def _add_missing_columns(net, elements_to_deserialize):
net.sgen["current_source"] = net.sgen["type"].apply(
func=lambda x: False if x == "motor" else True)

if _check_elements_to_deserialize('line', elements_to_deserialize) and \
"g_us_per_km" not in net.line:
net.line["g_us_per_km"] = 0.
if _check_elements_to_deserialize('line', elements_to_deserialize):
if "g_us_per_km" not in net.line:
net.line["g_us_per_km"] = 0.
if _check_elements_to_deserialize('line_geodata', elements_to_deserialize) and "geo" not in net.line:
net.line["geo"] = np.nan

if _check_elements_to_deserialize('gen', elements_to_deserialize) and \
"slack" not in net.gen:
Expand Down
20 changes: 2 additions & 18 deletions pandapower/converter/cim/cim2pp/build_pp_net.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,18 +125,7 @@ def convert_to_pp(self, convert_line_to_switch: bool = False, line_r_limit: floa
self.classes_dict['powerTransformersCim16'](cimConverter=self).convert_power_transformers_cim16()

# create the geo coordinates
gl_or_dl = str(self.kwargs.get('use_GL_or_DL_profile', 'both')).lower()
if gl_or_dl == 'gl':
use_gl_profile = True
use_dl_profile = False
elif gl_or_dl == 'dl':
use_gl_profile = False
use_dl_profile = True
else:
use_gl_profile = True
use_dl_profile = True
if self.cim['gl']['Location'].index.size > 0 and self.cim['gl']['PositionPoint'].index.size > 0 and \
use_gl_profile:
if self.cim['gl']['Location'].index.size > 0 and self.cim['gl']['PositionPoint'].index.size > 0:
try:
self.classes_dict['geoCoordinatesFromGLCim16'](cimConverter=self).add_geo_coordinates_from_gl_cim16()
except Exception as e:
Expand All @@ -148,11 +137,8 @@ def convert_to_pp(self, convert_line_to_switch: bool = False, line_r_limit: floa
self.report_container.add_log(Report(
level=LogLevel.EXCEPTION, code=ReportCode.EXCEPTION_CONVERTING,
message=traceback.format_exc()))
self.net.bus_geodata = self.net.bus_geodata[0:0]
self.net.line_geodata = self.net.line_geodata[0:0]
if self.cim['dl']['Diagram'].index.size > 0 and self.cim['dl']['DiagramObject'].index.size > 0 and \
self.cim['dl']['DiagramObjectPoint'].index.size > 0 and self.net.bus_geodata.index.size == 0 and \
use_dl_profile:
self.cim['dl']['DiagramObjectPoint'].index.size > 0:
try:
self.classes_dict['coordinatesFromDLCim16'](cimConverter=self).add_coordinates_from_dl_cim16(
diagram_name=kwargs.get('diagram_name', None))
Expand All @@ -164,8 +150,6 @@ def convert_to_pp(self, convert_line_to_switch: bool = False, line_r_limit: floa
message="Creating the coordinates failed, returning the net without coordinates!"))
self.report_container.add_log(Report(level=LogLevel.EXCEPTION, code=ReportCode.EXCEPTION_CONVERTING,
message=traceback.format_exc()))
self.net.bus_geodata = self.net.bus_geodata[0:0]
self.net.line_geodata = self.net.line_geodata[0:0]
self.net = pp_tools.set_pp_col_types(net=self.net)

# create transformer tap controller
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import logging
import time

import numpy as np
import numpy
import pandas as pd

from pandapower.converter.cim import cim_tools
Expand Down Expand Up @@ -41,69 +41,31 @@ def add_coordinates_from_dl_cim16(self, diagram_name: str = None):
# make sure that the columns 'xPosition' and 'yPosition' are floats
dl_data['xPosition'] = dl_data['xPosition'].astype(float)
dl_data['yPosition'] = dl_data['yPosition'].astype(float)
# the coordinates for the buses
buses = self.cimConverter.net.bus.reset_index()
buses = buses[['index', sc['o_id']]]
bus_geo = pd.merge(dl_data, buses, how='inner', left_on='IdentifiedObject', right_on=sc['o_id'])
bus_geo = bus_geo.sort_values(by=[sc['o_id'], 'sequenceNumber'])
bus_geo['coords'] = bus_geo[['xPosition', 'yPosition']].values.tolist()
bus_geo['coords'] = bus_geo[['coords']].values.tolist()
# for the buses which have more than one coordinate
bus_geo_mult = bus_geo[bus_geo.duplicated(subset=sc['o_id'], keep=False)]
# now deal with the buses which have more than one coordinate
for _, df_group in bus_geo_mult.groupby(by=sc['o_id'], sort=False):
bus_geo['coords'][df_group.index.values[0]] = df_group[['xPosition', 'yPosition']].values.tolist()
bus_geo = bus_geo.drop_duplicates([sc['o_id']], keep='first')
bus_geo = bus_geo.sort_values(by='index')
start_index_pp_net = self.cimConverter.net.bus_geodata.index.size
self.cimConverter.net.bus_geodata = pd.concat(
[self.cimConverter.net.bus_geodata, pd.DataFrame(None, index=bus_geo['index'].values)],
ignore_index=False, sort=False)
self.cimConverter.net.bus_geodata.x[start_index_pp_net:] = bus_geo.xPosition[:]
self.cimConverter.net.bus_geodata.y[start_index_pp_net:] = bus_geo.yPosition[:]
self.cimConverter.net.bus_geodata.coords[start_index_pp_net:] = bus_geo.coords[:]
# reduce to max two coordinates for buses (see pandapower documentation for details)
self.cimConverter.net.bus_geodata['coords_length'] = self.cimConverter.net.bus_geodata['coords'].apply(len)
self.cimConverter.net.bus_geodata.loc[
self.cimConverter.net.bus_geodata['coords_length'] == 1, 'coords'] = np.nan
self.cimConverter.net.bus_geodata['coords'] = self.cimConverter.net.bus_geodata.apply(
lambda row: [row['coords'][0], row['coords'][-1]] if row['coords_length'] > 2 else row['coords'], axis=1)
if 'coords_length' in self.cimConverter.net.bus_geodata.columns:
self.cimConverter.net.bus_geodata = self.cimConverter.net.bus_geodata.drop(columns=['coords_length'])
dl_data['coords_str'] = '['+dl_data['xPosition'].astype(str)+', '+dl_data['yPosition'].astype(str)+']'

# the coordinates for the lines
lines = self.cimConverter.net.line.reset_index()
lines = lines[['index', sc['o_id']]]
line_geo = pd.merge(dl_data, lines, how='inner', left_on='IdentifiedObject', right_on=sc['o_id'])
line_geo = line_geo.sort_values(by=[sc['o_id'], 'sequenceNumber'])
line_geo['coords'] = line_geo[['xPosition', 'yPosition']].values.tolist()
line_geo['coords'] = line_geo[['coords']].values.tolist()
for _, df_group in line_geo.groupby(by=sc['o_id']):
line_geo['coords'][df_group.index.values[0]] = df_group[['xPosition', 'yPosition']].values.tolist()
line_geo = line_geo.drop_duplicates([sc['o_id']], keep='first')
line_geo = line_geo.sort_values(by='index')
# now add the line coordinates
# if there are no bus geodata in the GL profile the line geodata from DL has higher priority
if self.cimConverter.net.line_geodata.index.size > 0 and line_geo.index.size > 0:
self.cimConverter.net.line_geodata = self.cimConverter.net.line_geodata[0:0]
self.cimConverter.net.line_geodata = pd.concat(
[self.cimConverter.net.line_geodata, line_geo[['coords', 'index']].set_index('index')],
ignore_index=False, sort=False)

# now create coordinates which are official not supported by pandapower, e.g. for transformer
for one_ele in ['trafo', 'trafo3w', 'switch', 'ext_grid', 'load', 'sgen', 'gen', 'impedance', 'dcline', 'shunt',
'storage', 'ward', 'xward']:
# create coordinates for the different assets bus, line, transformer
for one_ele in ['bus', 'trafo', 'trafo3w', 'switch', 'ext_grid', 'load', 'sgen', 'gen', 'line', 'dcline',
'impedance', 'shunt', 'storage', 'ward', 'xward']:
one_ele_df = self.cimConverter.net[one_ele][[sc['o_id']]]
one_ele_df = pd.merge(dl_data, one_ele_df, how='inner', left_on='IdentifiedObject', right_on=sc['o_id'])
one_ele_df = one_ele_df.sort_values(by=[sc['o_id'], 'sequenceNumber'])
one_ele_df['coords'] = one_ele_df[['xPosition', 'yPosition']].values.tolist()
one_ele_df['coords'] = one_ele_df[['coords']].values.tolist()
for _, df_group in one_ele_df.groupby(by=sc['o_id']):
one_ele_df['coords'][df_group.index.values[0]] = df_group[['xPosition', 'yPosition']].values.tolist()
one_ele_df = one_ele_df.drop_duplicates([sc['o_id']], keep='first')
# now add the coordinates
self.cimConverter.net[one_ele]['coords'] = self.cimConverter.net[one_ele][sc['o_id']].map(
one_ele_df.set_index(sc['o_id']).to_dict(orient='dict').get('coords'))
one_ele_df.sort_values(by=[sc['o_id'], 'sequenceNumber'], inplace=True)
if one_ele != 'line' and one_ele != 'dcline' and one_ele != 'impedance':
# only one coordinate for each asset (except line and impedance)
one_ele_df = one_ele_df.drop_duplicates([sc['o_id']], keep='first')
one_ele_df['diagram'] = '{"coordinates": ' + one_ele_df["coords_str"] + ', "type": "Point"}'
else:
# line strings
one_ele_df['coords'] = one_ele_df[['xPosition', 'yPosition']].values.tolist()
one_ele_df['coords'] = one_ele_df[['coords']].values.tolist()
for _, df_group in one_ele_df.groupby(by=sc['o_id']):
one_ele_df['coords'][df_group.index.values[0]] = df_group[
['xPosition', 'yPosition']].values.tolist()
one_ele_df.drop_duplicates([sc['o_id']], keep='first', inplace=True)
one_ele_df['coords'] = one_ele_df['coords'].astype(str)
one_ele_df['diagram'] = '{"coordinates": '+one_ele_df['coords'].astype(str)+', "type": "LineString"}'

self.cimConverter.net[one_ele]['diagram'] = self.cimConverter.net[one_ele][sc['o_id']].map(
one_ele_df.set_index(sc['o_id']).to_dict(orient='dict').get('diagram'))

self.logger.info("Finished creating the DL coordinates, needed time: %ss" % (time.time() - time_start))
self.cimConverter.report_container.add_log(Report(
Expand Down
Loading

0 comments on commit ab6d9ee

Please sign in to comment.