Skip to content

Commit 5ba2318

Browse files
Merge branch 'yaramo2' into operations
2 parents bad586c + 0357012 commit 5ba2318

File tree

7 files changed

+159
-24
lines changed

7 files changed

+159
-24
lines changed

test/helper.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
from yaramo.model import Edge, Node, Wgs84GeoNode
1+
from yaramo.model import Edge, EuclideanGeoNode, Node
22

33

44
def create_geo_node(x, y):
5-
return Wgs84GeoNode(x, y)
5+
return EuclideanGeoNode(x, y)
66

77

88
def create_node(x, y):

test/node_test.py

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22

33
from pytest import raises
44

5-
from yaramo.geo_node import Wgs84GeoNode
6-
from yaramo.model import Node
5+
import yaramo.utils.coordinateconversion
6+
from yaramo.model import DbrefGeoNode, Node, Wgs84GeoNode
77

88
from .helper import create_edge, create_node
99

@@ -151,3 +151,34 @@ def test_implausible_anschluss():
151151

152152
with raises(Exception) as exception:
153153
point.calc_anschluss_of_all_nodes()
154+
155+
156+
def test_wgs84_dbref_coordinate_conversion():
157+
def _test_distance(_dbref_x, _dbref_y, _wgs84_x, _wgs84_y, factor, smaller_as):
158+
wgs84_geo_node = Wgs84GeoNode(wgs84_x, wgs84_y)
159+
dbref_geo_node = DbrefGeoNode(dbref_x, dbref_y)
160+
assert wgs84_geo_node.get_distance_to_other_geo_node(dbref_geo_node) * factor < smaller_as
161+
162+
# Example 1:
163+
dbref_x = 4563230.251887853
164+
dbref_y = 5601992.441701063
165+
wgs84_x = 50.55025861737121
166+
wgs84_y = 12.890666340087865
167+
168+
_test_distance(dbref_x, dbref_y, wgs84_x, wgs84_y, 1000 * 1000, 2)
169+
170+
# Example 2:
171+
dbref_x = 4563437.90802
172+
dbref_y = 5601946.51808
173+
wgs84_x = 50.54982337298292
174+
wgs84_y = 12.893588101538324
175+
176+
_test_distance(dbref_x, dbref_y, wgs84_x, wgs84_y, 1000 * 1000, 2)
177+
178+
# Example 3: (close-by)
179+
dbref_x = 4564720.99586
180+
dbref_y = 5601735.46336
181+
wgs84_x = 50.5477883
182+
wgs84_y = 12.9116547
183+
184+
_test_distance(dbref_x, dbref_y, wgs84_x, wgs84_y, 1, 0.3)

yaramo/edge.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,15 @@ def get_other_node(self, node: Node) -> Node:
5858
return self.node_b
5959
return self.node_a
6060

61-
def update_length(self):
61+
def update_length(self, force: bool = False):
62+
if force:
63+
self.length = None
6264
self.length = self.__get_length()
6365

6466
def __get_length(self) -> float:
67+
if self.length is not None:
68+
return self.length
69+
6570
if len(self.intermediate_geo_nodes) == 0:
6671
return self.node_a.geo_node.get_distance_to_other_geo_node(self.node_b.geo_node)
6772

yaramo/geo_node.py

Lines changed: 52 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,24 @@
11
import math
22
from abc import ABC, abstractmethod
33

4-
import pyproj
54
from haversine import Unit, haversine
65

76
from yaramo.base_element import BaseElement
87

8+
from .utils.coordinateconversion import transform_dbref_to_wgs84, transform_wgs84_to_dbref
9+
910

1011
class GeoNode(ABC, BaseElement):
1112
"""This is the baseclass of specific GeoNodes that use different coordinate systems.
1213
1314
A GeoNode is characterized by it's x and y coordinates.
1415
"""
1516

16-
def __init__(self, x, y, **kwargs):
17+
def __init__(self, x, y, dbref_crs: str = "ER0", **kwargs):
1718
super().__init__(**kwargs)
1819
self.x = x
1920
self.y = y
21+
self.dbref_crs = dbref_crs
2022

2123
@abstractmethod
2224
def get_distance_to_other_geo_node(self, geo_node_b: "GeoNode"):
@@ -31,36 +33,63 @@ def to_wgs84(self) -> "Wgs84GeoNode":
3133
def to_dbref(self) -> "DbrefGeoNode":
3234
pass
3335

36+
@abstractmethod
37+
def to_euclidean(self) -> "EuclideanGeoNode":
38+
pass
39+
3440
def to_serializable(self):
3541
return self.__dict__, {}
3642

