tile38/vendor/github.com/iwpnd/sectr/sectr.go

161 lines
3.2 KiB
Go
Raw Normal View History

2021-09-05 12:48:34 +03:00
package sectr
import (
"encoding/json"
"math"
)
const earthRadius = 6371008.8 // earth radius
// Point ...
type Point struct {
Lng, Lat float64
}
// Sector ...
type Sector struct {
coordinates [][][]float64
origin Point
radius float64
bearing1 float64
bearing2 float64
}
// SectorGeometry ...
type SectorGeometry struct {
Type string `json:"type"`
Coordinates [][][]float64 `json:"coordinates"`
}
func radToDegree(rad float64) float64 {
return rad * 180 / math.Pi
}
func degreeToRad(degree float64) float64 {
return degree * math.Pi / 180
}
func distanceToRadians(distance float64) float64 {
const r = earthRadius
return distance / r
}
// terminal calculates the terminal position travelling a distance
// from a given origin
// see https://www.movable-type.co.uk/scripts/latlong.html
func terminal(start Point, distance, bearing float64) Point {
φ1 := degreeToRad(start.Lat)
λ1 := degreeToRad(start.Lng)
bearingRad := degreeToRad(bearing)
distanceRad := distanceToRadians(distance)
φ2 := math.Asin(
math.Sin(φ1)*
math.Cos(distanceRad) +
math.Cos(φ1)*
math.Sin(distanceRad)*
math.Cos(bearingRad))
λ2 := λ1 + math.Atan2(
math.Sin(bearingRad)*
math.Sin(distanceRad)*
math.Cos(φ1),
math.Cos(distanceRad)-
math.Sin(φ1)*
math.Sin(φ2))
// cap decimals at .00000001 degree ~= 1.11mm
lng := math.Round(radToDegree(λ2)*100000000) / 100000000
lat := math.Round(radToDegree(φ2)*100000000) / 100000000
return Point{Lng: lng, Lat: lat}
}
func bearingToAngle(bearing float64) float64 {
angle := math.Mod(bearing, 360)
if angle < 0 {
angle = angle + 360
}
return angle
}
// NewSector creates a sector from a given origin point, a radius and two bearings
func NewSector(origin Point, radius, bearing1, bearing2 float64) *Sector {
// to cap the maximum positions in a sector/circle to 64
// the higher the smoother, yet the bigger the coordinate array
const steps = 64
s := &Sector{
origin: origin,
bearing1: bearing1,
bearing2: bearing2,
radius: radius,
}
angle1 := bearingToAngle(bearing1)
angle2 := bearingToAngle(bearing2)
// if angle1 == angle2 return circle
if angle1 == angle2 {
for i := 1; i < steps; i++ {
α := float64(i * -360 / steps)
t := terminal(origin, radius, α)
s.addPoint(t)
}
s.coordinates[0] = append(s.coordinates[0], s.coordinates[0][0])
return s
}
var endDegree float64
startDegree := angle1
if angle1 < angle2 {
endDegree = angle2
} else {
endDegree = angle2 + 360
}
α := startDegree
s.addPoint(origin)
for i := 1; ; i++ {
if α < endDegree {
t := terminal(origin, radius, α)
s.addPoint(t)
α = startDegree + float64((i*360)/steps)
}
if α >= endDegree {
t := terminal(origin, radius, endDegree)
s.addPoint(t)
s.addPoint(origin)
return s
}
}
}
func (s *Sector) addPoint(p Point) {
if len(s.coordinates) == 0 {
s.coordinates = append(s.coordinates, [][]float64{{p.Lng, p.Lat}})
return
}
s.coordinates[0] = append(s.coordinates[0], []float64{p.Lng, p.Lat})
}
// JSON exports the Sector as json
func (s Sector) JSON() []byte {
f := SectorGeometry{Type: "Polygon", Coordinates: s.coordinates}
j, _ := json.Marshal(f)
return j
}