2018-10-11 00:25:40 +03:00
|
|
|
// Copyright 2018 Joshua J Baker. All rights reserved.
|
|
|
|
// Use of this source code is governed by an MIT-style
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
|
|
|
package geo
|
|
|
|
|
2018-10-27 19:23:29 +03:00
|
|
|
import (
|
|
|
|
"math"
|
|
|
|
"math/rand"
|
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
seed := time.Now().UnixNano()
|
|
|
|
//seed = 1540656736244531000
|
|
|
|
println(seed)
|
|
|
|
rand.Seed(seed)
|
|
|
|
}
|
2018-10-11 00:25:40 +03:00
|
|
|
|
|
|
|
func TestGeoCalc(t *testing.T) {
|
|
|
|
dist := 172853.26908429610193707048892974853515625
|
|
|
|
bearing := 320.8560640269032546711969189345836639404296875
|
|
|
|
latA, lonA := 33.112, -112.123
|
|
|
|
latB, lonB := 34.312, -113.311
|
|
|
|
// DistanceTo
|
|
|
|
value := DistanceTo(latA, lonA, latB, lonB)
|
|
|
|
if value != dist {
|
|
|
|
t.Fatalf("expected '%v', got '%v'", dist, value)
|
|
|
|
}
|
|
|
|
// BearingTo
|
|
|
|
value = BearingTo(latA, lonA, latB, lonB)
|
|
|
|
if value != bearing {
|
|
|
|
t.Fatalf("expected '%v', got '%v'", bearing, value)
|
|
|
|
}
|
|
|
|
// DestinationPoint
|
|
|
|
value1, value2 := DestinationPoint(latA, lonA, dist, bearing)
|
|
|
|
if value1 != latB {
|
|
|
|
t.Fatalf("expected '%v', got '%v'", latB, value1)
|
|
|
|
}
|
|
|
|
if value2 != lonB {
|
|
|
|
t.Fatalf("expected '%v', got '%v'", lonB, value2)
|
|
|
|
}
|
2019-06-28 20:01:12 +03:00
|
|
|
// RectFromCenter
|
|
|
|
// expected mins and maxes for wraparound and pole tests
|
|
|
|
expMinLat := -90.0
|
|
|
|
expMinLon := -180.0
|
|
|
|
expMaxLat := 90.0
|
|
|
|
expMaxLon := 180.0
|
|
|
|
|
|
|
|
dist1 := 1600000.0
|
|
|
|
|
|
|
|
wraparoundTests := []struct {
|
|
|
|
name string
|
|
|
|
lat, lon, searchRadius float64
|
|
|
|
}{
|
|
|
|
{name: "Wraparound E", lat: 0.0, lon: 179.0, searchRadius: dist1}, // at equator near 180th meridian East
|
|
|
|
{name: "Wraparound W", lat: 0.0, lon: -179.0, searchRadius: dist1}, // at equator near 180th meridian West
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tt := range wraparoundTests {
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
_, minLon, _, maxLon := RectFromCenter(tt.lat, tt.lon, tt.searchRadius)
|
|
|
|
if !(minLon == expMinLon && maxLon == expMaxLon) {
|
|
|
|
t.Errorf("\nexpected minLon = '%v', maxLon = '%v'"+"\ngot minLon = '%v', maxLon = '%v'\n",
|
|
|
|
expMinLon, expMaxLon,
|
|
|
|
minLon, maxLon)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
northPoleTests := []struct {
|
|
|
|
name string
|
|
|
|
lat, lon, searchRadius float64
|
|
|
|
}{
|
|
|
|
{name: "North Pole", lat: 89.0, lon: 90.0, searchRadius: dist1}, // near North Pole
|
|
|
|
{name: "North Pole: Tile38 iss422", lat: 13.0257553, lon: 77.6672509, searchRadius: 9000000.0},
|
|
|
|
}
|
|
|
|
for _, tt := range northPoleTests {
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
_, minLon, maxLat, maxLon := RectFromCenter(tt.lat, tt.lon, tt.searchRadius)
|
|
|
|
if !(minLon == expMinLon && maxLat == expMaxLat && maxLon == expMaxLon) {
|
|
|
|
t.Errorf("\nexpected minLon = '%v', maxLat = '%v', maxLon = '%v'"+"\ngot minLon = '%v', maxLat = '%v', maxLon = '%v'",
|
|
|
|
expMinLon, expMaxLat, expMaxLon,
|
|
|
|
minLon, maxLat, maxLon)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
southPoleTests := []struct {
|
|
|
|
name string
|
|
|
|
lat, lon, searchRadius float64
|
|
|
|
}{
|
|
|
|
{name: "South Pole", lat: -89.0, lon: 90.0, searchRadius: dist1}, // near South Pole
|
|
|
|
}
|
|
|
|
for _, tt := range southPoleTests {
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
minLat, minLon, _, maxLon := RectFromCenter(tt.lat, tt.lon, tt.searchRadius)
|
|
|
|
if !(minLat == expMinLat && minLon == expMinLon && maxLon == expMaxLon) {
|
|
|
|
t.Errorf("\nexpected minLat = '%v', minLon = '%v', maxLon = '%v'"+"\ngot minLat = '%v', minLon = '%v', maxLon = '%v'",
|
|
|
|
expMinLat, expMinLon, expMaxLon,
|
|
|
|
minLat, minLon, maxLon)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
2018-10-11 00:25:40 +03:00
|
|
|
}
|
2018-10-27 19:23:29 +03:00
|
|
|
|
|
|
|
func TestHaversine(t *testing.T) {
|
|
|
|
latA := rand.Float64()*180 - 90
|
|
|
|
lonA := rand.Float64()*360 - 180
|
|
|
|
start := time.Now()
|
|
|
|
for time.Since(start) < time.Second/4 {
|
|
|
|
for i := 0; i < 1000; i++ {
|
|
|
|
latB := rand.Float64()*180 - 90
|
|
|
|
lonB := rand.Float64()*360 - 180
|
|
|
|
latC := rand.Float64()*180 - 90
|
|
|
|
lonC := rand.Float64()*360 - 180
|
|
|
|
haver1 := Haversine(latA, lonA, latB, lonB)
|
|
|
|
haver2 := Haversine(latA, lonA, latC, lonC)
|
|
|
|
meters1 := DistanceTo(latA, lonA, latB, lonB)
|
|
|
|
meters2 := DistanceTo(latA, lonA, latC, lonC)
|
|
|
|
switch {
|
|
|
|
case haver1 < haver2:
|
|
|
|
if meters1 >= meters2 {
|
|
|
|
t.Fatalf("failed")
|
|
|
|
}
|
|
|
|
case haver1 == haver2:
|
|
|
|
if meters1 != meters2 {
|
|
|
|
t.Fatalf("failed")
|
|
|
|
}
|
|
|
|
case haver1 > haver2:
|
|
|
|
if meters1 <= meters2 {
|
|
|
|
t.Fatalf("failed")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestNormalizeDistance(t *testing.T) {
|
|
|
|
start := time.Now()
|
2018-11-01 23:49:39 +03:00
|
|
|
for time.Since(start) < time.Second {
|
2018-10-27 19:23:29 +03:00
|
|
|
for i := 0; i < 1000; i++ {
|
2018-11-01 23:49:39 +03:00
|
|
|
meters1 := rand.Float64() * earthRadius * 3 // wrap three times
|
2018-10-27 19:23:29 +03:00
|
|
|
meters2 := NormalizeDistance(meters1)
|
2018-11-01 23:49:39 +03:00
|
|
|
dist1 := math.Floor(DistanceToHaversine(meters2) * 1e8)
|
|
|
|
dist2 := math.Floor(DistanceToHaversine(meters1) * 1e8)
|
2018-10-27 19:23:29 +03:00
|
|
|
if dist1 != dist2 {
|
|
|
|
t.Fatalf("expected %f, got %f", dist2, dist1)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-11-01 23:49:39 +03:00
|
|
|
|
|
|
|
type point struct {
|
|
|
|
lat, lon float64
|
|
|
|
}
|
|
|
|
|
|
|
|
func BenchmarkHaversine(b *testing.B) {
|
|
|
|
pointA := point{
|
|
|
|
lat: rand.Float64()*180 - 90,
|
|
|
|
lon: rand.Float64()*360 - 180,
|
|
|
|
}
|
|
|
|
points := make([]point, b.N)
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
|
|
points[i].lat = rand.Float64()*180 - 90
|
|
|
|
points[i].lon = rand.Float64()*360 - 180
|
|
|
|
}
|
|
|
|
b.ResetTimer()
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
|
|
Haversine(pointA.lat, pointA.lon, points[i].lat, points[i].lon)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func BenchmarkDistanceTo(b *testing.B) {
|
|
|
|
pointA := point{
|
|
|
|
lat: rand.Float64()*180 - 90,
|
|
|
|
lon: rand.Float64()*360 - 180,
|
|
|
|
}
|
|
|
|
points := make([]point, b.N)
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
|
|
points[i].lat = rand.Float64()*180 - 90
|
|
|
|
points[i].lon = rand.Float64()*360 - 180
|
|
|
|
}
|
|
|
|
b.ResetTimer()
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
|
|
DistanceTo(pointA.lat, pointA.lon, points[i].lat, points[i].lon)
|
|
|
|
}
|
|
|
|
}
|