3743

3844
class Wgs84GeoNode(GeoNode):
39-
def get_distance_to_other_geo_node(self, geo_node_b: "Wgs84GeoNode"):
40-
assert type(self) == type(
41-
geo_node_b
42-
), "You cannot calculate the distance between a Wgs84GeoNode and a DbrefGeoNode!"
45+
def get_distance_to_other_geo_node(self, geo_node_b: "GeoNode"):
46+
geo_node_b = geo_node_b.to_wgs84()
4347
return self.__haversine_distance(geo_node_b)
4448

4549
def __haversine_distance(self, geo_node_b: "GeoNode"):
4650
own = (self.x, self.y)
4751
other = (geo_node_b.x, geo_node_b.y)
4852
return haversine(own, other, unit=Unit.METERS)
4953

50-
def to_wgs84(self):
54+
def to_wgs84(self) -> "Wgs84GeoNode":
5155
return self
5256

53-
def to_dbref(self):
54-
transformer = pyproj.Transformer.from_crs("epsg:4326", "epsg:31468")
55-
x, y = transformer.transform(self.y, self.x)
56-
return DbrefGeoNode(x, y)
57+
def to_dbref(self) -> "DbrefGeoNode":
58+
x, y = transform_wgs84_to_dbref(self.x, self.y, self.dbref_crs)
59+
return DbrefGeoNode(x, y, self.dbref_crs)
60+
61+
def to_euclidean(self) -> "EuclideanGeoNode":
62+
return self.to_dbref().to_euclidean()
5763

5864

5965
class DbrefGeoNode(GeoNode):
60-
def get_distance_to_other_geo_node(self, geo_node_b: "DbrefGeoNode"):
61-
assert type(self) == type(
62-
geo_node_b
63-
), "You cannot calculate the distance between a DbrefGeoNode and a Wgs84GeoNode!"
66+
def get_distance_to_other_geo_node(self, geo_node_b: "GeoNode"):
67+
# Separate DB Ref distance method not implemented yet, therefore use WGS84 distance
68+
return self.to_wgs84().get_distance_to_other_geo_node(geo_node_b)
69+
70+
def to_wgs84(self) -> "Wgs84GeoNode":
71+
x, y = transform_dbref_to_wgs84(self.x, self.y, self.dbref_crs)
72+
return Wgs84GeoNode(x, y, self.dbref_crs)
73+
74+
def to_dbref(self) -> "DbrefGeoNode":
75+
return self
76+
77+
def to_euclidean(self) -> "EuclideanGeoNode":
78+
# This transformation is just for testing purposes and not correct, see documentation in EuclideanGeoNode.
79+
_x_shift = 4533770.0
80+
_y_shift = 5625780.0
81+
return EuclideanGeoNode(self.x - _x_shift, self.y - _y_shift, self.dbref_crs)
82+
83+
84+
class EuclideanGeoNode(GeoNode):
85+
"""
86+
The Euclidean geo node is a simple geo node based on the simple concept of x and y coordinates and
87+
the Euclidean distance between two geo nodes. It's mainly for testing purposes and can only be converted to
88+
DBRef geo nodes, but the conversion is just a simple coordinate shift and with this, probably incorrect.
89+
"""
90+
91+
def get_distance_to_other_geo_node(self, geo_node_b: "EuclideanGeoNode"):
92+
geo_node_b = geo_node_b.to_euclidean()
6493
return self.__eucldian_distance(geo_node_b)
6594

6695
def __eucldian_distance(self, geo_node_b: "GeoNode"):
@@ -70,8 +99,13 @@ def __eucldian_distance(self, geo_node_b: "GeoNode"):
7099
max_y = max(self.y, geo_node_b.y)
71100
return math.sqrt(math.pow(max_x - min_x, 2) + math.pow(max_y - min_y, 2))
72101

73-
def to_wgs84(self):
74-
raise NotImplementedError
102+
def to_wgs84(self) -> "Wgs84GeoNode":
103+
return self.to_dbref().to_wgs84()
104+
105+
def to_dbref(self) -> "DbrefGeoNode":
106+
_x_shift = 4533770.0
107+
_y_shift = 5625780.0
108+
return DbrefGeoNode(self.x + _x_shift, self.y + _y_shift, self.dbref_crs)
75109

76-
def to_dbref(self):
110+
def to_euclidean(self) -> "EuclideanGeoNode":
77111
return self

