149 lines
3.9 KiB
Go
149 lines
3.9 KiB
Go
// Package maidenhead implements the Maidenhead Locator System, a geographic
|
|
// coordinate system used by amataur radio (HAM) operators.
|
|
package maidenhead
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"math"
|
|
"strings"
|
|
)
|
|
|
|
// Precision of the computed locator.
|
|
const (
|
|
FieldPrecision = iota + 1
|
|
SquarePrecision
|
|
SubSquarePrecision
|
|
ExtendedSquarePrecision
|
|
)
|
|
|
|
var (
|
|
upper = "ABCDEFGHIJKLMNOPQRSTUVWX"
|
|
lower = "abcdefghijklmnopqrstuvwx"
|
|
digit = "0123456789"
|
|
)
|
|
|
|
// locator computes the Maidenhead Locator for a given position.
|
|
func locator(p Point, precision int) (string, error) {
|
|
if math.IsNaN(p.Latitude) {
|
|
return "", errors.New("maidenhead: latitude is not a digit")
|
|
}
|
|
if math.IsInf(p.Latitude, 0) {
|
|
return "", errors.New("maidenhead: latitude is infinite")
|
|
}
|
|
if math.IsNaN(p.Longitude) {
|
|
return "", errors.New("maidenhead: longitude is not a digit")
|
|
}
|
|
if math.IsInf(p.Longitude, 0) {
|
|
return "", errors.New("maidenhead: longitude is infinite")
|
|
}
|
|
if math.Abs(p.Latitude) == 90.0 {
|
|
return "", errors.New("maidenhead: grid square invalid at poles")
|
|
} else if math.Abs(p.Latitude) > 90.0 {
|
|
return "", fmt.Errorf("maidenhead: invalid latitude %.04f", p.Latitude)
|
|
} else if math.Abs(p.Longitude) > 180.0 {
|
|
return "", fmt.Errorf("maidenhead: invalid longitude %.05f", p.Longitude)
|
|
}
|
|
|
|
var (
|
|
lat = p.Latitude + 90.0
|
|
lng = p.Longitude + 180.0
|
|
loc string
|
|
)
|
|
|
|
lat = lat/10.0 + 0.0000001
|
|
lng = lng/20.0 + 0.0000001
|
|
loc = loc + string(upper[int(lng)]) + string(upper[int(lat)])
|
|
if precision == 1 {
|
|
return loc, nil
|
|
}
|
|
lat = 10 * (lat - math.Floor(lat))
|
|
lng = 10 * (lng - math.Floor(lng))
|
|
loc = loc + fmt.Sprintf("%d%d", int(lng)%10, int(lat)%10)
|
|
if precision == 2 {
|
|
return loc, nil
|
|
}
|
|
lat = 24 * (lat - math.Floor(lat))
|
|
lng = 24 * (lng - math.Floor(lng))
|
|
loc = loc + string(upper[int(lng)]) + string(upper[int(lat)])
|
|
if precision == 3 {
|
|
return loc, nil
|
|
}
|
|
lat = 10 * (lat - math.Floor(lat))
|
|
lng = 10 * (lng - math.Floor(lng))
|
|
loc = loc + fmt.Sprintf("%d%d", int(lng)%10, int(lat)%10)
|
|
if precision == 4 {
|
|
return loc, nil
|
|
}
|
|
lat = 24 * (lat - math.Floor(lat))
|
|
lng = 24 * (lng - math.Floor(lng))
|
|
loc = loc + string(lower[int(lng)]) + string(lower[int(lat)])
|
|
return loc, nil
|
|
}
|
|
|
|
var parseLocatorMult = []struct {
|
|
s, p string
|
|
mult float64
|
|
}{
|
|
{upper[:18], lower[:18], 20.0},
|
|
{upper[:18], lower[:18], 10.0},
|
|
{digit[:10], digit[:10], 20.0 / 10.0},
|
|
{digit[:10], digit[:10], 10.0 / 10.0},
|
|
{upper[:24], lower[:24], 20.0 / (10.0 * 24.0)},
|
|
{upper[:24], lower[:24], 10.0 / (10.0 * 24.0)},
|
|
{digit[:10], digit[:10], 20.0 / (10.0 * 24.0 * 10.0)},
|
|
{digit[:10], digit[:10], 10.0 / (10.0 * 24.0 * 10.0)},
|
|
{lower[:24], lower[:24], 20.0 / (10.0 * 24.0 * 10.0 * 24.0)},
|
|
{lower[:24], lower[:24], 10.0 / (10.0 * 24.0 * 10.0 * 24.0)},
|
|
}
|
|
|
|
var maxLocatorLength = len(parseLocatorMult)
|
|
|
|
func parseLocator(locator string, strict bool, centered bool) (point Point, err error) {
|
|
var (
|
|
lnglat = [2]float64{
|
|
-180.0,
|
|
-90.0,
|
|
}
|
|
i, j int
|
|
char rune
|
|
)
|
|
|
|
if len(locator) > maxLocatorLength {
|
|
err = fmt.Errorf("maidenhead: locator is too long (%d characters, maximum %d characters allowed)",
|
|
len(locator), maxLocatorLength)
|
|
return
|
|
}
|
|
|
|
if len(locator)%2 != 0 {
|
|
err = fmt.Errorf("maidenhead: locator has odd number of characters")
|
|
return
|
|
}
|
|
|
|
if strict {
|
|
for i, char = range locator {
|
|
if j = strings.Index(parseLocatorMult[i].s, string(char)); j < 0 {
|
|
err = fmt.Errorf("maidenhead: invalid character at offset %d", i)
|
|
return
|
|
}
|
|
lnglat[i%2] += float64(j) * parseLocatorMult[i].mult
|
|
}
|
|
} else {
|
|
for i, char = range strings.ToLower(locator) {
|
|
if j = strings.Index(parseLocatorMult[i].p, string(char)); j < 0 {
|
|
err = fmt.Errorf("maidenhead: invalid character at offset %d", i)
|
|
return
|
|
}
|
|
lnglat[i%2] += float64(j) * parseLocatorMult[i].mult
|
|
}
|
|
}
|
|
|
|
if centered {
|
|
lnglat[0] += parseLocatorMult[i-1].mult / 2.0
|
|
lnglat[1] += parseLocatorMult[i].mult / 2.0
|
|
}
|
|
|
|
point = NewPoint(lnglat[1], lnglat[0])
|
|
return
|
|
}
|