Skip to content

Commit c5087ca

Browse files
authored
Add S2Earth (#191)
This is a copy of the google3 implementation, which was not included in previously because it lived elsewhere in the source tree. References to other files in google3 have been removed from comments. Fixes #151
1 parent 8c117c3 commit c5087ca

File tree

5 files changed

+416
-1
lines changed

5 files changed

+416
-1
lines changed

earth/earth.go

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
/*
16+
Package earth implements functions for working with the planet Earth modeled as
17+
a sphere.
18+
*/
19+
package earth
20+
21+
import (
22+
"math"
23+
24+
"github.com/golang/geo/s1"
25+
"github.com/golang/geo/s2"
26+
"github.com/google/go-units/unit"
27+
)
28+
29+
const (
30+
// Radius is the Earth's mean radius, which is the radius of the
31+
// equivalent sphere with the same surface area. According to NASA,
32+
// this value is 6371.01 +/- 0.02 km. The equatorial radius is 6378.136
33+
// km, and the polar radius is 6356.752 km. They differ by one part in
34+
// 298.257.
35+
//
36+
// Reference: http://ssd.jpl.nasa.gov/phys_props_earth.html, which
37+
// quotes Yoder, C.F. 1995. "Astrometric and Geodetic Properties of
38+
// Earth and the Solar System" in Global Earth Physics, A Handbook of
39+
// Physical Constants, AGU Reference Shelf 1, American Geophysical
40+
// Union, Table 2.
41+
//
42+
// This value is the same as in s2earth.h and S2Earth.java in order to be
43+
// able to make consistent conversions across programming languages.
44+
Radius = 6371.01 * unit.Kilometer
45+
46+
// LowestAltitude is the altitude of the lowest known point on Earth,
47+
// the Challenger Deep, below the surface of the spherical Earth. This value
48+
// is the same as the C++ and Java implementations of S2Earth. The value may
49+
// change as more precise measurements are made.
50+
51+
LowestAltitude = -10898 * unit.Meter
52+
53+
// HighestAltitude is the altitude of the highest known point on Earth,
54+
// Mount Everest, above the surface of the spherical Earth. This value is the
55+
// same as the C++ and Java implementations of S2Earth. The value may change
56+
// as more precise measurements are made.
57+
58+
HighestAltitude = 8848 * unit.Meter
59+
)
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: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package earth_test
16+
17+
import (
18+
"fmt"
19+
"math"
20+
21+
"github.com/golang/geo/earth"
22+
"github.com/golang/geo/s1"
23+
"github.com/golang/geo/s2"
24+
"github.com/google/go-units/unit"
25+
)
26+
27+
func ExampleAngleFromLength() {
28+
length := 500 * unit.Mile
29+
angle := earth.AngleFromLength(length)
30+
fmt.Printf("I would walk 500 miles (%.4f rad)", angle.Radians())
31+
// Output: I would walk 500 miles (0.1263 rad)
32+
}
33+
34+
func ExampleLengthFromAngle() {
35+
angle := 2 * s1.Degree
36+
length := earth.LengthFromAngle(angle)
37+
fmt.Printf("2° is %.0f miles", length.Miles())
38+
// Output: 2° is 138 miles
39+
}
40+
41+
func ExampleLengthFromPoints() {
42+
equator := s2.PointFromCoords(1, 0, 0)
43+
pole := s2.PointFromCoords(0, 1, 0)
44+
length := earth.LengthFromPoints(equator, pole)
45+
fmt.Printf("Equator to pole is %.2f km, π/2*Earth radius is %.2f km",
46+
length.Kilometers(), math.Pi/2*earth.Radius.Kilometers())
47+
// Output: Equator to pole is 10007.56 km, π/2*Earth radius is 10007.56 km
48+
}
49+
50+
func ExampleLengthFromLatLngs() {
51+
chukchi := s2.LatLngFromDegrees(66.025893, -169.699684)
52+
seward := s2.LatLngFromDegrees(65.609727, -168.093694)
53+
length := earth.LengthFromLatLngs(chukchi, seward)
54+
fmt.Printf("Bering Strait is %.0f feet", length.Feet())
55+
// Output: Bering Strait is 283979 feet
56+
}
57+
58+
func ExampleAreaFromSteradians() {
59+
bermuda := s2.PointFromLatLng(s2.LatLngFromDegrees(32.361457, -64.663495))
60+
culebra := s2.PointFromLatLng(s2.LatLngFromDegrees(18.311199, -65.300765))
61+
miami := s2.PointFromLatLng(s2.LatLngFromDegrees(25.802018, -80.269892))
62+
triangle := s2.PolygonFromLoops(
63+
[]*s2.Loop{s2.LoopFromPoints([]s2.Point{bermuda, miami, culebra})})
64+
area := earth.AreaFromSteradians(triangle.Area())
65+
fmt.Printf("Bermuda Triangle is %.2f square miles", area.SquareMiles())
66+
// Output: Bermuda Triangle is 464541.15 square miles
67+
}
68+
69+
func ExampleSteradiansFromArea() {
70+
steradians := earth.SteradiansFromArea(unit.SquareCentimeter)
71+
fmt.Printf("1 square centimeter is %g steradians, close to a level %d cell",
72+
steradians, s2.AvgAreaMetric.ClosestLevel(steradians))
73+
// Output: 1 square centimeter is 2.4636750563592804e-18 steradians, close to a level 30 cell
74+
}
75+
76+
func ExampleInitialBearingFromLatLngs() {
77+
christchurch := s2.LatLngFromDegrees(-43.491402, 172.522275)
78+
riogrande := s2.LatLngFromDegrees(-53.777156, -67.734719)
79+
bearing := earth.InitialBearingFromLatLngs(christchurch, riogrande)
80+
fmt.Printf("Head southeast (%.2f°)", bearing.Degrees())
81+
// Output: Head southeast (146.90°)
82+
}

0 commit comments

Comments
 (0)