Skip to content

Commit 9a32909

Browse files
committed
Restructuring "earth" package and distributing methods
1 parent 2224cdf commit 9a32909

20 files changed

+560
-369
lines changed

earth/earth.go

Lines changed: 3 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,13 @@
1313
// limitations under the License.
1414

1515
/*
16-
Package earth implements functions for working with the planet Earth modeled as
17-
a sphere.
16+
Package earth provides constants for working with the planet Earth modeled as
17+
a sphere. Functions that operate on angles are in s1 (e.g., s1.EarthAngleFromLength),
18+
and functions that operate on s2 geometry types are in s2 (e.g., s2.EarthLengthFromLatLngs).
1819
*/
1920
package earth
2021

2122
import (
22-
"math"
23-
24-
"github.com/golang/geo/s1"
25-
"github.com/golang/geo/s2"
2623
"github.com/google/go-units/unit"
2724
)
2825

@@ -57,63 +54,3 @@ const (
5754

5855
HighestAltitude = 8848 * unit.Meter
5956
)
60-
61-
// AngleFromLength returns the angle from a given distance on the spherical
62-
// earth's surface.
63-
func AngleFromLength(d unit.Length) s1.Angle {
64-
return s1.Angle(float64(d/Radius)) * s1.Radian
65-
}
66-
67-
// LengthFromAngle returns the distance on the spherical earth's surface from
68-
// a given angle.
69-
func LengthFromAngle(a s1.Angle) unit.Length {
70-
return unit.Length(a.Radians()) * Radius
71-
}
72-
73-
// LengthFromPoints returns the distance between two points on the spherical
74-
// earth's surface.
75-
func LengthFromPoints(a, b s2.Point) unit.Length {
76-
return LengthFromAngle(a.Distance(b))
77-
}
78-
79-
// LengthFromLatLngs returns the distance on the spherical earth's surface
80-
// between two LatLngs.
81-
func LengthFromLatLngs(a, b s2.LatLng) unit.Length {
82-
return LengthFromAngle(a.Distance(b))
83-
}
84-
85-
// AreaFromSteradians returns the area on the spherical Earth's surface covered
86-
// by s steradians, as returned by Area() methods on s2 geometry types.
87-
func AreaFromSteradians(s float64) unit.Area {
88-
return unit.Area(s * Radius.Meters() * Radius.Meters())
89-
}
90-
91-
// SteradiansFromArea returns the number of steradians covered by an area on the
92-
// spherical Earth's surface. The value will be between 0 and 4 * math.Pi if a
93-
// does not exceed the area of the Earth.
94-
func SteradiansFromArea(a unit.Area) float64 {
95-
return a.SquareMeters() / (Radius.Meters() * Radius.Meters())
96-
}
97-
98-
// InitialBearingFromLatLngs computes the initial bearing from a to b.
99-
//
100-
// This is the bearing an observer at point a has when facing point b. A bearing
101-
// of 0 degrees is north, and it increases clockwise (90 degrees is east, etc).
102-
//
103-
// If a == b, a == -b, or a is one of the Earth's poles, the return value is
104-
// undefined.
105-
func InitialBearingFromLatLngs(a, b s2.LatLng) s1.Angle {
106-
lat1 := a.Lat.Radians()
107-
cosLat2 := math.Cos(b.Lat.Radians())
108-
latDiff := b.Lat.Radians() - a.Lat.Radians()
109-
lngDiff := b.Lng.Radians() - a.Lng.Radians()
110-
111-
x := math.Sin(latDiff) + math.Sin(lat1)*cosLat2*2*haversine(lngDiff)
112-
y := math.Sin(lngDiff) * cosLat2
113-
return s1.Angle(math.Atan2(y, x)) * s1.Radian
114-
}
115-
116-
func haversine(radians float64) float64 {
117-
sinHalf := math.Sin(radians / 2)
118-
return sinHalf * sinHalf
119-
}

earth/earth_example_test.go

Lines changed: 0 additions & 82 deletions
This file was deleted.

earth/earth_test.go

