2021-07-11 20:09:51 +03:00
|
|
|
|
package collection
|
|
|
|
|
|
2022-09-21 00:20:53 +03:00
|
|
|
|
import (
|
|
|
|
|
"math"
|
2021-07-11 20:09:51 +03:00
|
|
|
|
|
2022-09-21 00:20:53 +03:00
|
|
|
|
"github.com/tidwall/tile38/internal/object"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func geodeticDistAlgo(center [2]float64) (
|
|
|
|
|
algo func(min, max [2]float64, obj *object.Object, item bool) (dist float64),
|
2021-07-11 20:09:51 +03:00
|
|
|
|
) {
|
|
|
|
|
const earthRadius = 6371e3
|
2022-09-21 00:20:53 +03:00
|
|
|
|
return func(min, max [2]float64, obj *object.Object, item bool) (dist float64) {
|
|
|
|
|
if item {
|
|
|
|
|
r := obj.Rect()
|
|
|
|
|
min[0] = r.Min.X
|
|
|
|
|
min[1] = r.Min.Y
|
|
|
|
|
max[0] = r.Max.X
|
|
|
|
|
max[1] = r.Max.Y
|
|
|
|
|
}
|
2021-07-11 20:09:51 +03:00
|
|
|
|
return earthRadius * pointRectDistGeodeticDeg(
|
|
|
|
|
center[1], center[0],
|
|
|
|
|
min[1], min[0],
|
|
|
|
|
max[1], max[0],
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func pointRectDistGeodeticDeg(pLat, pLng, minLat, minLng, maxLat, maxLng float64) float64 {
|
|
|
|
|
result := pointRectDistGeodeticRad(
|
|
|
|
|
pLat*math.Pi/180, pLng*math.Pi/180,
|
|
|
|
|
minLat*math.Pi/180, minLng*math.Pi/180,
|
|
|
|
|
maxLat*math.Pi/180, maxLng*math.Pi/180,
|
|
|
|
|
)
|
|
|
|
|
return result
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func pointRectDistGeodeticRad(φq, λq, φl, λl, φh, λh float64) float64 {
|
|
|
|
|
// Algorithm from:
|
|
|
|
|
// Schubert, E., Zimek, A., & Kriegel, H.-P. (2013).
|
|
|
|
|
// Geodetic Distance Queries on R-Trees for Indexing Geographic Data.
|
|
|
|
|
// Lecture Notes in Computer Science, 146–164.
|
|
|
|
|
// doi:10.1007/978-3-642-40235-7_9
|
|
|
|
|
const (
|
|
|
|
|
twoΠ = 2 * math.Pi
|
|
|
|
|
halfΠ = math.Pi / 2
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// distance on the unit sphere computed using Haversine formula
|
|
|
|
|
distRad := func(φa, λa, φb, λb float64) float64 {
|
|
|
|
|
if φa == φb && λa == λb {
|
|
|
|
|
return 0
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Δφ := φa - φb
|
|
|
|
|
Δλ := λa - λb
|
|
|
|
|
sinΔφ := math.Sin(Δφ / 2)
|
|
|
|
|
sinΔλ := math.Sin(Δλ / 2)
|
|
|
|
|
cosφa := math.Cos(φa)
|
|
|
|
|
cosφb := math.Cos(φb)
|
|
|
|
|
|
|
|
|
|
return 2 * math.Asin(math.Sqrt(sinΔφ*sinΔφ+sinΔλ*sinΔλ*cosφa*cosφb))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Simple case, point or invalid rect
|
|
|
|
|
if φl >= φh && λl >= λh {
|
|
|
|
|
return distRad(φl, λl, φq, λq)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if λl <= λq && λq <= λh {
|
|
|
|
|
// q is between the bounding meridians of r
|
|
|
|
|
// hence, q is north, south or within r
|
|
|
|
|
if φl <= φq && φq <= φh { // Inside
|
|
|
|
|
return 0
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if φq < φl { // South
|
|
|
|
|
return φl - φq
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return φq - φh // North
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// determine if q is closer to the east or west edge of r to select edge for
|
|
|
|
|
// tests below
|
|
|
|
|
Δλe := λl - λq
|
|
|
|
|
Δλw := λq - λh
|
|
|
|
|
if Δλe < 0 {
|
|
|
|
|
Δλe += twoΠ
|
|
|
|
|
}
|
|
|
|
|
if Δλw < 0 {
|
|
|
|
|
Δλw += twoΠ
|
|
|
|
|
}
|
|
|
|
|
var Δλ float64 // distance to closest edge
|
|
|
|
|
var λedge float64 // longitude of closest edge
|
|
|
|
|
if Δλe <= Δλw {
|
|
|
|
|
Δλ = Δλe
|
|
|
|
|
λedge = λl
|
|
|
|
|
} else {
|
|
|
|
|
Δλ = Δλw
|
|
|
|
|
λedge = λh
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sinΔλ, cosΔλ := math.Sincos(Δλ)
|
|
|
|
|
tanφq := math.Tan(φq)
|
|
|
|
|
|
|
|
|
|
if Δλ >= halfΠ {
|
|
|
|
|
// If Δλ > 90 degrees (1/2 pi in radians) we're in one of the corners
|
|
|
|
|
// (NW/SW or NE/SE depending on the edge selected). Compare against the
|
|
|
|
|
// center line to decide which case we fall into
|
|
|
|
|
φmid := (φh + φl) / 2
|
|
|
|
|
if tanφq >= math.Tan(φmid)*cosΔλ {
|
|
|
|
|
return distRad(φq, λq, φh, λedge) // North corner
|
|
|
|
|
}
|
|
|
|
|
return distRad(φq, λq, φl, λedge) // South corner
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if tanφq >= math.Tan(φh)*cosΔλ {
|
|
|
|
|
return distRad(φq, λq, φh, λedge) // North corner
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if tanφq <= math.Tan(φl)*cosΔλ {
|
|
|
|
|
return distRad(φq, λq, φl, λedge) // South corner
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// We're to the East or West of the rect, compute distance using cross-track
|
|
|
|
|
// Note that this is a simplification of the cross track distance formula
|
|
|
|
|
// valid since the track in question is a meridian.
|
|
|
|
|
return math.Asin(math.Cos(φq) * sinΔλ)
|
|
|
|
|
}
|