-
Notifications
You must be signed in to change notification settings - Fork 0
/
geo.py
204 lines (184 loc) · 8.17 KB
/
geo.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
# -*- coding: utf-8 -*-
"""
this code is part of PR #1731 at e2nIEE/pandapower
when the PR has been merged this file should be removed and its call replaced in ppqgis_import.py
https://github.com/e2nIEE/pandapower/blob/develop/pandapower/plotting/geo.py
"""
import sys
import math
import pandas as pd
from pandapower.auxiliary import soft_dependency_error
try:
from pyproj import Transformer
pyproj_INSTALLED = True
except ImportError:
pyproj_INSTALLED = False
try:
import geojson
geojson_INSTALLED = True
except ImportError:
geojson_INSTALLED = False
def convert_crs(net, epsg_in=4326, epsg_out=31467):
"""
This function works for pandapowerNet and pandapipesNet. Documentation will refer to names from pandapower.
Converts bus and line geodata in net from epsg_in to epsg_out
if GeoDataFrame data is present convert_geodata_to_gis should be used to update geometries after crs conversion
:param net: The pandapower network
:type net: pandapowerNet|pandapipesNet
:param epsg_in: current epsg projection
:type epsg_in: int, default 4326 (= WGS84)
:param epsg_out: epsg projection to be transformed to
:type epsg_out: int, default 31467 (= Gauss-Krüger Zone 3)
:return: net - the given pandapower network (no copy!)
"""
is_pandapower = net.__class__.__name__ == 'pandapowerNet'
if epsg_in == epsg_out:
return
if not pyproj_INSTALLED:
soft_dependency_error(str(sys._getframe().f_code.co_name)+"()", "pyproj")
transformer = Transformer.from_crs(epsg_in, epsg_out, always_xy=True)
def _geo_node_transformer(r):
(x, y) = transformer.transform(r.x, r.y)
if is_pandapower:
coords = r.coords
if coords and not pd.isna(coords):
coords = _geo_branch_transformer(coords)
return pd.Series([x, y, coords], ["x", "y", "coords"])
else:
return pd.Series([x, y], ["x", "y"])
def _geo_branch_transformer(r):
return list(transformer.itransform(r))
if is_pandapower:
net.bus_geodata = net.bus_geodata.apply(lambda r: _geo_node_transformer(r), axis=1)
net.line_geodata.coords = net.line_geodata.coords.apply(lambda r: _geo_branch_transformer(r))
net.bus_geodata.attrs = {"crs": f"EPSG:{epsg_out}"}
net.line_geodata.attrs = {"crs": f"EPSG:{epsg_out}"}
else:
net.junction_geodata = net.junction_geodata.apply(lambda r: _geo_node_transformer(r), axis=1)
net.pipe_geodata.coords = net.pipe_geodata.coords.apply(lambda r: _geo_branch_transformer(r))
net.junction_geodata.attrs = {"crs": f"EPSG:{epsg_out}"}
net.pipe_geodata.attrs = {"crs": f"EPSG:{epsg_out}"}
def dump_to_geojson(net, nodes=False, branches=False):
"""
This function works for pandapowerNet and pandapipesNet. Documentation will refer to names from pandapower.
Dumps all primitive values from bus, bus_geodata, res_bus, line, line_geodata and res_line into a geojson object.
It is recommended to only dump networks using WGS84 for GeoJSON specification compliance.
:param net: The pandapower network
:type net: pandapowerNet|pandapipesNet
:param nodes: if True return contains all bus data, can be a list of bus ids that should be contained
:type nodes: bool | list, default True
:param branches: if True return contains all line data, can be a list of line ids that should be contained
:type branches: bool | list, default True
:return: geojson
:return type: geojson.FeatureCollection
"""
is_pandapower = net.__class__.__name__ == 'pandapowerNet'
if is_pandapower:
node_geodata = net.bus_geodata
branch_geodata = net.line_geodata
else:
node_geodata = net.junction_geodata
branch_geodata = net.pipe_geodata
if not geojson_INSTALLED:
soft_dependency_error(str(sys._getframe().f_code.co_name) + "()", "geojson")
features = []
# build geojson features for nodes
if nodes:
props = {}
for table in (['bus', 'res_bus'] if is_pandapower else ['junction', 'res_junction']):
if table not in net.keys():
continue
cols = net[table].columns
# I use uid for the id of the feature, but it is NOT a unique identifier in the geojson structure,
# as line and bus (pipe and junction) can have same ids.
for uid, row in net[table].iterrows():
prop = {
'pp_type': 'bus' if is_pandapower else 'junction',
'pp_index': uid,
}
for c in cols:
try:
prop[c] = float(row[c])
if math.isnan(prop[c]):
prop[c] = None
except (ValueError, TypeError):
prop[c] = str(row[c])
if uid not in props:
props[uid] = {}
props[uid].update(prop)
if isinstance(nodes, bool):
iterator = node_geodata.iterrows()
else:
iterator = node_geodata.loc[nodes].iterrows()
for uid, row in iterator:
if is_pandapower and row.coords is not None and not pd.isna(row.coords):
# [(x, y), (x2, y2)] start and end of bus bar
geom = geojson.LineString(row.coords)
else:
# this is just a bus with x, y
geom = geojson.Point((row.x, row.y))
features.append(geojson.Feature(geometry=geom, id=uid, properties=props[uid]))
# build geojson features for branches
if branches:
props = {}
for table in (['line', 'res_line'] if is_pandapower else ['pipe', 'res_pipe']):
if table not in net.keys():
continue
cols = net[table].columns
for uid, row in net[table].iterrows():
prop = {
'pp_type': 'line' if is_pandapower else 'pipe',
'pp_index': uid,
}
for c in cols:
try:
prop[c] = float(row[c])
if math.isnan(prop[c]):
prop[c] = None
except (ValueError, TypeError):
prop[c] = str(row[c])
if uid not in props:
props[uid] = {}
props[uid].update(prop)
# Iterating over pipe_geodata won't work
# pipe_geodata only contains pipes that have inflection points!
if isinstance(branches, bool):
# if all iterating over pipe
iterator = net.line_geodata.iterrows() if is_pandapower else net.pipe.iterrows()
else:
iterator = net.line_geodata.loc[branches].iterrows() if is_pandapower else net.pipe.loc[branches].iterrows()
for uid, row in iterator:
if not is_pandapower:
coords = []
from_coords = net.junction_geodata.loc[row.from_junction]
to_coords = net.junction_geodata.loc[row.to_junction]
coords.append([float(from_coords.x), float(from_coords.y)])
if uid in net.pipe_geodata:
coords.append(net.pipe_geodata.loc[uid].coords)
coords.append([float(to_coords.x), float(to_coords.y)])
geom = geojson.LineString(row.coords if is_pandapower else coords)
features.append(geojson.Feature(geometry=geom, id=uid, properties=props[uid]))
# find and set crs if available
crs_node = None
if nodes and "crs" in node_geodata.attrs:
crs_node = node_geodata.attrs["crs"]
crs_branch = None
if branches and "crs" in branch_geodata.attrs:
crs_branch = branch_geodata.attrs["crs"]
crs = {
"type": "name",
"properties": {
"name": ""
}
}
if crs_node:
if crs_branch and crs_branch != crs_node:
raise ValueError("Node and Branch crs mismatch")
crs["properties"]["name"] = crs_node
elif crs_branch:
crs["properties"]["name"] = crs_branch
else:
crs = None
if crs:
return geojson.FeatureCollection(features, crs=crs)
return geojson.FeatureCollection(features)