mirror of https://github.com/tidwall/tile38.git
Merge pull request #376 from rshura/knn_haversine
Use haversine instead of distance in knn if distance is not required.
This commit is contained in:
commit
1a5ab9fb78
|
@ -243,7 +243,7 @@
|
|||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:c5ac96e72d3ff6694602f3273dd71ef04a67c9591465aac92dc1aa8c821b8f91"
|
||||
digest = "1:145703130ac1de36086ab350337777161f9c1d791e81a73659ac1f569e15b5e5"
|
||||
name = "github.com/tidwall/geojson"
|
||||
packages = [
|
||||
".",
|
||||
|
@ -251,7 +251,7 @@
|
|||
"geometry",
|
||||
]
|
||||
pruneopts = ""
|
||||
revision = "32782c39ca84f98113436a297f14601e4fee527d"
|
||||
revision = "dbcb73c57c65ff784ce2ccaad3f062c9787d6f81"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:3ddca2bd5496c6922a2a9e636530e178a43c2a534ea6634211acdc7d10222794"
|
||||
|
@ -467,6 +467,7 @@
|
|||
"github.com/tidwall/buntdb",
|
||||
"github.com/tidwall/evio",
|
||||
"github.com/tidwall/geojson",
|
||||
"github.com/tidwall/geojson/geo",
|
||||
"github.com/tidwall/geojson/geometry",
|
||||
"github.com/tidwall/gjson",
|
||||
"github.com/tidwall/lotsa",
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
|
||||
"github.com/mmcloughlin/geohash"
|
||||
"github.com/tidwall/geojson"
|
||||
"github.com/tidwall/geojson/geo"
|
||||
"github.com/tidwall/geojson/geometry"
|
||||
"github.com/tidwall/resp"
|
||||
"github.com/tidwall/tile38/internal/bing"
|
||||
|
@ -371,14 +372,9 @@ func (server *Server) cmdNearby(msg *Message) (res resp.Value, err error) {
|
|||
if sw.col != nil {
|
||||
var matched uint32
|
||||
iter := func(id string, o geojson.Object, fields []float64, dist *float64) bool {
|
||||
// Calculate distance if we need to
|
||||
distance := 0.0
|
||||
if s.distance {
|
||||
if dist != nil {
|
||||
distance = *dist
|
||||
} else {
|
||||
distance = o.Distance(s.obj)
|
||||
}
|
||||
distance = geo.DistanceFromHaversine(*dist)
|
||||
}
|
||||
return sw.writeObject(ScanWriterParams{
|
||||
id: id,
|
||||
|
@ -411,6 +407,7 @@ func (server *Server) nearestNeighbors(
|
|||
iter func(id string, o geojson.Object, fields []float64, dist *float64,
|
||||
) bool) {
|
||||
limit := int(sw.cursor + sw.limit)
|
||||
maxDist := target.Haversine()
|
||||
var items []iterItem
|
||||
sw.col.Nearby(target, func(id string, o geojson.Object, fields []float64) bool {
|
||||
if server.hasExpired(s.key, id) {
|
||||
|
@ -423,8 +420,8 @@ func (server *Server) nearestNeighbors(
|
|||
if !match {
|
||||
return true
|
||||
}
|
||||
dist := o.Distance(target)
|
||||
if target.Meters() > 0 && dist > target.Meters() {
|
||||
dist := target.HaversineTo(o.Center())
|
||||
if maxDist > 0 && dist > maxDist {
|
||||
return false
|
||||
}
|
||||
items = append(items, iterItem{id: id, o: o, fields: fields, dist: dist})
|
||||
|
|
|
@ -84,29 +84,37 @@ func (g *Circle) Center() geometry.Point {
|
|||
return g.center
|
||||
}
|
||||
|
||||
// Haversine returns the haversine corresponding to circle's radius
|
||||
func (g *Circle) Haversine() float64 {
|
||||
return g.haversine
|
||||
}
|
||||
|
||||
// HaversineTo returns the haversine from a given point to circle's center
|
||||
func (g *Circle) HaversineTo(p geometry.Point) float64 {
|
||||
return geo.Haversine(p.Y, p.X, g.center.Y, g.center.X)
|
||||
}
|
||||
|
||||
// Within returns true if circle is contained inside object
|
||||
func (g *Circle) Within(obj Object) bool {
|
||||
return obj.Contains(g)
|
||||
}
|
||||
|
||||
func (g *Circle) contains(p geometry.Point, allowOnEdge bool) bool {
|
||||
// containsPoint returns true if circle contains a given point
|
||||
func (g *Circle) containsPoint(p geometry.Point) bool {
|
||||
h := geo.Haversine(p.Y, p.X, g.center.Y, g.center.X)
|
||||
if allowOnEdge {
|
||||
return h <= g.haversine
|
||||
}
|
||||
return h < g.haversine
|
||||
return h <= g.haversine
|
||||
}
|
||||
|
||||
// Contains returns true if the circle contains other object
|
||||
func (g *Circle) Contains(obj Object) bool {
|
||||
switch other := obj.(type) {
|
||||
case *Point:
|
||||
return g.contains(other.Center(), false)
|
||||
return g.containsPoint(other.Center())
|
||||
case *Circle:
|
||||
return other.Distance(g) < (other.meters + g.meters)
|
||||
case *LineString:
|
||||
for i := 0; i < other.base.NumPoints(); i++ {
|
||||
if geoDistancePoints(other.base.PointAt(i), g.center) > g.meters {
|
||||
if !g.containsPoint(other.base.PointAt(i)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
@ -124,14 +132,13 @@ func (g *Circle) Contains(obj Object) bool {
|
|||
}
|
||||
}
|
||||
|
||||
// intersectsSegment returns true if the circle intersects a given segment
|
||||
func (g *Circle) intersectsSegment(seg geometry.Segment) bool {
|
||||
start, end := seg.A, seg.B
|
||||
|
||||
// These are faster checks. If they succeed there's no need do complicate things.
|
||||
if g.contains(start, true) {
|
||||
return true
|
||||
}
|
||||
if g.contains(end, true) {
|
||||
// These are faster checks.
|
||||
// If they succeed there's no need do complicate things.
|
||||
if g.containsPoint(start) || g.containsPoint(end) {
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -152,14 +159,14 @@ func (g *Circle) intersectsSegment(seg geometry.Segment) bool {
|
|||
}
|
||||
|
||||
// Distance from the closest point to the center
|
||||
return g.contains(geometry.Point{X: px, Y: py}, true)
|
||||
return g.containsPoint(geometry.Point{X: px, Y: py})
|
||||
}
|
||||
|
||||
// Intersects returns true the circle intersects other object
|
||||
func (g *Circle) Intersects(obj Object) bool {
|
||||
switch other := obj.(type) {
|
||||
case *Point:
|
||||
return g.contains(other.Center(), true)
|
||||
return g.containsPoint(other.Center())
|
||||
case *Circle:
|
||||
return other.Distance(g) <= (other.meters + g.meters)
|
||||
case *LineString:
|
||||
|
|
|
@ -163,11 +163,11 @@ func TestCircleIntersects(t *testing.T) {
|
|||
// This snippet tests 100M comparisons.
|
||||
// On my box this takes 24.5s without haversine trick, and 13.7s with the trick.
|
||||
//
|
||||
//func TestCircle_Performance(t *testing.T) {
|
||||
//func TestCirclePerformance(t *testing.T) {
|
||||
// g := NewCircle(P(-122.4412, 37.7335), 1000, 64)
|
||||
// r := rand.New(rand.NewSource(42))
|
||||
// for i:= 0; i < 100000000; i++ {
|
||||
// g.Contains(PO((r.Float64() - 0.5) * 180, r.Float64() * 90))
|
||||
// g.Contains(PO(r.Float64()*360 - 180, r.Float64()*180 - 90))
|
||||
// }
|
||||
// expect(t, true)
|
||||
//}
|
||||
|
|
|
@ -45,11 +45,15 @@ func DistanceToHaversine(meters float64) float64 {
|
|||
return sin * sin
|
||||
}
|
||||
|
||||
// DistanceTo return the distance in meteres between two point.
|
||||
// DistanceFromHaversine...
|
||||
func DistanceFromHaversine(haversine float64) float64 {
|
||||
return earthRadius * 2 * math.Asin(math.Sqrt(haversine))
|
||||
}
|
||||
|
||||
// DistanceTo return the distance in meters between two point.
|
||||
func DistanceTo(latA, lonA, latB, lonB float64) (meters float64) {
|
||||
a := Haversine(latA, lonA, latB, lonB)
|
||||
c := 2 * math.Atan2(math.Sqrt(a), math.Sqrt(1-a))
|
||||
return earthRadius * c
|
||||
return DistanceFromHaversine(a)
|
||||
}
|
||||
|
||||
// DestinationPoint return the destination from a point based on a
|
||||
|
|
|
@ -77,15 +77,51 @@ func TestHaversine(t *testing.T) {
|
|||
|
||||
func TestNormalizeDistance(t *testing.T) {
|
||||
start := time.Now()
|
||||
for time.Since(start) < time.Second/4 {
|
||||
for time.Since(start) < time.Second {
|
||||
for i := 0; i < 1000; i++ {
|
||||
meters1 := rand.Float64() * 100000000
|
||||
meters1 := rand.Float64() * earthRadius * 3 // wrap three times
|
||||
meters2 := NormalizeDistance(meters1)
|
||||
dist1 := math.Floor(DistanceToHaversine(meters2) * 100000000.0)
|
||||
dist2 := math.Floor(DistanceToHaversine(meters1) * 100000000.0)
|
||||
dist1 := math.Floor(DistanceToHaversine(meters2) * 1e8)
|
||||
dist2 := math.Floor(DistanceToHaversine(meters1) * 1e8)
|
||||
if dist1 != dist2 {
|
||||
t.Fatalf("expected %f, got %f", dist2, dist1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue