Skip to content

Commit

Permalink
Add tests, update docs, expose coordinate bound constants
Browse files Browse the repository at this point in the history
  • Loading branch information
lggomez committed Oct 3, 2021
1 parent 7405d6b commit af803da
Show file tree
Hide file tree
Showing 5 changed files with 105 additions and 17 deletions.
17 changes: 15 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,17 @@ type Point [2]float64

Point represents a latitude-longitude pair in decimal degrees

Constants

```go
const (
LatLowerBound = float64(-90)
LatUpperBound = float64(90)
LonLowerBound = float64(-180)
LonUpperBound = float64(180)
)
```

#### func (Point) Antipode

```go
Expand Down Expand Up @@ -106,8 +117,10 @@ the returned distance will be math.NaN().
func VincentyInverse(p1, p2 geodesy.Point, accuracy float64, calculateAzimuth bool) (float64, float64, float64)
```

VincentyInverse calculates the ellipsoidal distance in meters and azimuth in degrees between 2 points using the inverse Vincenty formulae and the WGS-84 ellipsoid constants. As it is an iterative operation it will converge to the defined accuracy, if accuracy < 0 it will use the default accuracy of 1e-12 (approximately 0.06 mm). If
calculateAzimuth is set to true, it will compute the forward and reverse azimuths (otherwise, these default to math.NaN())
VincentyInverse calculates the ellipsoidal distance in meters and azimuth in degrees between 2 points using the
inverse Vincenty formulae and the WGS-84 ellipsoid constants. As it is an iterative operation it will converge to
the defined accuracy, if accuracy < 0 it will use the default accuracy of 1e-12 (approximately 0.06 mm, magnitude should be no bigger than 1e-6).
If calculateAzimuth is set to true, it will compute the forward and reverse azimuths (otherwise, these default to math.NaN()).
If any of the points does not constitute a valid geographic coordinate, the returned distance will be math.NaN().

The following notations are used in the implementation:
Expand Down
23 changes: 22 additions & 1 deletion distance/haversine_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package distance_test

