|
| 1 | +# coding: utf-8 |
| 2 | + |
| 3 | +""" |
| 4 | + Coordinate transformations used in flight dynamics |
| 5 | +""" |
| 6 | + |
| 7 | +import numpy as np |
| 8 | +from numpy import sin, cos, deg2rad, array |
| 9 | + |
| 10 | + |
| 11 | +def lla2ecef(lat, lng, h): |
| 12 | + """ |
| 13 | + Calculates geocentric coordinates (ECEF - Earth Centered, Earth Fixed) |
| 14 | + for a given set of latitude, longitude and altitude inputs, following |
| 15 | + the WGS84 system. |
| 16 | +
|
| 17 | + Parameters |
| 18 | + ---------- |
| 19 | + lat : float |
| 20 | + latitude in degrees |
| 21 | + lng : float |
| 22 | + longitude in degrees |
| 23 | + h : float |
| 24 | + geometric altitude above sea level in meters |
| 25 | +
|
| 26 | + Returns |
| 27 | + ------- |
| 28 | + array-like |
| 29 | + ECEF coordinates in meters |
| 30 | + """ |
| 31 | + if abs(lat) > 90: |
| 32 | + raise ValueError('latitude should be -90º <= latitude <= 90º') |
| 33 | + |
| 34 | + if abs(lng) > 180: |
| 35 | + raise ValueError('longitude should be -180º <= longitude <= 180º') |
| 36 | + |
| 37 | + if not (0 <= h <= 84852.05): |
| 38 | + msg = 'pressure model is only valid if 0 <= h <= 84852.05' |
| 39 | + raise ValueError(msg) |
| 40 | + |
| 41 | + a = 6378137 # [m] Earth equatorial axis |
| 42 | + b = 6356752.3142 # [m] Earth polar axis |
| 43 | + e = 0.081819190842622 # Earth eccentricity |
| 44 | + |
| 45 | + lat = deg2rad(lat) # degrees to radians |
| 46 | + lng = deg2rad(lng) # degrees to radians |
| 47 | + |
| 48 | + N = a / (1 - (e * sin(lat))**2)**(.5) |
| 49 | + |
| 50 | + x = (N + h) * cos(lat) * cos(lng) |
| 51 | + y = (N + h) * cos(lat) * sin(lng) |
| 52 | + z = (((b/a)**2) * N + h) * sin(lat) |
| 53 | + |
| 54 | + return array([x, y, z]) |
| 55 | + |
| 56 | + |
| 57 | +def ned2ecef(v_ned, lat, lng): |
| 58 | + """ |
| 59 | + Converts vector from local geodetic horizon reference frame (NED - North, |
| 60 | + East, Down) at a given latitude and longitude to geocentric coordinates |
| 61 | + (ECEF - Earth Centered, Earth Fixed). |
| 62 | +
|
| 63 | + Parameters |
| 64 | + ---------- |
| 65 | + v_ned: array-like |
| 66 | + vector expressed in NED coordinates |
| 67 | + lat : float |
| 68 | + latitude in degrees |
| 69 | + lng : float |
| 70 | + longitude in degrees |
| 71 | +
|
| 72 | + Returns |
| 73 | + ------- |
| 74 | + v_ecef : array-like |
| 75 | + vector expressed in ECEF coordinates |
| 76 | + """ |
| 77 | + if abs(lat) > 90: |
| 78 | + raise ValueError('latitude should be -90º <= latitude <= 90º') |
| 79 | + |
| 80 | + if abs(lng) > 180: |
| 81 | + raise ValueError('longitude should be -180º <= longitude <= 180º') |
| 82 | + |
| 83 | + lat = deg2rad(lat) |
| 84 | + lng = deg2rad(lng) |
| 85 | + |
| 86 | + Lne = array([[-sin(lat) * cos(lng), -sin(lat) * sin(lng), cos(lat)], |
| 87 | + [-sin(lng), cos(lng), 0], |
| 88 | + [-cos(lat) * cos(lng), -cos(lat) * sin(lng), -sin(lat)]]) |
| 89 | + |
| 90 | + Len = Lne.transpose() |
| 91 | + v_ecef = Len.dot(v_ned) |
| 92 | + |
| 93 | + return v_ecef |
| 94 | + |
| 95 | + |
| 96 | +def body2ned(v_body, theta, phi, psi): |
| 97 | + """ |
| 98 | + Converts vector from body reference frame to local geodetic horizon |
| 99 | + reference frame (NED - North, East, Down) |
| 100 | +
|
| 101 | + Parameters |
| 102 | + ---------- |
| 103 | + v_body: array-like |
| 104 | + vector expressed in body coordinates |
| 105 | + theta : float |
| 106 | + pitch angle in radians |
| 107 | + phi : float |
| 108 | + bank angle in radians |
| 109 | + psi : float |
| 110 | + yaw angle in radians |
| 111 | +
|
| 112 | + Returns |
| 113 | + ------- |
| 114 | + v_ned : array_like |
| 115 | + vector expressed in local horizon (NED) coordinates |
| 116 | + """ |
| 117 | + if abs(theta) > np.pi/2: |
| 118 | + raise ValueError('theta should be -pi/2 <= theta <= pi/2') |
| 119 | + |
| 120 | + if abs(phi) > np.pi: |
| 121 | + raise ValueError('phi should be -pi <= phi <= pi') |
| 122 | + |
| 123 | + if not 0 <= psi <= 2*np.pi: |
| 124 | + raise ValueError('psi should be 0 <= psi <= 2*pi') |
| 125 | + |
| 126 | + Lnb = array([[cos(theta) * cos(psi), |
| 127 | + sin(phi) * sin(theta) * cos(psi) - cos(phi) * sin(psi), |
| 128 | + cos(phi) * sin(theta) * cos(psi) + sin(phi) * sin(psi)], |
| 129 | + [cos(theta) * sin(psi), |
| 130 | + sin(phi) * sin(theta) * sin(psi) + cos(phi) * cos(psi), |
| 131 | + cos(phi) * sin(theta) * sin(psi) - sin(phi) * cos(psi)], |
| 132 | + [-sin(theta), |
| 133 | + sin(phi) * cos(theta), |
| 134 | + cos(phi) * cos(theta)]]) |
| 135 | + |
| 136 | + v_ned = Lnb.dot(v_body) |
| 137 | + |
| 138 | + return v_ned |
| 139 | + |
| 140 | + |
| 141 | +def ned2body(v_ned, theta, phi, psi): |
| 142 | + """ |
| 143 | + Converts vector from local geodetic horizon reference frame (NED - |
| 144 | + North, East, Down) to body reference frame |
| 145 | +
|
| 146 | + Parameters |
| 147 | + ---------- |
| 148 | + v_ned : array_like |
| 149 | + vector expressed in local horizon (NED) coordinates |
| 150 | + theta : float |
| 151 | + pitch angle in radians |
| 152 | + phi : float |
| 153 | + bank angle in radians |
| 154 | + psi : float |
| 155 | + yaw angle in radians |
| 156 | +
|
| 157 | + Returns |
| 158 | + ------- |
| 159 | + v_body: array-like |
| 160 | + vector expressed in body coordinates |
| 161 | + """ |
| 162 | + if abs(theta) > np.pi/2: |
| 163 | + raise ValueError('theta should be -pi/2 <= theta <= pi/2') |
| 164 | + |
| 165 | + if abs(phi) > np.pi: |
| 166 | + raise ValueError('phi should be -pi <= phi <= pi') |
| 167 | + |
| 168 | + if not 0 <= psi <= 2*np.pi: |
| 169 | + raise ValueError('psi should be 0 <= psi <= 2*pi') |
| 170 | + |
| 171 | + Lbn = array([[cos(theta) * cos(psi), |
| 172 | + cos(theta) * sin(psi), |
| 173 | + -sin(theta)], |
| 174 | + [sin(phi) * sin(theta) * cos(psi) - cos(phi) * sin(psi), |
| 175 | + sin(phi) * sin(theta) * sin(psi) + cos(phi) * cos(psi), |
| 176 | + sin(phi) * cos(theta)], |
| 177 | + [cos(phi) * sin(theta) * cos(psi) + sin(phi) * sin(psi), |
| 178 | + cos(phi) * sin(theta) * sin(psi) - sin(phi) * cos(psi), |
| 179 | + cos(phi) * cos(theta)]]) |
| 180 | + |
| 181 | + v_body = Lbn.dot(v_ned) |
| 182 | + |
| 183 | + return v_body |
| 184 | + |
| 185 | + |
| 186 | +def body2wind(v_body, alpha, beta): |
| 187 | + """ |
| 188 | + Converts vector from body reference frame to wind reference frame |
| 189 | +
|
| 190 | + Parameters |
| 191 | + ---------- |
| 192 | + v_body : array_like |
| 193 | + vector expressed in body coordinates |
| 194 | + alpha : float |
| 195 | + angle of attack in radians |
| 196 | + beta : float |
| 197 | + sideslip angle in radians |
| 198 | +
|
| 199 | + Returns |
| 200 | + ------- |
| 201 | + v_wind : array_like |
| 202 | + vector expressed in wind coordinates |
| 203 | + """ |
| 204 | + if abs(alpha) > np.pi/2: |
| 205 | + raise ValueError('alpha should be -pi/2 <= alpha <= pi/2') |
| 206 | + |
| 207 | + if abs(beta) > np.pi: |
| 208 | + raise ValueError('beta should be -pi <= beta <= pi') |
| 209 | + |
| 210 | + Lwb = array([[cos(alpha) * cos(beta), sin(beta), sin(alpha) * cos(beta)], |
| 211 | + [-cos(alpha) * sin(beta), cos(beta), -sin(alpha) * sin(beta)], |
| 212 | + [-sin(alpha), 0, cos(alpha)]]) |
| 213 | + |
| 214 | + v_wind = Lwb.dot(v_body) |
| 215 | + |
| 216 | + return v_wind |
| 217 | + |
| 218 | + |
| 219 | +def wind2body(v_wind, alpha, beta): |
| 220 | + """ |
| 221 | + Converts vector from wind reference frame to body reference frame |
| 222 | +
|
| 223 | + Parameters |
| 224 | + ---------- |
| 225 | + v_wind : array_like |
| 226 | + vector expressed in wind coordinates |
| 227 | + alpha : float |
| 228 | + angle of attack in radians |
| 229 | + beta : float |
| 230 | + sideslip angle in radians |
| 231 | +
|
| 232 | + Returns |
| 233 | + ------- |
| 234 | + v_body : array_like |
| 235 | + vector expressed in body coordinates |
| 236 | + """ |
| 237 | + if abs(alpha) > np.pi/2: |
| 238 | + raise ValueError('alpha should be -pi/2 <= alpha <= pi/2') |
| 239 | + |
| 240 | + if abs(beta) > np.pi: |
| 241 | + raise ValueError('beta should be -pi <= beta <= pi') |
| 242 | + |
| 243 | + Lbw = array([[cos(alpha) * cos(beta), |
| 244 | + -cos(alpha) * sin(beta), |
| 245 | + -sin(alpha)], |
| 246 | + [sin(beta), cos(beta), 0], |
| 247 | + [sin(alpha) * cos(beta), |
| 248 | + -sin(alpha) * sin(beta), |
| 249 | + cos(alpha)]]) |
| 250 | + |
| 251 | + v_body = Lbw.dot(v_wind) |
| 252 | + |
| 253 | + return v_body |
0 commit comments