diff --git a/Gopkg.lock b/Gopkg.lock index 47b1fadb..85b5a6a6 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -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", diff --git a/internal/server/search.go b/internal/server/search.go index bfc90ae9..cb9ebda7 100644 --- a/internal/server/search.go +++ b/internal/server/search.go @@ -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}) diff --git a/vendor/github.com/tidwall/geojson/circle.go b/vendor/github.com/tidwall/geojson/circle.go index 143269e8..277766f0 100644 --- a/vendor/github.com/tidwall/geojson/circle.go +++ b/vendor/github.com/tidwall/geojson/circle.go @@ -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: diff --git a/vendor/github.com/tidwall/geojson/circle_test.go b/vendor/github.com/tidwall/geojson/circle_test.go index d793e1a1..d72d0537 100644 --- a/vendor/github.com/tidwall/geojson/circle_test.go +++ b/vendor/github.com/tidwall/geojson/circle_test.go @@ -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) //} diff --git a/vendor/github.com/tidwall/geojson/geo/geo.go b/vendor/github.com/tidwall/geojson/geo/geo.go index 3b5c769b..d82fc9a3 100644 --- a/vendor/github.com/tidwall/geojson/geo/geo.go +++ b/vendor/github.com/tidwall/geojson/geo/geo.go @@ -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 diff --git a/vendor/github.com/tidwall/geojson/geo/geo_test.go b/vendor/github.com/tidwall/geojson/geo/geo_test.go index f406e80a..2fd4f522 100644 --- a/vendor/github.com/tidwall/geojson/geo/geo_test.go +++ b/vendor/github.com/tidwall/geojson/geo/geo_test.go @@ -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) + } +}