import (
"math"
"testing"

"github.com/lggomez/go-geodesy"
Expand Down Expand Up @@ -118,11 +119,31 @@ func TestHaversine(t *testing.T) {
},
expectedDistance: 2.0015114352233686e+07,
},
{
name: "FAIL/invalid_p1",
args: args{
p1: geodesy.Point{geodesy.LatUpperBound+1, -57.534954},
p2: geodesy.Point{-34.579340, -57.534954},
},
expectedDistance: math.NaN(),
},
{
name: "FAIL/invalid_p2",
args: args{
p1: geodesy.Point{-34.579340, -57.534954},
p2: geodesy.Point{geodesy.LatUpperBound+1, -57.534954},
},
expectedDistance: math.NaN(),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
d := distance.Haversine(tt.args.p1, tt.args.p2)
assert.Equal(t, tt.expectedDistance, d)
if math.IsNaN(tt.expectedDistance) {
assert.True(t, math.IsNaN(d), "got %f", d)
} else {
assert.EqualValues(t, tt.expectedDistance, d)
}
})
}
}
4 changes: 2 additions & 2 deletions distance/vincenty.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ const (
/*
VincentyInverse calculates the ellipsoidal distance in meters and azimuth in degrees between 2 points using the
inverse Vincenty formulae and the WGS-84 ellipsoid constants. As it is an iterative operation it will converge to
the defined accuracy, if accuracy < 0 it will use the default accuracy of 1e-12 (approximately 0.06 mm). If
calculateAzimuth is set to true, it will compute the forward and reverse azimuths (otherwise, these default to math.NaN()).
the defined accuracy, if accuracy < 0 it will use the default accuracy of 1e-12 (approximately 0.06 mm, magnitude should be no bigger than 1e-6).
If calculateAzimuth is set to true, it will compute the forward and reverse azimuths (otherwise, these default to math.NaN()).
If any of the points does not constitute a valid geographic coordinate, the returned distance will be math.NaN().
The following notations are used:
Expand Down
60 changes: 57 additions & 3 deletions distance/vincenty_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ func TestVincentyInverse(t *testing.T) {
antipodeOriginSE := geodesy.Point{-46.272337, 169.398118}
antipodeSE := antipodeOriginSE.Antipode()

// Antipodes will fail to converge on the exact points
// and within a short radius, so induce a non-convergent
// point pair
antipodeNonconvOrigin := geodesy.Point{42.35831235, -95.31046631}
antipodeNonconv := geodesy.Point{antipodeNonconvOrigin.Antipode().Lat() + 0.00000001, antipodeNonconvOrigin.Antipode().Lon() + 0.00000001}

type args struct {
p1 geodesy.Point
p2 geodesy.Point
Expand Down Expand Up @@ -71,6 +77,18 @@ func TestVincentyInverse(t *testing.T) {
expectedAzimuth1: math.NaN(),
expectedAzimuth2: math.NaN(),
},
{
name: "OK/SW_sub_1k_km_less_accurate",
args: args{
p1: geodesy.Point{-37.550643, -56.51251},
p2: geodesy.Point{-37.5507, -56.5126},
accuracy: 1e-6,
calculateAzimuth: false,
},
expectedDistance: 10.149099956337418,
expectedAzimuth1: math.NaN(),
expectedAzimuth2: math.NaN(),
},
{
name: "OK/SW/SW_sub_500km",
args: args{
Expand Down Expand Up @@ -179,6 +197,18 @@ func TestVincentyInverse(t *testing.T) {
expectedAzimuth1: 28.09660240174846,
expectedAzimuth2: 208.17248801650823,
},
{
name: "FAIL/divergent",
args: args{
p1: antipodeNonconvOrigin,
p2: antipodeNonconv,
accuracy: -1,
calculateAzimuth: true,
},
expectedDistance: math.NaN(),
expectedAzimuth1: math.NaN(),
expectedAzimuth2: math.NaN(),
},
{
name: "FAIL/antipode",
args: args{
Expand All @@ -191,23 +221,47 @@ func TestVincentyInverse(t *testing.T) {
expectedAzimuth1: math.NaN(),
expectedAzimuth2: math.NaN(),
},
{
name: "FAIL/invalid_p1",
args: args{
p1: geodesy.Point{geodesy.LatUpperBound+1, -57.534954},
p2: geodesy.Point{-34.579340, -57.534954},
accuracy: -1,
calculateAzimuth: true,
},
expectedDistance: math.NaN(),
expectedAzimuth1: math.NaN(),
expectedAzimuth2: math.NaN(),
},
{
name: "FAIL/invalid_p2",
args: args{
p1: geodesy.Point{-34.579340, -57.534954},
p2: geodesy.Point{geodesy.LatUpperBound+1, -57.534954},
accuracy: -1,
calculateAzimuth: true,
},
expectedDistance: math.NaN(),
expectedAzimuth1: math.NaN(),
expectedAzimuth2: math.NaN(),
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
d, az1, az2 := distance.VincentyInverse(tt.args.p1, tt.args.p2, tt.args.accuracy, tt.args.calculateAzimuth)
if math.IsNaN(tt.expectedDistance) {
assert.True(t, math.IsNaN(d))
assert.True(t, math.IsNaN(d), "got %f", d)
} else {
assert.EqualValues(t, tt.expectedDistance, d)
}
if math.IsNaN(tt.expectedAzimuth1) {
assert.True(t, math.IsNaN(az1))
assert.True(t, math.IsNaN(az1), "got %f", az1)
} else {
assert.EqualValues(t, tt.expectedAzimuth1, az1)
}
if math.IsNaN(tt.expectedAzimuth2) {
assert.True(t, math.IsNaN(az2))
assert.True(t, math.IsNaN(az2), "got %f", az2)
} else {
assert.EqualValues(t, tt.expectedAzimuth2, az2)
}
Expand Down
18 changes: 9 additions & 9 deletions point.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ package geodesy
import "math"

const (
latLowerBound = float64(-90)
latUpperBound = float64(90)
lonLowerBound = float64(-180)
lonUpperBound = float64(180)
LatLowerBound = float64(-90)
LatUpperBound = float64(90)
LonLowerBound = float64(-180)
LonUpperBound = float64(180)
)

// Point represents a latitude-longitude pair in decimal degrees
Expand All @@ -19,7 +19,7 @@ func (p Point) Lat() float64 {

// LatRadians returns point p's latitude in radians
func (p Point) LatRadians() float64 {
return (p[0]*math.Pi)/180
return (p[0] * math.Pi) / 180
}

// Lon returns point p's longitude
Expand All @@ -29,7 +29,7 @@ func (p Point) Lon() float64 {

// LonRadians returns point p's longitude in radians
func (p Point) LonRadians() float64 {
return (p[1]*math.Pi)/180
return (p[1] * math.Pi) / 180
}

// Antipode returns a new point representing the geographical antipode of p
Expand All @@ -41,7 +41,7 @@ func (p Point) Antipode() Point {
func (p Point) IsAntipodeOf(p2 Point) bool {
// Shorthand check to avoid Equals() calls between p and p2
return ((p[0] == -p2[0]) && (p[1] == (180 - math.Abs(p2[1])))) ||
(p2[0] == -p[0]) && (p2[1] == (180 - math.Abs(p[1])))
(p2[0] == -p[0]) && (p2[1] == (180-math.Abs(p[1])))
}

// Equals returns whether p is equal in latitude and longitude to p2
Expand All @@ -52,6 +52,6 @@ func (p Point) Equals(p2 Point) bool {
// Valid returns whether p is valid, that is, contained within the valid range of
// geographic coordinates
func (p Point) Valid() bool {
return ((p[0] >= latLowerBound) && (p[0] <= latUpperBound)) &&
((p[1] >= lonLowerBound) && (p[1] <= lonUpperBound))
return ((p[0] >= LatLowerBound) && (p[0] <= LatUpperBound)) &&
((p[1] >= LonLowerBound) && (p[1] <= LonUpperBound))
}

0 comments on commit af803da

Please sign in to comment.