163 lines
4.1 KiB
Go
163 lines
4.1 KiB
Go
package maidenhead
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
)
|
|
|
|
const (
|
|
// Earth radius
|
|
r = 6371
|
|
)
|
|
|
|
var compassBearing = []struct {
|
|
label string
|
|
start, ended float64
|
|
}{
|
|
{"N", 000.00, 011.25}, {"NNE", 011.25, 033.75}, {"NE", 033.75, 056.25}, {"ENE", 056.25, 078.75},
|
|
{"E", 078.75, 101.25}, {"ESE", 101.25, 123.75}, {"SE", 123.75, 146.25}, {"SSE", 146.25, 168.75},
|
|
{"S", 168.75, 191.25}, {"SSW", 191.25, 213.75}, {"SW", 213.75, 236.25}, {"WSW", 236.25, 258.75},
|
|
{"W", 258.75, 281.25}, {"WNW", 281.25, 303.75}, {"NW", 303.75, 326.25}, {"NNW", 326.25, 348.75},
|
|
{"N", 348.75, 360.00},
|
|
}
|
|
|
|
// Point is a geographical point on the map.
|
|
type Point struct {
|
|
Latitude float64
|
|
Longitude float64
|
|
}
|
|
|
|
// NewPoint returns a new Point structure with given latitude and longitude.
|
|
func NewPoint(latitude, longitude float64) Point {
|
|
return Point{latitude, longitude}
|
|
}
|
|
|
|
// ParseLocator parses a Maidenhead Locator with permissive rule matching.
|
|
func ParseLocator(locator string) (Point, error) {
|
|
return parseLocator(locator, false, false)
|
|
}
|
|
|
|
// ParseLocatorStrict parses a Maidenhead Locator with strict rule matching.
|
|
func ParseLocatorStrict(locator string) (Point, error) {
|
|
return parseLocator(locator, true, false)
|
|
}
|
|
|
|
// ParseLocatorCentered parses a Maidenhead Locator with permissive rule matching.
|
|
// Returns Points structure with coordinates of the square center
|
|
func ParseLocatorCentered(locator string) (Point, error) {
|
|
return parseLocator(locator, false, true)
|
|
}
|
|
|
|
// ParseLocatorStrictCentered parses a Maidenhead Locator with strict rule matching.
|
|
// Returns Points structure with coordinates of the square center
|
|
func ParseLocatorStrictCentered(locator string) (Point, error) {
|
|
return parseLocator(locator, true, true)
|
|
}
|
|
|
|
// EqualTo returns true if the coordinates point to the same geographical location.
|
|
func (p Point) EqualTo(other Point) bool {
|
|
var (
|
|
dlat = p.Latitude - other.Latitude
|
|
dlng = p.Longitude - other.Longitude
|
|
)
|
|
|
|
for dlat < -180.0 {
|
|
dlat += 360.0
|
|
}
|
|
for dlat > 180.0 {
|
|
dlat -= 360.0
|
|
}
|
|
for dlng < -90.0 {
|
|
dlng += 90.0
|
|
}
|
|
for dlng > 90.0 {
|
|
dlng -= 90.0
|
|
}
|
|
|
|
return dlat == 0.0 && dlng == 0.0
|
|
}
|
|
|
|
// Bearing calculates the (approximate) bearing to another heading.
|
|
func (p Point) Bearing(heading Point) float64 {
|
|
var (
|
|
hn = p.Latitude / 180 * math.Pi
|
|
he = p.Longitude / 180 * math.Pi
|
|
n = heading.Latitude / 180 * math.Pi
|
|
e = heading.Longitude / 180 * math.Pi
|
|
co = math.Cos(he-e)*math.Cos(hn)*math.Cos(n) + math.Sin(hn)*math.Sin(n)
|
|
ca = math.Atan(math.Abs(math.Sqrt(1-co*co) / co))
|
|
)
|
|
|
|
if co < 0.0 {
|
|
ca = math.Pi - ca
|
|
}
|
|
|
|
var si = math.Sin(e-he) * math.Cos(n) * math.Cos(hn)
|
|
co = math.Sin(n) - math.Sin(hn)*math.Cos(ca)
|
|
var az = math.Atan(math.Abs(si / co))
|
|
if co < 0.0 {
|
|
az = math.Pi - az
|
|
}
|
|
if si < 0.0 {
|
|
az = -az
|
|
}
|
|
if az < 0.0 {
|
|
az = az + 2.0*math.Pi
|
|
}
|
|
return az * 180 / math.Pi
|
|
}
|
|
|
|
// CompassBearing returns the compass bearing to a heading.
|
|
func (p Point) CompassBearing(heading Point) string {
|
|
bearing := p.Bearing(heading)
|
|
for bearing < 0.0 {
|
|
bearing += 360.0
|
|
}
|
|
for bearing > 360.0 {
|
|
bearing -= 360.0
|
|
}
|
|
|
|
for _, compass := range compassBearing {
|
|
if bearing >= compass.start && bearing <= compass.ended {
|
|
return compass.label
|
|
}
|
|
}
|
|
|
|
// Should never reach
|
|
return ""
|
|
}
|
|
|
|
// Distance calculates the (approximate) distance to another point in km.
|
|
func (p Point) Distance(other Point) float64 {
|
|
var (
|
|
hn = p.Latitude / 180 * math.Pi
|
|
he = p.Longitude / 180 * math.Pi
|
|
n = other.Latitude / 180 * math.Pi
|
|
e = other.Longitude / 180 * math.Pi
|
|
co = math.Cos(he-e)*math.Cos(hn)*math.Cos(n) + math.Sin(hn)*math.Sin(n)
|
|
ca = math.Atan(math.Abs(math.Sqrt(1-co*co) / co))
|
|
)
|
|
|
|
if co < 0.0 {
|
|
ca = math.Pi - ca
|
|
}
|
|
|
|
return r * ca
|
|
}
|
|
|
|
// GridSquare returns a Maidenhead Locator for the point coordinates.
|
|
func (p Point) GridSquare() (string, error) {
|
|
return locator(p, SubSquarePrecision)
|
|
}
|
|
|
|
// Locator returns a Maidenhead Locator for the point coordinates with
|
|
// specified precision
|
|
func (p Point) Locator(precision int) (string, error) {
|
|
return locator(p, precision)
|
|
}
|
|
|
|
// String returns a stringified Point structure.
|
|
func (p Point) String() string {
|
|
return fmt.Sprintf("Point(%f, %f)", p.Latitude, p.Longitude)
|
|
}
|