mirror of
https://github.com/tailscale/tailscale.git
synced 2025-07-30 07:43:42 +00:00

Package geo provides functionality to represent and process geographical locations on a sphere. The main type, geo.Point, represents a pair of latitude and longitude coordinates. Updates tailscale/corp#29968 Signed-off-by: Simon Law <sfllaw@tailscale.com>
192 lines
4.9 KiB
Go
192 lines
4.9 KiB
Go
// Copyright (c) Tailscale Inc & AUTHORS
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
package geo
|
|
|
|
import (
|
|
"math"
|
|
"strconv"
|
|
"strings"
|
|
"unicode"
|
|
)
|
|
|
|
const (
|
|
Degree Degrees = 1
|
|
Radian Radians = 1
|
|
Turn Turns = 1
|
|
Meter Distance = 1
|
|
)
|
|
|
|
// Degrees represents a latitude or longitude, in decimal degrees.
|
|
type Degrees float64
|
|
|
|
// ParseDegrees parses s as decimal degrees.
|
|
func ParseDegrees(s string) (Degrees, error) {
|
|
s = strings.TrimSuffix(s, "°")
|
|
f, err := strconv.ParseFloat(s, 64)
|
|
return Degrees(f), err
|
|
}
|
|
|
|
// MustParseDegrees parses s as decimal degrees, but panics on error.
|
|
func MustParseDegrees(s string) Degrees {
|
|
d, err := ParseDegrees(s)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return d
|
|
}
|
|
|
|
// String implements the [Stringer] interface. The output is formatted in
|
|
// decimal degrees, prefixed by either the appropriate + or - sign, and suffixed
|
|
// by a ° degree symbol.
|
|
func (d Degrees) String() string {
|
|
b, _ := d.AppendText(nil)
|
|
b = append(b, []byte("°")...)
|
|
return string(b)
|
|
}
|
|
|
|
// AppendText implements [encoding.TextAppender]. The output is formatted in
|
|
// decimal degrees, prefixed by either the appropriate + or - sign.
|
|
func (d Degrees) AppendText(b []byte) ([]byte, error) {
|
|
b = d.AppendZeroPaddedText(b, 0)
|
|
return b, nil
|
|
}
|
|
|
|
// AppendZeroPaddedText appends d formatted as decimal degrees to b. The number of
|
|
// integer digits will be zero-padded to nint.
|
|
func (d Degrees) AppendZeroPaddedText(b []byte, nint int) []byte {
|
|
n := float64(d)
|
|
|
|
if math.IsInf(n, 0) || math.IsNaN(n) {
|
|
return strconv.AppendFloat(b, n, 'f', -1, 64)
|
|
}
|
|
|
|
sign := byte('+')
|
|
if math.Signbit(n) {
|
|
sign = '-'
|
|
n = -n
|
|
}
|
|
b = append(b, sign)
|
|
|
|
pad := nint - 1
|
|
for nn := n / 10; nn >= 1 && pad > 0; nn /= 10 {
|
|
pad--
|
|
}
|
|
for range pad {
|
|
b = append(b, '0')
|
|
}
|
|
return strconv.AppendFloat(b, n, 'f', -1, 64)
|
|
}
|
|
|
|
// Radians converts d into radians.
|
|
func (d Degrees) Radians() Radians {
|
|
return Radians(d * math.Pi / 180.0)
|
|
}
|
|
|
|
// Turns converts d into a number of turns.
|
|
func (d Degrees) Turns() Turns {
|
|
return Turns(d / 360.0)
|
|
}
|
|
|
|
// Radians represents a latitude or longitude, in radians.
|
|
type Radians float64
|
|
|
|
// ParseRadians parses s as radians.
|
|
func ParseRadians(s string) (Radians, error) {
|
|
s = strings.TrimSuffix(s, "rad")
|
|
s = strings.TrimRightFunc(s, unicode.IsSpace)
|
|
f, err := strconv.ParseFloat(s, 64)
|
|
return Radians(f), err
|
|
}
|
|
|
|
// MustParseRadians parses s as radians, but panics on error.
|
|
func MustParseRadians(s string) Radians {
|
|
r, err := ParseRadians(s)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return r
|
|
}
|
|
|
|
// String implements the [Stringer] interface.
|
|
func (r Radians) String() string {
|
|
return strconv.FormatFloat(float64(r), 'f', -1, 64) + " rad"
|
|
}
|
|
|
|
// Degrees converts r into decimal degrees.
|
|
func (r Radians) Degrees() Degrees {
|
|
return Degrees(r * 180.0 / math.Pi)
|
|
}
|
|
|
|
// Turns converts r into a number of turns.
|
|
func (r Radians) Turns() Turns {
|
|
return Turns(r / 2 / math.Pi)
|
|
}
|
|
|
|
// Turns represents a number of complete revolutions around a sphere.
|
|
type Turns float64
|
|
|
|
// String implements the [Stringer] interface.
|
|
func (o Turns) String() string {
|
|
return strconv.FormatFloat(float64(o), 'f', -1, 64)
|
|
}
|
|
|
|
// Degrees converts t into decimal degrees.
|
|
func (o Turns) Degrees() Degrees {
|
|
return Degrees(o * 360.0)
|
|
}
|
|
|
|
// Radians converts t into radians.
|
|
func (o Turns) Radians() Radians {
|
|
return Radians(o * 2 * math.Pi)
|
|
}
|
|
|
|
// Distance represents a great-circle distance in meters.
|
|
type Distance float64
|
|
|
|
// ParseDistance parses s as distance in meters.
|
|
func ParseDistance(s string) (Distance, error) {
|
|
s = strings.TrimSuffix(s, "m")
|
|
s = strings.TrimRightFunc(s, unicode.IsSpace)
|
|
f, err := strconv.ParseFloat(s, 64)
|
|
return Distance(f), err
|
|
}
|
|
|
|
// MustParseDistance parses s as distance in meters, but panics on error.
|
|
func MustParseDistance(s string) Distance {
|
|
d, err := ParseDistance(s)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return d
|
|
}
|
|
|
|
// String implements the [Stringer] interface.
|
|
func (d Distance) String() string {
|
|
return strconv.FormatFloat(float64(d), 'f', -1, 64) + "m"
|
|
}
|
|
|
|
// DistanceOnEarth converts t turns into the great-circle distance, in meters.
|
|
func DistanceOnEarth(t Turns) Distance {
|
|
return Distance(t) * EarthMeanCircumference
|
|
}
|
|
|
|
// Earth Fact Sheet
|
|
// https://nssdc.gsfc.nasa.gov/planetary/factsheet/earthfact.html
|
|
const (
|
|
// EarthMeanRadius is the volumetric mean radius of the Earth.
|
|
EarthMeanRadius = 6_371_000 * Meter
|
|
// EarthMeanCircumference is the volumetric mean circumference of the Earth.
|
|
EarthMeanCircumference = 2 * math.Pi * EarthMeanRadius
|
|
|
|
// earthEquatorialRadius is the equatorial radius of the Earth.
|
|
earthEquatorialRadius = 6_378_137 * Meter
|
|
// earthEquatorialCircumference is the equatorial circumference of the Earth.
|
|
earthEquatorialCircumference = 2 * math.Pi * earthEquatorialRadius
|
|
|
|
// earthPolarRadius is the polar radius of the Earth.
|
|
earthPolarRadius = 6_356_752 * Meter
|
|
// earthPolarCircumference is the polar circumference of the Earth.
|
|
earthPolarCircumference = 2 * math.Pi * earthPolarRadius
|
|
)
|