I have been working with GeographicLib for a few weeks and I have a couple
of questions regarding how to determine the cross track error from a flight
path. I use the library to make accurate point to point calculations along
a flight path, but now I need to determine the cross track error away from
the path (based on GPS coordinates received during flight). I saw
somewhere where you performed some sort of successive approximation
technique for cross track evaluation (sorry I cannot find the link) but I
think the easiest/fastest way for me to determine this value would be to
use Ed Williams’ formulary ftp://ftp.bartol.udel.edu/anita/amir/My_thesis/Figures4Thesis/CRC_plots/Aviation%20Formulary%20V1.46.pdf
which indicates:
Cross track error:
Suppose you are proceeding on a great circle route from A to B (course
=crs_AB) and end up at D, perhaps off course. (We presume that A is ot a
pole!) You can calculate the course from A to D (crs_AD) and the distance
from A to D (dist_AD) using the formulae above. In terms of these the cross
track error, XTD, (distance off course) is given by:
XTD =asin(sin(dist_AD)*sin(crs_AD-crs_AB))
My question is:
Given 3 points A, B and D (as described in the cross track error problem
above with A and B along a geodesic), which of the GeographicLib formulae
should I use to compute the required crs_AD and crs_AB course that I could
plug into the simple formula above?
I thought these course values might be the azi1 and azi2 values returned
form Geodesic.Inverse(….) from A to D and A to D respectively, but these
returned azimuth values are very different (after conversion to radians) to
those in the Williams JavaScript worked example https://gist.github.com/missinglink/ab7c8b9b8699622539986373e6237fd4
(which is very easy to convert and debug in C++, note I changed to a
havDistance() instead of distance() for improved accuracy). The worked
example site (Williams) indicates the calculation for course between
geoPoints a & b is defined as:
Here you have the relevant part of my code – I would like to eliminate hand
coded course and distance calculations below favor of those in the
GeograhicLib and just use the xTrack function which as far as I am aware is
not available in GeographicLib to evaluate the cross track error.
One more thing I would also like to know how to solve is:
Given a set of flight waypoints (a flight plan), how can I calculate the
coordinates for a thick box around the flight plan as a center line. The
width of this line would effectively describe the an acceptable cross track
error (as long as I am flying within the bounds. At each waypoint, I would
need to calculate 2 normal points +/- some max cross track error away. I
have no idea how to do so and it seems like GeographicLib is perfectly
setup to calculate these values for me.
Without trying to address all of your post in detail, let me, nevertheless offer some pointers:
azi1 and azi2 are the angles α1 and α2 shown in the documentation.
I believe the formulas for Williams are correct for a sphere. So you should be able to get everything to match up between these and the results from GeographicLib by specifying a sphere (f = 0) when you instantiate a Geodesic object.
Assuming that D is close to the geodesic AB, then the ellipsoidal formula for cross track distance is [α1(AD) − α1(AB)]m12(AD), Here m12 is the reduced length.
Please let me know if you have any questions on this...
I defer addressing your second question (which seems awfully fiddly) until after we've cleared up the first.
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
Thanks for following up with me. Perhaps I misunderstood something (I'm new to geodesics) but I am getting a cross track error of 15.71 nautical miles with the above formula. With the Williams formula I correctly get 0.0021674699088523514 from the xTrack function (I presume these are radians). This value correctly matches the worked example - presumably converting this to NM is just a matter of multiplying by WGS84_a() and dividing by Constants::nauticalmile(), which ends up being 7.464NM. This value is way off (even accounting for using a non zero flattening factor). I wonder if you can spot something really obviously wrong with the following block of test code.
Thanks
John
// we travel from JFK to LAX and end up at D
const auto laxRadians = GeoPointRads{ 0.592539, 2.066470 };
const auto jfkRadians = GeoPointRads{ 0.709186, 1.287762 };
const auto lostRadians = GeoPointRads{ 0.6021386, 2.033309 };
auto xTrack = getTrackInfoNM(laxRadians, jfkRadians, lostRadians);
. . .
/static/
std::pair<double, double="">
CoPilotUtils::getTrackInfoNM(
const GeoPointRads& a,
const GeoPointRads& b,
const GeoPointRads& d)
{
std::pair<double, double=""> result;
// Solve the Geodesic inverse problem from A->D
double s12, azi1, azi2, m12, M12, M21, S12 = 0.0;
auto arcAD = gGeodesic.Inverse(a.lat, a.lon, d.lat, d.lon,
s12, azi1, azi2, m12, M12, M21, S12);
const auto azi1AD = azi1;
const auto m12AD = m12;
auto arcAB = gGeodesic.Inverse(a.lat, a.lon, b.lat, b.lon,
s12, azi1, azi2, m12, M12, M21, S12);
const auto azi1AB = azi1;
const auto ellipsiodalXtrackNM = (azi1AD - azi1AB) * m12AD /
GeographicLib::Constants::nauticalmile();
// Assuming D is close to the geodesic AB, then the
// ellipsoidal formula for cross track distance is
// [azi1(AD) - azi1(AB)] * m12(AD). (m12 = reduced length).
// https://sourceforge.net/p/geographiclib/discussion/1026621/thread/299518a3e4/</double,></double,>
const auto dist_AD = havDistance(a, d);
const auto crs_AD = course(a, d, dist_AD);
const auto dist_AB = havDistance(a, b);
const auto crs_AB = course(a, b, dist_AB);
const auto xTrackRads = xTrack(dist_AD, crs_AD, crs_AB);
const auto aTrackRads = aTrack(dist_AD, xTrackRads);
return {
Constants::WGS84_a() * xTrackRads / GeographicLib::Constants::nauticalmile(),
Constants::WGS84_a() * aTrackRads / GeographicLib::Constants::nauticalmile()
};
}
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
The factor of pi/180 = Math::degree() is needed to convert degrees to radians (always .needed in a formula like this) and 1/1852 converts to NM. This seems to .be close to the result you got for the spherical case?
Last edit: Charles Karney 2018-11-15
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
Thank you for the above script, I figured out that my error was passing in radians to GeographicLib - I should have been passing in degrees. I also needed to convert the returned azimuth angles back to radians. Everything was in radians in the williams approach.
By the way, I am not sure where I was it before but from what I can recall I saw an approach you used to use to make a sort of successive approximation to calculating the cross track error. Was that just old code that has been replaced with this updated formula? I wish I could find the example but no luck (I do recall you posted it somewher as a C++ example).
Now I can replace the Williams temporary code with GeographicLib calls.
John
This was the missing trick before calling Geographic lib
OK, good. GeographicLib uses degrees for its interface so that directions like "due south" and positions like the "north pole" can be represented exactly.
NOTE WELL: the formula I gave you applies only for points close to the track. In the general case I recommend using the approach given in Section 8 of Algorithms for geodesics. Elsewhere on this site I give a C++ implementation. Here's an implementation in MATLAB.
function[lat,lon,s,azi]=geodintercept...(lata,lona,latb,lonb,latc,lonc,ell)%GEODINTERCEPTApproximatedistancebetweenapointandageodesictrack%%[lat,lon,s,azi]=GEODINTERCEPT(lata,lona,latb,lonb,latc,lonc)%[lat,lon,s,azi]=%GEODINTERCEPT(lata,lona,latb,lonb,latc,lonc,ell)%%GivenapointCat(latitude,longitude)=(latc,lonc)andageodesic%ABfrom(lata,lona)to(latb,lonb),computetheinterceptpointO=%(lat,lon),i.e.,thepointonABwhichisclosesttoC.Alsoreturn%thedistance,s,andstartingazimuth,azi,ofthegeodesicfromCto%O.Theargumentsmustbecolumnvectorsofthesamesize.Allangles%(latitudes,longitudes,azimuth)areindegrees;distanceisinmeters.%%Theoptionalellvectorisoftheform[a,e],whereaisthe%equatorialradiusinmeters,eistheeccentricity.Ifellisomitted,%theWGS84ellipsoid(moreprecisely,thevaluereturnedby%defaultellipsoid)isused.%%Themethodperformstwoiterationsofthesolutionoftheintercept%problemfrom"Algorithms for geodesics",Section8,usingCasthe%initialguessforO.Becausethegnomonicprojectionisused,AandB%shouldbewithinabout9900kmofC.%%IfAandBarewithin8000kmofeachotherandifCiswithin200km%ofAB,thentheresultsareaccuratetoround-off.%%ThisfunctionrequirestheinstallationoftheGeographicLibtoolbox%https://www.mathworks.com/matlabcentral/fileexchange/50605%%SeealsoGNOMONIC_FWD,GNOMONIC_FWD,DEFAULTELLIPSOID.ifnargin<7,ell=defaultellipsoid;endk=0;kmax=2;lat=latc;lon=lonc;%startingguesswhilek<kmax%projectA,B,CtognomonicprojectioncenteredatO[x,y]=gnomonic_fwd(lat,lon,lata,lona,ell);a=[x,y];[x,y]=gnomonic_fwd(lat,lon,latb,lonb,ell);b=[x,y];ifk==0c=0*a;else[x,y]=gnomonic_fwd(lat,lon,latc,lonc,ell);c=[x,y];end%computeinterceptionpoint(lasteq.ofSection8)d=b-a;o=(sum(c.*d,2).*d-...(a(:,1).*b(:,2)-a(:,2).*b(:,1)).*[-d(:,2),d(:,1)])./...sum(d.^2,2);%updateO[lat,lon]=gnomonic_inv(lat,lon,o(:,1),o(:,2),ell);k=k+1;end%computegeodesicCOifnargout>2[s,azi]=geoddistance(latc,lonc,lat,lon,ell);endend
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
Dr. Karney,
I have been working with GeographicLib for a few weeks and I have a couple
of questions regarding how to determine the cross track error from a flight
path. I use the library to make accurate point to point calculations along
a flight path, but now I need to determine the cross track error away from
the path (based on GPS coordinates received during flight). I saw
somewhere where you performed some sort of successive approximation
technique for cross track evaluation (sorry I cannot find the link) but I
think the easiest/fastest way for me to determine this value would be to
use Ed Williams’ formulary
ftp://ftp.bartol.udel.edu/anita/amir/My_thesis/Figures4Thesis/CRC_plots/Aviation%20Formulary%20V1.46.pdf
which indicates:
Cross track error:
Suppose you are proceeding on a great circle route from A to B (course
=crs_AB) and end up at D, perhaps off course. (We presume that A is ot a
pole!) You can calculate the course from A to D (crs_AD) and the distance
from A to D (dist_AD) using the formulae above. In terms of these the cross
track error, XTD, (distance off course) is given by:
XTD =asin(sin(dist_AD)*sin(crs_AD-crs_AB))
My question is:
Given 3 points A, B and D (as described in the cross track error problem
above with A and B along a geodesic), which of the GeographicLib formulae
should I use to compute the required crs_AD and crs_AB course that I could
plug into the simple formula above?
I thought these course values might be the azi1 and azi2 values returned
form Geodesic.Inverse(….) from A to D and A to D respectively, but these
returned azimuth values are very different (after conversion to radians) to
those in the Williams JavaScript worked example
https://gist.github.com/missinglink/ab7c8b9b8699622539986373e6237fd4
(which is very easy to convert and debug in C++, note I changed to a
havDistance() instead of distance() for improved accuracy). The worked
example site (Williams) indicates the calculation for course between
geoPoints a & b is defined as:
Course = std::acos((std::sin(b.lat) - std::sin(a.lat) * std::cos(d)) /
(std::sin(d) * std::cos(a.lat)))
Here you have the relevant part of my code – I would like to eliminate hand
coded course and distance calculations below favor of those in the
GeograhicLib and just use the xTrack function which as far as I am aware is
not available in GeographicLib to evaluate the cross track error.
One more thing I would also like to know how to solve is:
Given a set of flight waypoints (a flight plan), how can I calculate the
coordinates for a thick box around the flight plan as a center line. The
width of this line would effectively describe the an acceptable cross track
error (as long as I am flying within the bounds. At each waypoint, I would
need to calculate 2 normal points +/- some max cross track error away. I
have no idea how to do so and it seems like GeographicLib is perfectly
setup to calculate these values for me.
Thank you for a great library
John Coffey
// STATIC VARIABLE INITIALIZATIONS
static Geodesic gGeodesic(Constants::WGS84_a(), Constants::WGS84_f());
. . .
double
distance(const GeoPointRads& a, const GeoPointRads& b) {
}
double
havDistance(const GeoPointRads& a, const GeoPointRads& b) {
b.lon)/2), 2)));
}
double
course(const GeoPointRads& a, const GeoPointRads& b, double d) {
}
double
xTrack(const double d, const double crs1, const double crs2) {
}
double aTrack(const double d, const double xtd) {
}
double aTrackSimple(const double d, const double xtd) {
}
/static/
double
CoPilotUtils::getCrossTrackErrorNM(
{
// should be 3973km
(dist_AD, crs_AD, crs_AB);
}
Without trying to address all of your post in detail, let me, nevertheless offer some pointers:
azi1 and azi2 are the angles α1 and α2 shown in the documentation.
I believe the formulas for Williams are correct for a sphere. So you should be able to get everything to match up between these and the results from GeographicLib by specifying a sphere (f = 0) when you instantiate a Geodesic object.
Assuming that D is close to the geodesic AB, then the ellipsoidal formula for cross track distance is [α1(AD) − α1(AB)] m12(AD), Here m12 is the reduced length.
Please let me know if you have any questions on this...
I defer addressing your second question (which seems awfully fiddly) until after we've cleared up the first.
Thanks for following up with me. Perhaps I misunderstood something (I'm new to geodesics) but I am getting a cross track error of 15.71 nautical miles with the above formula. With the Williams formula I correctly get 0.0021674699088523514 from the xTrack function (I presume these are radians). This value correctly matches the worked example - presumably converting this to NM is just a matter of multiplying by WGS84_a() and dividing by Constants::nauticalmile(), which ends up being 7.464NM. This value is way off (even accounting for using a non zero flattening factor). I wonder if you can spot something really obviously wrong with the following block of test code.
Thanks
John
. . .
/static/
std::pair<double, double="">
CoPilotUtils::getTrackInfoNM(
const GeoPointRads& a,
const GeoPointRads& b,
const GeoPointRads& d)
{
std::pair<double, double=""> result;
// Solve the Geodesic inverse problem from A->D
double s12, azi1, azi2, m12, M12, M21, S12 = 0.0;
auto arcAD = gGeodesic.Inverse(a.lat, a.lon, d.lat, d.lon,
s12, azi1, azi2, m12, M12, M21, S12);
const auto azi1AD = azi1;
const auto m12AD = m12;
auto arcAB = gGeodesic.Inverse(a.lat, a.lon, b.lat, b.lon,
s12, azi1, azi2, m12, M12, M21, S12);
const auto azi1AB = azi1;
const auto ellipsiodalXtrackNM = (azi1AD - azi1AB) * m12AD /
GeographicLib::Constants::nauticalmile();
// Assuming D is close to the geodesic AB, then the
// ellipsoidal formula for cross track distance is
// [azi1(AD) - azi1(AB)] * m12(AD). (m12 = reduced length).
// https://sourceforge.net/p/geographiclib/discussion/1026621/thread/299518a3e4/</double,></double,>
}
I can't reproduce your results and don't terribly want to plough through your code snippets. However, here's my working of your example:
Finally
The factor of
pi/180 = Math::degree()
is needed to convert degrees to radians (always .needed in a formula like this) and 1/1852 converts to NM. This seems to .be close to the result you got for the spherical case?Last edit: Charles Karney 2018-11-15
Thank you for the above script, I figured out that my error was passing in radians to GeographicLib - I should have been passing in degrees. I also needed to convert the returned azimuth angles back to radians. Everything was in radians in the williams approach.
By the way, I am not sure where I was it before but from what I can recall I saw an approach you used to use to make a sort of successive approximation to calculating the cross track error. Was that just old code that has been replaced with this updated formula? I wish I could find the example but no luck (I do recall you posted it somewher as a C++ example).
Now I can replace the Williams temporary code with GeographicLib calls.
John
This was the missing trick before calling Geographic lib
Last edit: John Coffey 2018-11-16
OK, good. GeographicLib uses degrees for its interface so that directions like "due south" and positions like the "north pole" can be represented exactly.
NOTE WELL: the formula I gave you applies only for points close to the track. In the general case I recommend using the approach given in Section 8 of Algorithms for geodesics. Elsewhere on this site I give a C++ implementation. Here's an implementation in MATLAB.