Skip to content

Commit e4cf3ef

Browse files
committed
Add initial ellipsoid code
1 parent fe61f15 commit e4cf3ef

File tree

2 files changed

+130
-0
lines changed

2 files changed

+130
-0
lines changed

ellipsoid/ellipsoid.go

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package ellipsoid
2+
3+
// FIXME add Vincenty algorithm https://github.com/twpayne/ol3/blob/189ba34955029280ba348a65540e5e36539477f9/src/ol/ellipsoid/ellipsoid.js#L56
4+
// FIXME compare with https://github.com/StefanSchroeder/Golang-Ellipsoid
5+
6+
import (
7+
"math"
8+
9+
"github.com/twpayne/go-kml/v3"
10+
)
11+
12+
// An Ellipsoid is an ellipsoid.
13+
type Ellipsoid struct {
14+
A float64
15+
Flattening float64
16+
}
17+
18+
// WGS84 is the WGS84 ellipsoid.
19+
var WGS84 = Ellipsoid{
20+
A: 6378137,
21+
Flattening: 1.0 / 298.257223563,
22+
}
23+
24+
// Distance returns the distance between c1 and c2.
25+
func (e Ellipsoid) Distance(c1, c2 kml.Coordinate) float64 {
26+
// See "Annex C Ellipsoid distance between two points" in the "FAI Sporting
27+
// Code: Section 7F – XC Scoring".
28+
//
29+
// https://fai.org/sites/default/files/civl/documents/sporting_code_s7_f_-_xc_scoring_2025_v1.0.pdf
30+
lat1r := math.Pi * c1.Lat / 180
31+
lon1r := math.Pi * c1.Lon / 180
32+
lat2r := math.Pi * c2.Lat / 180
33+
lon2r := math.Pi * c2.Lon / 180
34+
if lon1r == lon2r && lat1r == lat2r {
35+
return 0
36+
}
37+
theta1 := math.Atan(e.oneMinusF() * math.Tan(lat1r))
38+
theta2 := math.Atan(e.oneMinusF() * math.Tan(lat2r))
39+
thetaM := (theta1 + theta2) / 2.0
40+
dThetaM := (theta2 - theta1) / 2.0
41+
dLambda := lon2r - lon1r
42+
dLambdaM := dLambda / 2.0
43+
sinThetaM := math.Sin(thetaM)
44+
cosThetaM := math.Cos(thetaM)
45+
sinDThetaM := math.Sin(dThetaM)
46+
cosDThetaM := math.Cos(dThetaM)
47+
sin2ThetaM := sinThetaM * sinThetaM
48+
cos2ThetaM := cosThetaM * cosThetaM
49+
sin2DThetaM := sinDThetaM * sinDThetaM
50+
cos2DThetaM := cosDThetaM * cosDThetaM
51+
sinDLambdaM := math.Sin(dLambdaM)
52+
sin2DLambdaM := sinDLambdaM * sinDLambdaM
53+
H := cos2ThetaM - sin2DThetaM
54+
L := sin2DThetaM + H*sin2DLambdaM
55+
cosD := 1.0 - 2.0*L
56+
d := math.Acos(cosD)
57+
sinD := math.Sin(d)
58+
oneMinusL := 1.0 - L
59+
if sinD == 0.0 || L == 0.0 || oneMinusL == 0.0 {
60+
return 0
61+
}
62+
U := 2.0 * sin2ThetaM * cos2DThetaM / oneMinusL
63+
V := 2.0 * sin2DThetaM * cos2ThetaM / L
64+
X := U + V
65+
Y := U - V
66+
T := d / sinD
67+
D := 4.0 * T * T
68+
E := 2.0 * cosD
69+
A := D * E
70+
B := 2.0 * D
71+
C := T - (A-E)/2.0
72+
n1 := X * (A + C*X)
73+
n2 := Y * (B + E*Y)
74+
n3 := D * X * Y
75+
delta1d := e.Flattening * (T*X - Y) / 4.0
76+
delta2d := e.flatSq64() * (n1 - n2 + n3)
77+
return e.A * sinD * (T - delta1d + delta2d)
78+
}
79+
80+
func (e Ellipsoid) flatSq64() float64 {
81+
return e.Flattening * e.Flattening / 64
82+
}
83+
84+
func (e Ellipsoid) oneMinusF() float64 {
85+
return 1 - e.Flattening
86+
}

ellipsoid/ellipsoid_test.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package ellipsoid_test
2+
3+
import (
4+
"math"
5+
"strconv"
6+
"testing"
7+
8+
"github.com/alecthomas/assert/v2"
9+
10+
"github.com/twpayne/go-kml/v3"
11+
"github.com/twpayne/go-kml/v3/ellipsoid"
12+
"github.com/twpayne/go-kml/v3/sphere"
13+
)
14+
15+
func TestEllipsoid_Distance(t *testing.T) {
16+
t.Parallel()
17+
18+
sphere := sphere.WGS84
19+
ellipsoid := ellipsoid.WGS84
20+
21+
const (
22+
deltaDegrees = 0.02 // A delta of +0.02 degrees in both lat and lon at null island about 3145m
23+
sphericalTolerance = 11
24+
)
25+
26+
for lat := -89; lat <= 89; lat++ {
27+
t.Run("lat"+strconv.Itoa(lat), func(t *testing.T) {
28+
t.Parallel()
29+
for lon := -179; lon <= 179; lon++ {
30+
t.Run("lon"+strconv.Itoa(lon), func(t *testing.T) {
31+
for _, deltaLat := range []float64{-deltaDegrees, deltaDegrees} {
32+
for _, deltaLon := range []float64{-deltaDegrees, deltaDegrees} {
33+
c1 := kml.Coordinate{Lat: float64(lat), Lon: float64(lon)}
34+
c2 := kml.Coordinate{Lat: float64(lat) + deltaLat, Lon: float64(lon) + deltaLon}
35+
sphericalDistance := sphere.HaversineDistance(c1, c2)
36+
ellipsoidDistance := ellipsoid.Distance(c1, c2)
37+
assert.True(t, math.Abs(ellipsoidDistance-sphericalDistance) < sphericalTolerance)
38+
}
39+
}
40+
})
41+
}
42+
})
43+
}
44+
}

0 commit comments

Comments
 (0)