mirror of https://github.com/tidwall/tile38.git
161 lines
3.2 KiB
Go
161 lines
3.2 KiB
Go
|
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
|
|||
|
}
|