Lines changed: 12 additions & 181 deletions
Original file line numberDiff line numberDiff line change
@@ -15,195 +15,26 @@
1515
package earth
1616

1717
import (
18-
"math"
1918
"testing"
20-
21-
"github.com/golang/geo/s1"
22-
"github.com/golang/geo/s2"
23-
"github.com/google/go-units/unit"
2419
)
2520

26-
func float64Eq(x, y float64) bool {
27-
if x == y {
28-
return true
29-
}
30-
if math.Abs(x) > math.Abs(y) {
31-
return math.Abs(1-y/x) < 1e-14
32-
}
33-
return math.Abs(1-x/y) < 1e-14
34-
}
35-
36-
var degreesToMeters = []struct {
37-
angle s1.Angle
38-
length unit.Length
39-
}{
40-
{-89.93201943346866 * s1.Degree, -1e7 * unit.Meter},
41-
{-30 * s1.Degree, -3335853.035324518 * unit.Meter},
42-
{0 * s1.Degree, 0 * unit.Meter},
43-
{30 * s1.Degree, 3335853.035324518 * unit.Meter},
44-
{89.93201943346866 * s1.Degree, 1e7 * unit.Meter},
45-
{90 * s1.Degree, 10007559.105973555 * unit.Meter},
46-
{179.86403886693734 * s1.Degree, 2e7 * unit.Meter},
47-
{180 * s1.Degree, 20015118.21194711 * unit.Meter},
48-
{359.72807773387467 * s1.Degree, 4e7 * unit.Meter},
49-
{360 * s1.Degree, 40030236.42389422 * unit.Meter},
50-
{899.3201943346867 * s1.Degree, 1e8 * unit.Meter},
51-
}
52-
53-
func TestAngleFromLength(t *testing.T) {
54-
for _, test := range degreesToMeters {
55-
if got, want := AngleFromLength(test.length), test.angle; !float64Eq(got.Radians(), want.Radians()) {
56-
t.Errorf("AngleFromLength(%v) = %v, want %v", test.length, got, want)
57-
}
58-
}
59-
}
60-
61-
func TestLengthFromAngle(t *testing.T) {
62-
for _, test := range degreesToMeters {
63-
if got, want := LengthFromAngle(test.angle), test.length; !float64Eq(got.Meters(), want.Meters()) {
64-
t.Errorf("LengthFromAngle(%v) = %v, want %v", test.angle, got, want)
65-
}
66-
}
67-
}
68-
69-
func TestLengthFromPoints(t *testing.T) {
70-
tests := []struct {
71-
x1, y1, z1 float64
72-
x2, y2, z2 float64
73-
length unit.Length
74-
}{
75-
{1, 0, 0, 1, 0, 0, 0 * unit.Meter},
76-
{1, 0, 0, 0, 1, 0, 10007559.105973555 * unit.Meter},
77-
{1, 0, 0, 0, 1, 1, 10007559.105973555 * unit.Meter},
78-
{1, 0, 0, -1, 0, 0, 20015118.21194711 * unit.Meter},
79-
{1, 2, 3, 2, 3, -1, 7680820.247060414 * unit.Meter},
80-
}
81-
for _, test := range tests {
82-
p1 := s2.PointFromCoords(test.x1, test.y1, test.z1)
83-
p2 := s2.PointFromCoords(test.x2, test.y2, test.z2)
84-
if got, want := LengthFromPoints(p1, p2), test.length; !float64Eq(got.Meters(), want.Meters()) {
85-
t.Errorf("LengthFromPoints(%v, %v) = %v, want %v", p1, p2, got, want)
86-
}
87-
}
88-
}
89-
90-
func TestLengthFromLatLngs(t *testing.T) {
91-
tests := []struct {
92-
lat1, lng1, lat2, lng2 float64
93-
length unit.Length
94-
}{
95-
{90, 0, 90, 0, 0 * unit.Meter},
96-
{-37, 25, -66, -155, 8562022.790666264 * unit.Meter},
97-
{0, 165, 0, -80, 12787436.635410652 * unit.Meter},
98-
{47, -127, -47, 53, 20015118.077688109 * unit.Meter},
99-
{51.961951, -180.227156, 51.782383, 181.126878, 95.0783566198074 * unit.Kilometer},
100-
}
101-
for _, test := range tests {
102-
ll1 := s2.LatLngFromDegrees(test.lat1, test.lng1)
103-
ll2 := s2.LatLngFromDegrees(test.lat2, test.lng2)
104-
if got, want := LengthFromLatLngs(ll1, ll2), test.length; !float64Eq(got.Meters(), want.Meters()) {
105-
t.Errorf("LengthFromLatLngs(%v, %v) = %v, want %v", ll1, ll2, got, want)
106-
}
107-
}
108-
}
109-
110-
var (
111-
earthArea = unit.Area(Radius.Meters()*Radius.Meters()) * math.Pi * 4
112-
steradiansToArea = []struct {
113-
steradians float64
114-
area unit.Area
115-
}{
116-
{1, earthArea / 4 / math.Pi},
117-
{4 * math.Pi, earthArea},
118-
{s2.PolygonFromLoops([]*s2.Loop{s2.FullLoop()}).Area(), earthArea},
119-
{s2.PolygonFromLoops([]*s2.Loop{s2.LoopFromPoints([]s2.Point{
120-
s2.PointFromLatLng(s2.LatLngFromDegrees(-90, 0)),
121-
s2.PointFromLatLng(s2.LatLngFromDegrees(0, 0)),
122-
s2.PointFromLatLng(s2.LatLngFromDegrees(90, 0)),
123-
s2.PointFromLatLng(s2.LatLngFromDegrees(0, -90)),
124-
})}).Area(), earthArea / 4},
125-
{s2.CellFromCellID(s2.CellIDFromFace(2)).ExactArea(), earthArea / 6},
126-
{s2.AvgAreaMetric.Value(10), 81.07281893380302 * unit.SquareKilometer}, // average area of level 10 cells
127-
{s2.AvgAreaMetric.Value(20), 77.31706517582228 * unit.SquareMeter}, // average area of level 20 cells
128-
{s2.AvgAreaMetric.Value(30), 73.73529927808979 * unit.SquareMillimeter}, // average area of level 30 cells
129-
{s2.PolygonFromLoops([]*s2.Loop{s2.EmptyLoop()}).Area(), 0 * unit.SquareMeter},
130-
}
131-
)
132-
133-
func TestAreaFromSteradians(t *testing.T) {
134-
for _, test := range steradiansToArea {
135-
if got, want := AreaFromSteradians(test.steradians), test.area; !float64Eq(got.SquareMeters(), want.SquareMeters()) {
136-
t.Errorf("AreaFromSteradians(%v) = %v, want %v", test.steradians, got, want)
137-
}
138-
}
139-
}
140-
141-
func TestSteradiansFromArea(t *testing.T) {
142-
for _, test := range steradiansToArea {
143-
if got, want := SteradiansFromArea(test.area), test.steradians; !float64Eq(got, want) {
144-
t.Errorf("SteradiansFromArea(%v) = %v, want %v", test.area, got, want)
145-
}
21+
func TestRadius(t *testing.T) {
22+
// Verify the Earth radius constant is approximately correct.
23+
if got, want := Radius.Kilometers(), 6371.01; got != want {
24+
t.Errorf("Radius.Kilometers() = %v, want %v", got, want)
14625
}
14726
}
14827

149-
func TestInitialBearingFromLatLngs(t *testing.T) {
150-
for _, tc := range []struct {
151-
name string
152-
a, b s2.LatLng
153-
want s1.Angle
154-
}{
155-
{"Westward on equator", s2.LatLngFromDegrees(0, 50),
156-
s2.LatLngFromDegrees(0, 100), s1.Degree * 90},
157-
{"Eastward on equator", s2.LatLngFromDegrees(0, 50),
158-
s2.LatLngFromDegrees(0, 0), s1.Degree * -90},
159-
{"Northward on meridian", s2.LatLngFromDegrees(16, 28),
160-
s2.LatLngFromDegrees(81, 28), s1.Degree * 0},
161-
{"Southward on meridian", s2.LatLngFromDegrees(24, 64),
162-
s2.LatLngFromDegrees(-27, 64), s1.Degree * 180},
163-
{"Towards north pole", s2.LatLngFromDegrees(12, 76),
164-
s2.LatLngFromDegrees(90, 50), s1.Degree * 0},
165-
{"Towards south pole", s2.LatLngFromDegrees(-35, 105),
166-
s2.LatLngFromDegrees(-90, -120), s1.Degree * 180},
167-
{"Spain to Japan", s2.LatLngFromDegrees(40.4379332, -3.749576),
168-
s2.LatLngFromDegrees(35.6733227, 139.6403486), s1.Degree * 29.2},
169-
{"Japan to Spain", s2.LatLngFromDegrees(35.6733227, 139.6403486),
170-
s2.LatLngFromDegrees(40.4379332, -3.749576), s1.Degree * -27.2},
171-
} {
172-
t.Run(tc.name, func(t *testing.T) {
173-
got := InitialBearingFromLatLngs(tc.a, tc.b)
174-
if diff := (got - tc.want).Abs(); diff > 0.01 {
175-
t.Errorf("InitialBearingFromLatLngs(%s, %s): got %s, want %s, diff %s", tc.a, tc.b, got, tc.want, diff)
176-
}
177-
})
28+
func TestLowestAltitude(t *testing.T) {
29+
// Mariana Trench depth
30+
if got, want := LowestAltitude.Meters(), -10898.0; got != want {
31+
t.Errorf("LowestAltitude.Meters() = %v, want %v", got, want)
17832
}
17933
}
18034

181-
func TestInitialBearingFromLatLngsUndefinedResultDoesNotCrash(t *testing.T) {
182-
// InitialBearingFromLatLngs says if a == b, a == -b, or a is one of Earth's
183-
// poles, the return value is undefined. Make sure it returns a real value
184-
// (but don't assert what it is) rather than panicking or NaN.
185-
// Bearing from a pole is undefined because 0° is north, but the observer
186-
// can't face north from the north pole, so the calculation depends on the
187-
// latitude value at the pole, even though 90°N 123°E and 90°N 45°W represent
188-
// the same point. Bearing is undefined when a == b because the observer can
189-
// point any direction and still be present. Bearing is undefined when
190-
// a == -b (two antipodal points) because there are two possible paths.
191-
for _, tc := range []struct {
192-
name string
193-
a, b s2.LatLng
194-
}{
195-
{"North pole prime meridian to Null Island", s2.LatLngFromDegrees(90, 0), s2.LatLngFromDegrees(0, 0)},
196-
{"North pole facing east to Guatemala", s2.LatLngFromDegrees(90, 90), s2.LatLngFromDegrees(15, -90)},
197-
{"South pole facing west to McMurdo", s2.LatLngFromDegrees(-90, -90), s2.LatLngFromDegrees(-78, 166)},
198-
{"South pole anti-prime meridian to Null Island", s2.LatLngFromDegrees(-90, -180), s2.LatLngFromDegrees(0, 0)},
199-
{"Jakarta and antipode", s2.LatLngFromDegrees(-6.109, 106.668), s2.LatLngFromDegrees(6.109, -180+106.668)},
200-
{"Alert and antipode", s2.LatLngFromDegrees(82.499, -62.350), s2.LatLngFromDegrees(-82.499, 180-62.350)},
201-
} {
202-
t.Run(tc.name, func(t *testing.T) {
203-
got := InitialBearingFromLatLngs(tc.a, tc.b)
204-
if math.IsNaN(got.Radians()) || math.IsInf(got.Radians(), 0) {
205-
t.Errorf("InitialBearingFromLatLngs(%s, %s): got %s, want a real value", tc.a, tc.b, got)
206-
}
207-
})
35+
func TestHighestAltitude(t *testing.T) {
36+
// Mount Everest height
37+
if got, want := HighestAltitude.Meters(), 8848.0; got != want {
38+
t.Errorf("HighestAltitude.Meters() = %v, want %v", got, want)
20839
}
20940
}

0 commit comments

Comments
 (0)