-
Notifications
You must be signed in to change notification settings - Fork 23
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Eaxcact distance #7
Comments
Hi @mbecker First of all thank you for filling this issue and spending your time researching the problem. I agree with all your points. Do you maybe have the time to implement them yourself. If not, can I use the For example:
What do you think? Ps. Regarding:
Neither do I. I tried to understand it (not only for Strava) times, but no luck. :( At the end of the day every software uses its own heuristics to make a good guesstimate. |
Hi @tkrajina, I've implemented it in a more easy way. The requirement should be at first not to break your API and that's why I've just added a new function: func (g *GPX) LengthVincenty() float64 gpx.go
geo.go const (
oneDegree = 1000.0 * 10000.8 / 90.0
earthRadius float64 = 6378137 // WGS-84 ellipsoid; See https://en.wikipedia.org/wiki/World_Geodetic_System
flattening float64 = 1 / 298.257223563
semiMinorAxisB float64 = 6356752.314245
//
epsilon = 1e-12
maxIterations = 200
)
// Vincenty formula
func toRadians(deg float64) float64 {
return deg * (math.Pi / 180)
}
func lengthVincenty(locs []Point) (float64, error) {
var previousLoc Point
var res float64
for k, v := range locs {
if k > 0 {
previousLoc = locs[k-1]
d, err := v.DistanceVincenty(&previousLoc)
if err != nil {
return 0, err
}
res += d
}
}
return res, nil
}
// LengthVincenty returns the geographical distance in km between the points p1 (lat1, long1) and p2 (lat2, long2) using Vincenty's inverse formula.
// The surface of the Earth is approximated by the WGS-84 ellipsoid.
// This method may fail to converge for nearly antipodal points.
// https://github.com/asmarques/geodist/blob/master/vincenty.go
func DistanceVincenty(lat1, long1, lat2, long2 float64) (float64, error) {
if lat1 == lat2 && long1 == long2 {
return 0, nil
}
U1 := math.Atan((1 - flattening) * math.Tan(toRadians(lat1)))
U2 := math.Atan((1 - flattening) * math.Tan(toRadians(lat2)))
L := toRadians(long2 - long1)
sinU1 := math.Sin(U1)
cosU1 := math.Cos(U1)
sinU2 := math.Sin(U2)
cosU2 := math.Cos(U2)
lambda := L
result := math.NaN()
for i := 0; i < maxIterations; i++ {
curLambda := lambda
sinSigma := math.Sqrt(math.Pow(cosU2*math.Sin(lambda), 2) +
math.Pow(cosU1*sinU2-sinU1*cosU2*math.Cos(lambda), 2))
cosSigma := sinU1*sinU2 + cosU1*cosU2*math.Cos(lambda)
sigma := math.Atan2(sinSigma, cosSigma)
sinAlpha := (cosU1 * cosU2 * math.Sin(lambda)) / math.Sin(sigma)
cosSqrAlpha := 1 - math.Pow(sinAlpha, 2)
cos2sigmam := 0.0
if cosSqrAlpha != 0 {
cos2sigmam = math.Cos(sigma) - ((2 * sinU1 * sinU2) / cosSqrAlpha)
}
C := (flattening / 16) * cosSqrAlpha * (4 + flattening*(4-3*cosSqrAlpha))
lambda = L + (1-C)*flattening*sinAlpha*(sigma+C*sinSigma*(cos2sigmam+C*cosSigma*(-1+2*math.Pow(cos2sigmam, 2))))
if math.Abs(lambda-curLambda) < epsilon {
uSqr := cosSqrAlpha * ((math.Pow(earthRadius, 2) - math.Pow(semiMinorAxisB, 2)) / math.Pow(semiMinorAxisB, 2))
k1 := (math.Sqrt(1+uSqr) - 1) / (math.Sqrt(1+uSqr) + 1)
A := (1 + (math.Pow(k1, 2) / 4)) / (1 - k1)
B := k1 * (1 - (3*math.Pow(k1, 2))/8)
deltaSigma := B * sinSigma * (cos2sigmam + (B/4)*(cosSigma*(-1+2*math.Pow(cos2sigmam, 2))-
(B/6)*cos2sigmam*(-3+4*math.Pow(sinSigma, 2))*(-3+4*math.Pow(cos2sigmam, 2))))
s := semiMinorAxisB * A * (sigma - deltaSigma)
result = s / 1000
break
}
}
if math.IsNaN(result) {
return result, fmt.Errorf("failed to converge for Point(Latitude: %f, Lomgitude: %f) and Point(Latitude: %f, Lomgitude: %f)", lat1, long1, lat2, long2)
}
return result, nil
}
//Length3D calculates the lenght of given points list including elevation distance
func LengthVincenty(locs []Point) (float64, error) {
return lengthVincenty(locs)
} I've adapted your style to use the funcs but would be happy to help that a user may provide a custom calculation method. P.s. The used method for Vincenty is from: https://github.com/asmarques/geodist/blob/master/vincenty.go |
Is there anything I can do to assist whatever getting merged in? |
Hi @EliDavis3D , I'm sorry, the posts are quite old and I do not remember all stuff. But I see that I've more or less clearly described what to do ;-) Looking at my repos and my commits for the forked repo at 42d8413 I would say that the implementation is already done and must be only pushed back to the original repo. Because I have in my implementation some references to my forked repo I would say do the following: (1) Fork this repo Does this make sense? :-) |
Hi @tkrajina ,
thanks for the library! Good work!
Referenced gpx file: https://gist.github.com/mbecker/a44881bfe29b0982fac6c69cae498125
Looking at the length2d and length3d of the referenced gpx file I've noticed that Strava is showing me different values.
The Strava values are as follows:
The gpxgo values are as follows:
So looking into your code I've noticed the following:
The method "HaversineDistance" is only used if the distance between the two points are too distant:
gpxgo/gpx/geo.go
Line 188 in 1b1f71e
-> Valid aproach since you are saying in gpxpy that the calculation is too complex (Correct distance between points with haversine formula gpxpy#9)
-> Maybe the user should decide which operation to use? Just an idea!
In gpxy you've changed the value for EARTH_RADIUS as follows: jedie/gpxpy@b371de3
-> In gpxgo it's still the "old" value:
gpxgo/gpx/geo.go
Line 14 in 1b1f71e
-> Since the value of "earthRadius" is only in the func "HaversineDistance" it's not used for calculation
-> I've manually changed the function to use "HaversineDistance" and gets the follwing results:
(Not sure about this)
In the func "HaversineDistance" the elevation is not used. Is that by design of the formula?
Digging into the different formulas for calculating the distance between two points I've read that the formula "Vincenty" should be more accurate (https://en.wikipedia.org/wiki/Vincenty%27s_formulae). Using that formula I do get the following value:
(5) I've inserted all points into a postgresql postgis database and created a LINESTRING() with the points. Postgresql Potsgis is calculating the length of the LINESTRING as follows:
So to summarize my observation of the different calculations are as follows:
Referencing to the gpxpy issue 123 (tkrajina/gpxpy#123) and using the generated generated_10km,gpx and half_equator.gpx the results are as follows:
Based on the test results I would propose to do the following:
gpxgo/gpx/geo.go
Line 14 in 1b1f71e
Starting from my original question, I still don't kow "how strava is calculating the distance" :-D
What do you think of the small updates?
Thanks again for your work and best regards.
The text was updated successfully, but these errors were encountered: