Cogs.Core
GeodeticUtils.cpp
1#include "GeodeticUtils.h"
2
3#include <string>
4#include <cmath>
5#include <glm/glm.hpp>
6
7namespace {
8 struct Ellipsoid
9 {
10 int id;
11 std::string name;
12 double equatorialRadius;
13 double eccentricitySquared;
14 };
15
16 Ellipsoid ellipsoids[] = {
17 {-1, "Placeholder", 0, 0},//placeholder only, To allow array indices to match id numbers
18 {1, "Airy", 6377563, 0.00667054},
19 {2, "Australian National", 6378160, 0.006694542},
20 {3, "Bessel 1841", 6377397, 0.006674372},
21 {4, "Bessel 1841 (Nambia) ", 6377484, 0.006674372},
22 {5, "Clarke 1866", 6378206, 0.006768658},
23 {6, "Clarke 1880", 6378249, 0.006803511},
24 {7, "Everest", 6377276, 0.006637847},
25 {8, "Fischer 1960 (Mercury) ", 6378166, 0.006693422},
26 {9, "Fischer 1968", 6378150, 0.006693422},
27 {10, "GRS 1967", 6378160, 0.006694605},
28 {11, "GRS 1980", 6378137, 0.00669438},
29 {12, "Helmert 1906", 6378200, 0.006693422},
30 {13, "Hough", 6378270, 0.00672267},
31 {14, "International", 6378388, 0.00672267},
32 {15, "Krassovsky", 6378245, 0.006693422},
33 {16, "Modified Airy", 6377340, 0.00667054},
34 {17, "Modified Everest", 6377304, 0.006637847},
35 {18, "Modified Fischer 1960", 6378155, 0.006693422},
36 {19, "South American 1969", 6378160, 0.006694542},
37 {20, "WGS 60", 6378165, 0.006693422},
38 {21, "WGS 66", 6378145, 0.006694542},
39 {22, "WGS-72", 6378135, 0.006694318},
40 {23, "WGS-84", 6378137, 0.00669438}
41 };
42}
43
44void LLtoUTMZone(int& zoneNumber,
45 bool& northernHemisphere,
46 const double latitude,
47 const double longitude,
48 const int /*referenceEllipsoid*/)
49{
50 //converts lat/long to UTM coords. Equations from USGS Bulletin 1532
51 //East Longitudes are positive, West longitudes are negative.
52 //North latitudes are positive, South latitudes are negative
53 //Lat and Long are in decimal degrees
54 //Original C++ version Written by Chuck Gantz- chuck.gantz@globalstar.com
55
56 //Make sure the longitude is between -180.00 .. 179.9
57 const double LongTemp = (longitude + 180) - (int)((longitude + 180) / 360) * 360 - 180; // -180.00 .. 179.9;
58
59 zoneNumber = (int)((LongTemp + 180) / 6) + 1;
60
61 if (latitude >= 56.0 && latitude < 64.0 && LongTemp >= 3.0 && LongTemp < 12.0)
62 zoneNumber = 32;
63
64 // Special zones for Svalbard
65 if (latitude >= 72.0 && latitude < 84.0)
66 {
67 if (LongTemp >= 0.0 && LongTemp < 9.0) zoneNumber = 31;
68 else if (LongTemp >= 9.0 && LongTemp < 21.0) zoneNumber = 33;
69 else if (LongTemp >= 21.0 && LongTemp < 33.0) zoneNumber = 35;
70 else if (LongTemp >= 33.0 && LongTemp < 42.0) zoneNumber = 37;
71 }
72
73 northernHemisphere = latitude >= 0;
74}
75
76
77void LLtoUTM(double& easting,
78 double& northing,
79 const double latitude,
80 const double longitude,
81 int zone,
82 bool north,
83 const int referenceEllipsoid)
84{
85 //converts lat/long to UTM coords. Equations from USGS Bulletin 1532
86 //East Longitudes are positive, West longitudes are negative.
87 //North latitudes are positive, South latitudes are negative
88 //Lat and Long are in decimal degrees
89 //Original C++ version Written by Chuck Gantz- chuck.gantz@globalstar.com
90
91 double a = ellipsoids[referenceEllipsoid].equatorialRadius;
92 double eccSquared = ellipsoids[referenceEllipsoid].eccentricitySquared;
93 double k0 = 0.9996;
94
95 double eccPrimeSquared;
96 double N, T, C, A, M;
97
98 //Make sure the longitude is between -180.00 .. 179.9
99 double LongTemp = (longitude + 180) - (int)((longitude + 180) / 360) * 360 - 180; // -180.00 .. 179.9;
100
101 double LatRad = glm::radians(latitude);
102 double LongRad = glm::radians(LongTemp);
103
104 if (zone == -1) {
105 LLtoUTMZone(zone, north, latitude, longitude, referenceEllipsoid);
106 }
107
108 const double LongOrigin = (zone - 1) * 6 - 180 + 3; //+3 puts origin in middle of zone
109 const double LongOriginRad = glm::radians(LongOrigin);
110
111 eccPrimeSquared = (eccSquared) / (1 - eccSquared);
112
113 N = a / std::sqrt(1 - eccSquared * std::sin(LatRad) * std::sin(LatRad));
114 T = std::tan(LatRad) * std::tan(LatRad);
115 C = eccPrimeSquared * std::cos(LatRad) * std::cos(LatRad);
116 A = std::cos(LatRad) * (LongRad - LongOriginRad);
117
118 M = a * ((1 - eccSquared / 4 - 3 * eccSquared * eccSquared / 64 - 5 * eccSquared * eccSquared * eccSquared / 256) * LatRad
119 - (3 * eccSquared / 8 + 3 * eccSquared * eccSquared / 32 + 45 * eccSquared * eccSquared * eccSquared / 1024) * std::sin(2 * LatRad)
120 + (15 * eccSquared * eccSquared / 256 + 45 * eccSquared * eccSquared * eccSquared / 1024) * std::sin(4 * LatRad)
121 - (35 * eccSquared * eccSquared * eccSquared / 3072) * std::sin(6 * LatRad));
122
123 easting = (double)(k0 * N * (A + (1 - T + C) * A * A * A / 6
124 + (5 - 18 * T + T * T + 72 * C - 58 * eccPrimeSquared) * A * A * A * A * A / 120)
125 + 500000.0);
126
127 northing = (double)(k0 * (M + N * std::tan(LatRad) * (A * A / 2 + (5 - T + 9 * C + 4 * C * C) * A * A * A * A / 24
128 + (61 - 58 * T + T * T + 600 * C - 330 * eccPrimeSquared) * A * A * A * A * A * A / 720)));
129 if (!north)
130 northing += 10000000.0; //10000000 meter offset for southern hemisphere
131}
132
138glm::dvec3 LLtoECEF(double latitude,
139 double longitude,
140 double altitude) {
141 constexpr double semimajorAxis = 6378137.0;
142 constexpr double semiminorAxis = 6356752.314245;
143
144 latitude = glm::radians(latitude);
145 longitude = glm::radians(longitude);
146
147 double sinLat = std::sin(latitude);
148 double cosLat = std::cos(latitude);
149 double sinLong = std::sin(longitude);
150 double cosLong = std::cos(longitude);
151
152 constexpr double fllipsoidFlatness = ((semimajorAxis - semiminorAxis) / semimajorAxis);
153 constexpr double firstEccentricitySquared = (fllipsoidFlatness * (2.0 - fllipsoidFlatness));
154
155 double n = semimajorAxis / std::sqrt(1.0 - firstEccentricitySquared * sinLat * sinLat);
156 return glm::dvec3((altitude + n) * cosLat * cosLong,
157 (altitude + n) * cosLat * sinLong,
158 (altitude + (1.0 - firstEccentricitySquared) * n) * sinLat);
159}
160
161glm::dvec2 calcUTMVectorBetweenCoordinates(double lat1, double long1, double lat2, double long2, int zone1, int zone2) {
162 bool north1 = lat1 >= 0.0;
163 bool north2 = lat2 >= 0.0;
164
165 if (zone1 == -1) {
166 LLtoUTMZone(zone1, north1, lat1, long1);
167 }
168 if (zone2 == -1) {
169 LLtoUTMZone(zone2, north2, lat2, long2);
170 }
171
172 if (zone1 == zone2) {
173 double easting1;
174 double northing1;
175 double easting2;
176 double northing2;
177
178 LLtoUTM(easting1, northing1, lat1, long1, zone1, north1);
179 LLtoUTM(easting2, northing2, lat2, long2, zone2, north2);
180
181 if (north1 == north2) {
182 return glm::dvec2(static_cast<float>(easting2 - easting1), static_cast<float>(northing2 - northing1));
183 }
184 else if (!north1) {
185 return glm::dvec2(static_cast<float>(easting2 - easting1), static_cast<float>(northing2 - (northing1 - 10000000.0)));
186 }
187 else {
188 return glm::dvec2(static_cast<float>(easting2 - easting1), static_cast<float>((northing2 - 10000000.0) - northing1));
189 }
190 }
191 return glm::dvec2();
192}
193
194float calcBearingBetweenCoordinates(double lat1, double long1, double lat2, double long2, int zone) {
195 bool north = lat2 >= 0.0;
196
197 if (zone == -1) {
198 LLtoUTMZone(zone, north, lat2, long2);
199 }
200
201 lat1 = glm::radians(lat1);
202 long1 = glm::radians(long1);
203 lat2 = glm::radians(lat2);
204 long2 = glm::radians(long2);
205
206 double y = std::sin(long2-long1) * std::cos(lat2);
207 double x = (std::cos(lat1) * std::sin(lat2)) - (std::sin(lat1) * std::cos(lat2) * std::cos(long2 - long1));
208 double meridian = glm::radians((zone * 6.0) - 183.0);
209 double convergence = glm::degrees(std::atan(std::tan(long2 - meridian) * std::sin(lat2)));
210
211 return static_cast<float>(std::fmod(glm::degrees(std::atan2(y, x)) - convergence + 360.0, 360.0));
212}
213
220Cogs::LLToENUConverter::LLToENUConverter(double latitude, double longitude, double altitude) {
221 ecefRef = LLtoECEF(latitude, longitude, altitude);
222
223 latitude = glm::radians(latitude);
224 longitude = glm::radians(longitude);
225
226 double sinLat = std::sin(latitude);
227 double cosLat = std::cos(latitude);
228 double sinLong = std::sin(longitude);
229 double cosLong = std::cos(longitude);
230
231 ecefToENUMatrix = glm::dmat3x3(-sinLong, -sinLat * cosLong, cosLat * cosLong,
232 cosLong, -sinLat * sinLong, cosLat * sinLong,
233 0.0, cosLat, sinLat);
234}
235
244glm::dvec3 Cogs::LLToENUConverter::convert(double latitude, double longitude, double altitude) const {
245 return ecefToENUMatrix * (LLtoECEF(latitude, longitude, altitude) - ecefRef);
246}
247
251glm::dvec2 Cogs::LLToENUConverter::calcVectorBetweenCoordinates(double lat1, double long1, double lat2, double long2) const {
252 return convert(lat2, long2) - convert(lat1, long1);
253}
glm::dvec2 calcVectorBetweenCoordinates(double lat1, double long1, double lat2, double long2) const
Calculates a vector between the two geodetic points in ENU space.
LLToENUConverter(double latitude, double longitude, double altitude=0.0)
Constructs a new LLToENUConverter.
glm::dvec3 convert(double latitude, double longitude, double altitude=0.0) const
Converts the given latitude/longitude/altitude coordinates into local tangent plane ENU coordinates w...