yaramo/model.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from yaramo.edge import Edge # pylint: noqa
2-
from yaramo.geo_node import DbrefGeoNode, GeoNode, Wgs84GeoNode # pylint: noqa
2+
from yaramo.geo_node import DbrefGeoNode, EuclideanGeoNode, GeoNode, Wgs84GeoNode # pylint: noqa
33
from yaramo.node import EdgeConnectionDirection, Node # pylint: noqa
44
from yaramo.route import Route # pylint: noqa
55
from yaramo.signal import ( # pylint: noqa

yaramo/utils/__init__.py

Whitespace-only changes.
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
"""
2+
Copyright (c) 2021 DB Netz AG and others.
3+
4+
All rights reserved. This program and the accompanying materials
5+
are made available under the terms of the Eclipse Public License v2.0
6+
which accompanies this distribution, and is available at
7+
http://www.eclipse.org/legal/epl-v20.html
8+
9+
10+
-------------
11+
12+
The DBRef and WGS84 conversions are taken from the Eclipse Signalling Engineering Toolbox (https://github.com/eclipse-set/set/tree/main)
13+
14+
See
15+
https://github.com/eclipse-set/set/blob/main/java/bundles/org.eclipse.set.feature.siteplan/src/org/eclipse/set/feature/siteplan/positionservice/PositionServiceImpl.java
16+
and
17+
https://github.com/eclipse-set/set/blob/main/java/bundles/org.eclipse.set.ppmodel.extensions/src/org/eclipse/set/ppmodel/extensions/geometry/CoordinateExtensions.xtend
18+
for the original sources.
19+
"""
20+
import pyproj
21+
22+
EPSG_3857_STRING = "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs"
23+
24+
25+
def transform_dbref_to_wgs84(x: float, y: float, coordinate_str: str):
26+
proj_dbref_str = __get_proj_dbref_str(coordinate_str)
27+
28+
# Step 1 DBRef to Pseudo-Mercator:
29+
transformer = pyproj.Transformer.from_crs(crs_from=proj_dbref_str, crs_to=EPSG_3857_STRING)
30+
x, y = transformer.transform(x, y)
31+
32+
# Step 2 Pseudo-Mercator to WGS84:
33+
transformer_2 = pyproj.Transformer.from_crs(crs_from=3857, crs_to=4326)
34+
return transformer_2.transform(x, y)
35+
36+
37+
def transform_wgs84_to_dbref(x: float, y: float, coordinate_str: str):
38+
proj_dbref_str = __get_proj_dbref_str(coordinate_str)
39+
40+
# Step 2 WGS84 to Pseudo-Mercator:
41+
transformer_2 = pyproj.Transformer.from_crs(crs_from=4326, crs_to=3857)
42+
x, y = transformer_2.transform(x, y)
43+
44+
# Step 1 Pseudo-Mercator to DBRef:
45+
transformer = pyproj.Transformer.from_crs(crs_from=EPSG_3857_STRING, crs_to=proj_dbref_str)
46+
return transformer.transform(x, y)
47+
48+
49+
def __get_proj_dbref_str(coordinate_str: str):
50+
CRS_CR0_STRING = "+proj=tmerc +lat_0=0 +lon_0=6 +k=1 +x_0=2500000 +y_0=0 +ellps=bessel +towgs84=598.1,73.7,418.2,0.202,0.045,-2.455,6.7 +units=m +no_defs"
51+
CRS_DR0_STRING = "+proj=tmerc +lat_0=0 +lon_0=9 +k=1 +x_0=3500000 +y_0=0 +ellps=bessel +towgs84=598.1,73.7,418.2,0.202,0.045,-2.455,6.7 +units=m +no_defs"
52+
CRS_ERO_STRING = "+proj=tmerc +lat_0=0 +lon_0=12 +k=1 +x_0=4500000 +y_0=0 +ellps=bessel +towgs84=598.1,73.7,418.2,0.202,0.045,-2.455,6.7 +units=m +no_defs"
53+
CRS_FRO_STRING = "+proj=tmerc +lat_0=0 +lon_0=15 +k=1 +x_0=5500000 +y_0=0 +ellps=bessel +towgs84=598.1,73.7,418.2,0.202,0.045,-2.455,6.7 +units=m +no_defs"
54+
55+
match coordinate_str:
56+
case "CR0":
57+
return CRS_CR0_STRING
58+
case "DR0":
59+
return CRS_DR0_STRING
60+
case "ER0":
61+
return CRS_ERO_STRING
62+
case "FR0":
63+
return CRS_FRO_STRING
64+
case _:
65+
raise ValueError("No valid coordinate_str given. Supported are: CR0, DR0, ER0 and FR0.")

0 commit comments

Comments
 (0)