Skip to content

Commit 0357012

Browse files
Fix geo conversion
1 parent 7c780a2 commit 0357012

File tree

4 files changed

+127
-52
lines changed

4 files changed

+127
-52
lines changed

test/node_test.py

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

33
from pytest import raises
44

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

78
from .helper import create_edge, create_node
89

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

151152
with raises(Exception) as exception:
152153
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/geo_node.py

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

4-
import pyproj
4+
from haversine import Unit, haversine
55

66
from yaramo.base_element import BaseElement
77

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

911
class GeoNode(ABC, BaseElement):
1012
"""This is the baseclass of specific GeoNodes that use different coordinate systems.
1113
1214
A GeoNode is characterized by it's x and y coordinates.
1315
"""
1416

15-
def __init__(self, x, y, **kwargs):
17+
def __init__(self, x, y, dbref_crs: str = "ER0", **kwargs):
1618
super().__init__(**kwargs)
1719
self.x = x
1820
self.y = y
21+
self.dbref_crs = dbref_crs
1922

2023
@abstractmethod
2124
def get_distance_to_other_geo_node(self, geo_node_b: "GeoNode"):
@@ -39,66 +42,43 @@ def to_serializable(self):
3942

4043

4144
class Wgs84GeoNode(GeoNode):
42-
def get_distance_to_other_geo_node(self, geo_node_b: "Wgs84GeoNode"):
43-
assert type(self) == type(
44-
geo_node_b
45-
), "You cannot calculate the distance between a Wgs84GeoNode and a geo node of a different type!"
46-
return self.__haversine_distance(geo_node_b) / 1000
45+
def get_distance_to_other_geo_node(self, geo_node_b: "GeoNode"):
46+
geo_node_b = geo_node_b.to_wgs84()
47+
return self.__haversine_distance(geo_node_b)
4748

4849
def __haversine_distance(self, geo_node_b: "GeoNode"):
49-
pi_over_180 = float(math.pi / 180)
50-
return (
51-
2
52-
* 6371000
53-
* math.asin(
54-
math.pi
55-
/ 180
56-
* math.sqrt(
57-
math.pow(math.sin((pi_over_180 * (geo_node_b.x - self.x)) / 2), 2)
58-
+ math.cos(pi_over_180 * self.x)
59-
* math.cos(pi_over_180 * geo_node_b.x)
60-
* math.pow(math.sin((pi_over_180 * (geo_node_b.y - self.y)) / 2), 2)
61-
)
62-
)
63-
)
64-
65-
def to_wgs84(self):
50+
own = (self.x, self.y)
51+
other = (geo_node_b.x, geo_node_b.y)
52+
return haversine(own, other, unit=Unit.METERS)
53+
54+
def to_wgs84(self) -> "Wgs84GeoNode":
6655
return self
6756

68-
def to_dbref(self):
69-
transformer = pyproj.Transformer.from_crs("epsg:4326", "epsg:31468")
70-
x, y = transformer.transform(self.y, self.x)
71-
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)
7260

7361
def to_euclidean(self) -> "EuclideanGeoNode":
74-
raise NotImplementedError
62+
return self.to_dbref().to_euclidean()
7563

7664

7765
class DbrefGeoNode(GeoNode):
78-
def get_distance_to_other_geo_node(self, geo_node_b: "DbrefGeoNode"):
79-
assert type(self) == type(
80-
geo_node_b
81-
), "You cannot calculate the distance between a DbrefGeoNode and a geo node of a different type!"
82-
return self.__eucldian_distance(geo_node_b)
83-
84-
def __eucldian_distance(self, geo_node_b: "GeoNode"):
85-
min_x = min(self.x, geo_node_b.x)
86-
min_y = min(self.y, geo_node_b.y)
87-
max_x = max(self.x, geo_node_b.x)
88-
max_y = max(self.y, geo_node_b.y)
89-
return math.sqrt(math.pow(max_x - min_x, 2) + math.pow(max_y - min_y, 2))
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)
9069

91-
def to_wgs84(self):
92-
raise NotImplementedError
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)
9373

94-
def to_dbref(self):
74+
def to_dbref(self) -> "DbrefGeoNode":
9575
return self
9676

9777
def to_euclidean(self) -> "EuclideanGeoNode":
9878
# This transformation is just for testing purposes and not correct, see documentation in EuclideanGeoNode.
9979
_x_shift = 4533770.0
10080
_y_shift = 5625780.0
101-
return EuclideanGeoNode(self.x - _x_shift, self.y - _y_shift)
81+
return EuclideanGeoNode(self.x - _x_shift, self.y - _y_shift, self.dbref_crs)
10282

10383

10484
class EuclideanGeoNode(GeoNode):
@@ -109,9 +89,7 @@ class EuclideanGeoNode(GeoNode):
10989
"""
11090

11191
def get_distance_to_other_geo_node(self, geo_node_b: "EuclideanGeoNode"):
112-
assert type(self) == type(
113-
geo_node_b
114-
), "You cannot calculate the distance between a EuclideanGeoNode and a geo node of a different type!"
92+
geo_node_b = geo_node_b.to_euclidean()
11593
return self.__eucldian_distance(geo_node_b)
11694

11795
def __eucldian_distance(self, geo_node_b: "GeoNode"):
@@ -122,12 +100,12 @@ def __eucldian_distance(self, geo_node_b: "GeoNode"):
122100
return math.sqrt(math.pow(max_x - min_x, 2) + math.pow(max_y - min_y, 2))
123101

124102
def to_wgs84(self) -> "Wgs84GeoNode":
125-
raise NotImplementedError
103+
return self.to_dbref().to_wgs84()
126104

127-
def to_dbref(self):
105+
def to_dbref(self) -> "DbrefGeoNode":
128106
_x_shift = 4533770.0
129107
_y_shift = 5625780.0
130-
return DbrefGeoNode(self.x + _x_shift, self.y + _y_shift)
108+
return DbrefGeoNode(self.x + _x_shift, self.y + _y_shift, self.dbref_crs)
131109

132110
def to_euclidean(self) -> "EuclideanGeoNode":
133111
return self

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)