mirror of
https://github.com/tailscale/tailscale.git
synced 2025-08-22 02:50:42 +00:00
types/geo: add geo.Point and its associated units (#16583)
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>
This commit is contained in:
191
types/geo/units.go
Normal file
191
types/geo/units.go
Normal file
@@ -0,0 +1,191 @@
|
||||
// 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
|
||||
)
|
Reference in New Issue
Block a user