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]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
digest = "1:c5ac96e72d3ff6694602f3273dd71ef04a67c9591465aac92dc1aa8c821b8f91"
|
digest = "1:145703130ac1de36086ab350337777161f9c1d791e81a73659ac1f569e15b5e5"
|
||||||
name = "github.com/tidwall/geojson"
|
name = "github.com/tidwall/geojson"
|
||||||
packages = [
|
packages = [
|
||||||
".",
|
".",
|
||||||
|
@ -251,7 +251,7 @@
|
||||||
"geometry",
|
"geometry",
|
||||||
]
|
]
|
||||||
pruneopts = ""
|
pruneopts = ""
|
||||||
revision = "32782c39ca84f98113436a297f14601e4fee527d"
|
revision = "dbcb73c57c65ff784ce2ccaad3f062c9787d6f81"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
digest = "1:3ddca2bd5496c6922a2a9e636530e178a43c2a534ea6634211acdc7d10222794"
|
digest = "1:3ddca2bd5496c6922a2a9e636530e178a43c2a534ea6634211acdc7d10222794"
|
||||||
|
@ -467,6 +467,7 @@
|
||||||
"github.com/tidwall/buntdb",
|
"github.com/tidwall/buntdb",
|
||||||
"github.com/tidwall/evio",
|
"github.com/tidwall/evio",
|
||||||
"github.com/tidwall/geojson",
|
"github.com/tidwall/geojson",
|
||||||
|
"github.com/tidwall/geojson/geo",
|
||||||
"github.com/tidwall/geojson/geometry",
|
"github.com/tidwall/geojson/geometry",
|
||||||
"github.com/tidwall/gjson",
|
"github.com/tidwall/gjson",
|
||||||
"github.com/tidwall/lotsa",
|
"github.com/tidwall/lotsa",
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
|
|
||||||
"github.com/mmcloughlin/geohash"
|
"github.com/mmcloughlin/geohash"
|
||||||
"github.com/tidwall/geojson"
|
"github.com/tidwall/geojson"
|
||||||
|
"github.com/tidwall/geojson/geo"
|
||||||
"github.com/tidwall/geojson/geometry"
|
"github.com/tidwall/geojson/geometry"
|
||||||
"github.com/tidwall/resp"
|
"github.com/tidwall/resp"
|
||||||
"github.com/tidwall/tile38/internal/bing"
|
"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 {
|
if sw.col != nil {
|
||||||
var matched uint32
|
var matched uint32
|
||||||
iter := func(id string, o geojson.Object, fields []float64, dist *float64) bool {
|
iter := func(id string, o geojson.Object, fields []float64, dist *float64) bool {
|
||||||
// Calculate distance if we need to
|
|
||||||
distance := 0.0
|
distance := 0.0
|
||||||
if s.distance {
|
if s.distance {
|
||||||
if dist != nil {
|
distance = geo.DistanceFromHaversine(*dist)
|
||||||
distance = *dist
|
|
||||||
} else {
|
|
||||||
distance = o.Distance(s.obj)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return sw.writeObject(ScanWriterParams{
|
return sw.writeObject(ScanWriterParams{
|
||||||
id: id,
|
id: id,
|
||||||
|
@ -411,6 +407,7 @@ func (server *Server) nearestNeighbors(
|
||||||
iter func(id string, o geojson.Object, fields []float64, dist *float64,
|
iter func(id string, o geojson.Object, fields []float64, dist *float64,
|
||||||
) bool) {
|
) bool) {
|
||||||
limit := int(sw.cursor + sw.limit)
|
limit := int(sw.cursor + sw.limit)
|
||||||
|
maxDist := target.Haversine()
|
||||||
var items []iterItem
|
var items []iterItem
|
||||||
sw.col.Nearby(target, func(id string, o geojson.Object, fields []float64) bool {
|
sw.col.Nearby(target, func(id string, o geojson.Object, fields []float64) bool {
|
||||||
if server.hasExpired(s.key, id) {
|
if server.hasExpired(s.key, id) {
|
||||||
|
@ -423,8 +420,8 @@ func (server *Server) nearestNeighbors(
|
||||||
if !match {
|
if !match {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
dist := o.Distance(target)
|
dist := target.HaversineTo(o.Center())
|
||||||
if target.Meters() > 0 && dist > target.Meters() {
|
if maxDist > 0 && dist > maxDist {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
items = append(items, iterItem{id: id, o: o, fields: fields, dist: dist})
|
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
|
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
|
// Within returns true if circle is contained inside object
|
||||||
func (g *Circle) Within(obj Object) bool {
|
func (g *Circle) Within(obj Object) bool {
|
||||||
return obj.Contains(g)
|
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)
|
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
|
// Contains returns true if the circle contains other object
|
||||||
func (g *Circle) Contains(obj Object) bool {
|
func (g *Circle) Contains(obj Object) bool {
|
||||||
switch other := obj.(type) {
|
switch other := obj.(type) {
|
||||||
case *Point:
|
case *Point:
|
||||||
return g.contains(other.Center(), false)
|
return g.containsPoint(other.Center())
|
||||||
case *Circle:
|
case *Circle:
|
||||||
return other.Distance(g) < (other.meters + g.meters)
|
return other.Distance(g) < (other.meters + g.meters)
|
||||||
case *LineString:
|
case *LineString:
|
||||||
for i := 0; i < other.base.NumPoints(); i++ {
|
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
|
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 {
|
func (g *Circle) intersectsSegment(seg geometry.Segment) bool {
|
||||||
start, end := seg.A, seg.B
|
start, end := seg.A, seg.B
|
||||||
|
|
||||||
// These are faster checks. If they succeed there's no need do complicate things.
|
// These are faster checks.
|
||||||
if g.contains(start, true) {
|
// If they succeed there's no need do complicate things.
|
||||||
return true
|
if g.containsPoint(start) || g.containsPoint(end) {
|
||||||
}
|
|
||||||
if g.contains(end, true) {
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -152,14 +159,14 @@ func (g *Circle) intersectsSegment(seg geometry.Segment) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Distance from the closest point to the center
|
// 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
|
// Intersects returns true the circle intersects other object
|
||||||
func (g *Circle) Intersects(obj Object) bool {
|
func (g *Circle) Intersects(obj Object) bool {
|
||||||
switch other := obj.(type) {
|
switch other := obj.(type) {
|
||||||
case *Point:
|
case *Point:
|
||||||
return g.contains(other.Center(), true)
|
return g.containsPoint(other.Center())
|
||||||
case *Circle:
|
case *Circle:
|
||||||
return other.Distance(g) <= (other.meters + g.meters)
|
return other.Distance(g) <= (other.meters + g.meters)
|
||||||
case *LineString:
|
case *LineString:
|
||||||
|
|
|
@ -163,11 +163,11 @@ func TestCircleIntersects(t *testing.T) {
|
||||||
// This snippet tests 100M comparisons.
|
// This snippet tests 100M comparisons.
|
||||||
// On my box this takes 24.5s without haversine trick, and 13.7s with the trick.
|
// 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)
|
// g := NewCircle(P(-122.4412, 37.7335), 1000, 64)
|
||||||
// r := rand.New(rand.NewSource(42))
|
// r := rand.New(rand.NewSource(42))
|
||||||
// for i:= 0; i < 100000000; i++ {
|
// 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)
|
// expect(t, true)
|
||||||
//}
|
//}
|
||||||
|
|
|
@ -45,11 +45,15 @@ func DistanceToHaversine(meters float64) float64 {
|
||||||
return sin * sin
|
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) {
|
func DistanceTo(latA, lonA, latB, lonB float64) (meters float64) {
|
||||||
a := Haversine(latA, lonA, latB, lonB)
|
a := Haversine(latA, lonA, latB, lonB)
|
||||||
c := 2 * math.Atan2(math.Sqrt(a), math.Sqrt(1-a))
|
return DistanceFromHaversine(a)
|
||||||
return earthRadius * c
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DestinationPoint return the destination from a point based on 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) {
|
func TestNormalizeDistance(t *testing.T) {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
for time.Since(start) < time.Second/4 {
|
for time.Since(start) < time.Second {
|
||||||
for i := 0; i < 1000; i++ {
|
for i := 0; i < 1000; i++ {
|
||||||
meters1 := rand.Float64() * 100000000
|
meters1 := rand.Float64() * earthRadius * 3 // wrap three times
|
||||||
meters2 := NormalizeDistance(meters1)
|
meters2 := NormalizeDistance(meters1)
|
||||||
dist1 := math.Floor(DistanceToHaversine(meters2) * 100000000.0)
|
dist1 := math.Floor(DistanceToHaversine(meters2) * 1e8)
|
||||||
dist2 := math.Floor(DistanceToHaversine(meters1) * 100000000.0)
|
dist2 := math.Floor(DistanceToHaversine(meters1) * 1e8)
|
||||||
if dist1 != dist2 {
|
if dist1 != dist2 {
|
||||||
t.Fatalf("expected %f, got %f", dist2, dist1)
|
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