Skip to content

Commit 76a465e

Browse files
authored
Write orientation & transform in C++ (#1637)
* locationd at 20hz * update ref * bump cereal * dont modify global state * add scons files * ecef2geodetic and geodetic2ecef * Finish local coords class * Add header file * Add orientation.cc * cleanup * Add functions to header file * Add cython wrapper * y u no work? * This passes the tests * test rot2quat and quat2rot * Teste euler2rot and rot2euler * rot_matrix * test ecef_euler_from_ned and ned_euler_from_ecef * add benchmark * Add test * Consistent newlines * no more radians supported in geodetic * test localcoord single * test localcoord single * all tests pass * Unused import * Add alternate namings * Add source for formulas * no explicit tests needed * remove benchmark * Add release files * Typo * Remove print statement * no access to raw transform matrix * temporarily add tolerance * handcode quat2euler * update ref old-commit-hash: c18e7da
1 parent 5fb85ed commit 76a465e

22 files changed

+683
-420
lines changed

.editorconfig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ end_of_line = lf
55
insert_final_newline = true
66
trim_trailing_whitespace = true
77

8-
[{*.py, *.pyx, *pxd}]
8+
[{*.py, *.pyx, *.pxd}]
99
charset = utf-8
1010
indent_style = space
1111
indent_size = 2

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,3 +62,4 @@ htmlcov
6262
pandaextra
6363

6464
.mypy_cache/
65+
flycheck_*

SConstruct

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,7 @@ SConscript(['opendbc/can/SConscript'])
212212

213213
SConscript(['common/SConscript'])
214214
SConscript(['common/kalman/SConscript'])
215+
SConscript(['common/transformations/SConscript'])
215216
SConscript(['phonelibs/SConscript'])
216217

217218
if arch != "Darwin":

cereal

common/transformations/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
transformations
2+
transformations.cpp

common/transformations/SConscript

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
Import('env')
2+
3+
d = Dir('.')
4+
5+
env.Command(
6+
['transformations.so'],
7+
['transformations.pxd', 'transformations.pyx',
8+
'coordinates.cc', 'orientation.cc', 'coordinates.hpp', 'orientation.hpp'],
9+
'cd ' + d.path + ' && python3 setup.py build_ext --inplace')
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
#define _USE_MATH_DEFINES
2+
3+
#include <iostream>
4+
#include <cmath>
5+
#include <eigen3/Eigen/Dense>
6+
7+
#include "coordinates.hpp"
8+
9+
#define DEG2RAD(x) ((x) * M_PI / 180.0)
10+
#define RAD2DEG(x) ((x) * 180.0 / M_PI)
11+
12+
13+
double a = 6378137;
14+
double b = 6356752.3142;
15+
double esq = 6.69437999014 * 0.001;
16+
double e1sq = 6.73949674228 * 0.001;
17+
18+
19+
static Geodetic to_degrees(Geodetic geodetic){
20+
geodetic.lat = RAD2DEG(geodetic.lat);
21+
geodetic.lon = RAD2DEG(geodetic.lon);
22+
return geodetic;
23+
}
24+
25+
static Geodetic to_radians(Geodetic geodetic){
26+
geodetic.lat = DEG2RAD(geodetic.lat);
27+
geodetic.lon = DEG2RAD(geodetic.lon);
28+
return geodetic;
29+
}
30+
31+
32+
ECEF geodetic2ecef(Geodetic g){
33+
g = to_radians(g);
34+
double xi = sqrt(1.0 - esq * pow(sin(g.lat), 2));
35+
double x = (a / xi + g.alt) * cos(g.lat) * cos(g.lon);
36+
double y = (a / xi + g.alt) * cos(g.lat) * sin(g.lon);
37+
double z = (a / xi * (1.0 - esq) + g.alt) * sin(g.lat);
38+
return {x, y, z};
39+
}
40+
41+
Geodetic ecef2geodetic(ECEF e){
42+
// Convert from ECEF to geodetic using Ferrari's methods
43+
// https://en.wikipedia.org/wiki/Geographic_coordinate_conversion#Ferrari.27s_solution
44+
double x = e.x;
45+
double y = e.y;
46+
double z = e.z;
47+
48+
double r = sqrt(x * x + y * y);
49+
double Esq = a * a - b * b;
50+
double F = 54 * b * b * z * z;
51+
double G = r * r + (1 - esq) * z * z - esq * Esq;
52+
double C = (esq * esq * F * r * r) / (pow(G, 3));
53+
double S = cbrt(1 + C + sqrt(C * C + 2 * C));
54+
double P = F / (3 * pow((S + 1 / S + 1), 2) * G * G);
55+
double Q = sqrt(1 + 2 * esq * esq * P);
56+
double r_0 = -(P * esq * r) / (1 + Q) + sqrt(0.5 * a * a*(1 + 1.0 / Q) - P * (1 - esq) * z * z / (Q * (1 + Q)) - 0.5 * P * r * r);
57+
double U = sqrt(pow((r - esq * r_0), 2) + z * z);
58+
double V = sqrt(pow((r - esq * r_0), 2) + (1 - esq) * z * z);
59+
double Z_0 = b * b * z / (a * V);
60+
double h = U * (1 - b * b / (a * V));
61+
62+
double lat = atan((z + e1sq * Z_0) / r);
63+
double lon = atan2(y, x);
64+
65+
return to_degrees({lat, lon, h});
66+
}
67+
68+
LocalCoord::LocalCoord(Geodetic g, ECEF e){
69+
init_ecef << e.x, e.y, e.z;
70+
71+
g = to_radians(g);
72+
73+
ned2ecef_matrix <<
74+
-sin(g.lat)*cos(g.lon), -sin(g.lon), -cos(g.lat)*cos(g.lon),
75+
-sin(g.lat)*sin(g.lon), cos(g.lon), -cos(g.lat)*sin(g.lon),
76+
cos(g.lat), 0, -sin(g.lat);
77+
ecef2ned_matrix = ned2ecef_matrix.transpose();
78+
}
79+
80+
NED LocalCoord::ecef2ned(ECEF e) {
81+
Eigen::Vector3d ecef;
82+
ecef << e.x, e.y, e.z;
83+
84+
Eigen::Vector3d ned = (ecef2ned_matrix * (ecef - init_ecef));
85+
return {ned[0], ned[1], ned[2]};
86+
}
87+
88+
ECEF LocalCoord::ned2ecef(NED n) {
89+
Eigen::Vector3d ned;
90+
ned << n.n, n.e, n.d;
91+
92+
Eigen::Vector3d ecef = (ned2ecef_matrix * ned) + init_ecef;
93+
return {ecef[0], ecef[1], ecef[2]};
94+
}
95+
96+
NED LocalCoord::geodetic2ned(Geodetic g) {
97+
ECEF e = ::geodetic2ecef(g);
98+
return ecef2ned(e);
99+
}
100+
101+
Geodetic LocalCoord::ned2geodetic(NED n){
102+
ECEF e = ned2ecef(n);
103+
return ::ecef2geodetic(e);
104+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
#pragma once
2+
3+
struct ECEF {
4+
double x, y, z;
5+
Eigen::Vector3d to_vector(){
6+
return Eigen::Vector3d(x, y, z);
7+
}
8+
};
9+
10+
struct NED {
11+
double n, e, d;
12+
};
13+
14+
struct Geodetic {
15+
double lat, lon, alt;
16+
bool radians=false;
17+
};
18+
19+
ECEF geodetic2ecef(Geodetic g);
20+
Geodetic ecef2geodetic(ECEF e);
21+
22+
class LocalCoord {
23+
private:
24+
Eigen::Matrix3d ned2ecef_matrix;
25+
Eigen::Matrix3d ecef2ned_matrix;
26+
Eigen::Vector3d init_ecef;
27+
public:
28+
LocalCoord(Geodetic g, ECEF e);
29+
LocalCoord(Geodetic g) : LocalCoord(g, ::geodetic2ecef(g)) {}
30+
LocalCoord(ECEF e) : LocalCoord(::ecef2geodetic(e), e) {}
31+
32+
NED ecef2ned(ECEF e);
33+
ECEF ned2ecef(NED n);
34+
NED geodetic2ned(Geodetic g);
35+
Geodetic ned2geodetic(NED n);
36+
};
Lines changed: 12 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -1,113 +1,19 @@
1-
"""
2-
Coordinate transformation module. All methods accept arrays as input
3-
with each row as a position.
4-
"""
5-
import numpy as np
1+
# pylint: skip-file
2+
from common.transformations.orientation import numpy_wrap
3+
from common.transformations.transformations import (ecef2geodetic_single,
4+
geodetic2ecef_single)
5+
from common.transformations.transformations import LocalCoord as LocalCoord_single
66

77

8-
a = 6378137
9-
b = 6356752.3142
10-
esq = 6.69437999014 * 0.001
11-
e1sq = 6.73949674228 * 0.001
8+
class LocalCoord(LocalCoord_single):
9+
ecef2ned = numpy_wrap(LocalCoord_single.ecef2ned_single, (3,), (3,))
10+
ned2ecef = numpy_wrap(LocalCoord_single.ned2ecef_single, (3,), (3,))
11+
geodetic2ned = numpy_wrap(LocalCoord_single.geodetic2ned_single, (3,), (3,))
12+
ned2geodetic = numpy_wrap(LocalCoord_single.ned2geodetic_single, (3,), (3,))
1213

1314

14-
def geodetic2ecef(geodetic, radians=False):
15-
geodetic = np.array(geodetic)
16-
input_shape = geodetic.shape
17-
geodetic = np.atleast_2d(geodetic)
18-
19-
ratio = 1.0 if radians else (np.pi / 180.0)
20-
lat = ratio*geodetic[:, 0]
21-
lon = ratio*geodetic[:, 1]
22-
alt = geodetic[:, 2]
23-
24-
xi = np.sqrt(1 - esq * np.sin(lat)**2)
25-
x = (a / xi + alt) * np.cos(lat) * np.cos(lon)
26-
y = (a / xi + alt) * np.cos(lat) * np.sin(lon)
27-
z = (a / xi * (1 - esq) + alt) * np.sin(lat)
28-
ecef = np.array([x, y, z]).T
29-
return ecef.reshape(input_shape)
30-
31-
32-
def ecef2geodetic(ecef, radians=False):
33-
"""
34-
Convert ECEF coordinates to geodetic using ferrari's method
35-
"""
36-
# Save shape and export column
37-
ecef = np.atleast_1d(ecef)
38-
input_shape = ecef.shape
39-
ecef = np.atleast_2d(ecef)
40-
x, y, z = ecef[:, 0], ecef[:, 1], ecef[:, 2]
41-
42-
ratio = 1.0 if radians else (180.0 / np.pi)
43-
44-
# Conver from ECEF to geodetic using Ferrari's methods
45-
# https://en.wikipedia.org/wiki/Geographic_coordinate_conversion#Ferrari.27s_solution
46-
r = np.sqrt(x * x + y * y)
47-
Esq = a * a - b * b
48-
F = 54 * b * b * z * z
49-
G = r * r + (1 - esq) * z * z - esq * Esq
50-
C = (esq * esq * F * r * r) / (pow(G, 3))
51-
S = np.cbrt(1 + C + np.sqrt(C * C + 2 * C))
52-
P = F / (3 * pow((S + 1 / S + 1), 2) * G * G)
53-
Q = np.sqrt(1 + 2 * esq * esq * P)
54-
r_0 = -(P * esq * r) / (1 + Q) + np.sqrt(0.5 * a * a*(1 + 1.0 / Q) -
55-
P * (1 - esq) * z * z / (Q * (1 + Q)) - 0.5 * P * r * r)
56-
U = np.sqrt(pow((r - esq * r_0), 2) + z * z)
57-
V = np.sqrt(pow((r - esq * r_0), 2) + (1 - esq) * z * z)
58-
Z_0 = b * b * z / (a * V)
59-
h = U * (1 - b * b / (a * V))
60-
lat = ratio*np.arctan((z + e1sq * Z_0) / r)
61-
lon = ratio*np.arctan2(y, x)
62-
63-
# stack the new columns and return to the original shape
64-
geodetic = np.column_stack((lat, lon, h))
65-
return geodetic.reshape(input_shape)
66-
15+
geodetic2ecef = numpy_wrap(geodetic2ecef_single, (3,), (3,))
16+
ecef2geodetic = numpy_wrap(ecef2geodetic_single, (3,), (3,))
6717

6818
geodetic_from_ecef = ecef2geodetic
6919
ecef_from_geodetic = geodetic2ecef
70-
71-
72-
class LocalCoord():
73-
"""
74-
Allows conversions to local frames. In this case NED.
75-
That is: North East Down from the start position in
76-
meters.
77-
"""
78-
def __init__(self, init_geodetic, init_ecef):
79-
self.init_ecef = init_ecef
80-
lat, lon, _ = (np.pi/180)*np.array(init_geodetic)
81-
self.ned2ecef_matrix = np.array([[-np.sin(lat)*np.cos(lon), -np.sin(lon), -np.cos(lat)*np.cos(lon)],
82-
[-np.sin(lat)*np.sin(lon), np.cos(lon), -np.cos(lat)*np.sin(lon)],
83-
[np.cos(lat), 0, -np.sin(lat)]])
84-
self.ecef2ned_matrix = self.ned2ecef_matrix.T
85-
self.ecef_from_ned_matrix = self.ned2ecef_matrix
86-
self.ned_from_ecef_matrix = self.ecef2ned_matrix
87-
88-
@classmethod
89-
def from_geodetic(cls, init_geodetic):
90-
init_ecef = geodetic2ecef(init_geodetic)
91-
return LocalCoord(init_geodetic, init_ecef)
92-
93-
@classmethod
94-
def from_ecef(cls, init_ecef):
95-
init_geodetic = ecef2geodetic(init_ecef)
96-
return LocalCoord(init_geodetic, init_ecef)
97-
98-
def ecef2ned(self, ecef):
99-
ecef = np.array(ecef)
100-
return np.dot(self.ecef2ned_matrix, (ecef - self.init_ecef).T).T
101-
102-
def ned2ecef(self, ned):
103-
ned = np.array(ned)
104-
# Transpose so that init_ecef will broadcast correctly for 1d or 2d ned.
105-
return (np.dot(self.ned2ecef_matrix, ned.T).T + self.init_ecef)
106-
107-
def geodetic2ned(self, geodetic):
108-
ecef = geodetic2ecef(geodetic)
109-
return self.ecef2ned(ecef)
110-
111-
def ned2geodetic(self, ned):
112-
ecef = self.ned2ecef(ned)
113-
return ecef2geodetic(ecef)

0 commit comments

Comments
 (0)