Add subcommand to the INTERSECTS and WITHIN

This commit is contained in:
Alex Roitman 2018-05-04 10:21:40 -07:00
parent e60191caed
commit c4d9ba26d9
17 changed files with 410 additions and 9 deletions

View File

@ -417,9 +417,11 @@ func (c *Collection) Nearby(sparse uint8, lat, lon, meters, minZ, maxZ float64,
}
// Within returns all object that are fully contained within an object or bounding box. Set obj to nil in order to use the bounding box.
func (c *Collection) Within(sparse uint8, obj geojson.Object, minLat, minLon, maxLat, maxLon, minZ, maxZ float64, iterator func(id string, obj geojson.Object, fields []float64) bool) bool {
func (c *Collection) Within(sparse uint8, obj geojson.Object, minLat, minLon, maxLat, maxLon, lat, lon, meters, minZ, maxZ float64, iterator func(id string, obj geojson.Object, fields []float64) bool) bool {
var keepon = true
var bbox geojson.BBox
center := geojson.Position{X: lon, Y: lat, Z: 0}
if obj != nil {
bbox = obj.CalculatedBBox()
if minZ == math.Inf(-1) && maxZ == math.Inf(+1) {
@ -428,6 +430,8 @@ func (c *Collection) Within(sparse uint8, obj geojson.Object, minLat, minLon, ma
bbox.Max.Z = maxZ
}
}
} else if meters != -1 {
bbox = geojson.BBoxesFromCenter(lat, lon, meters)
} else {
bbox = geojson.BBox{Min: geojson.Position{X: minLon, Y: minLat, Z: minZ}, Max: geojson.Position{X: maxLon, Y: maxLat, Z: maxZ}}
}
@ -443,6 +447,15 @@ func (c *Collection) Within(sparse uint8, obj geojson.Object, minLat, minLon, ma
}
return true
})
} else if meters != -1 {
keepon = c.geoSearch(bbox, func(id string, o geojson.Object, fields []float64) bool {
if o.WithinCircle(center, meters) {
if iterator(id, o, fields) {
return false
}
}
return true
})
}
if keepon {
keepon = c.geoSearch(bbox, func(id string, o geojson.Object, fields []float64) bool {
@ -467,6 +480,13 @@ func (c *Collection) Within(sparse uint8, obj geojson.Object, minLat, minLon, ma
}
return true
})
} else if meters != -1 {
return c.geoSearch(bbox, func(id string, o geojson.Object, fields []float64) bool {
if o.WithinCircle(center, meters) {
return iterator(id, o, fields)
}
return true
})
}
return c.geoSearch(bbox, func(id string, o geojson.Object, fields []float64) bool {
if o.WithinBBox(bbox) {
@ -477,9 +497,10 @@ func (c *Collection) Within(sparse uint8, obj geojson.Object, minLat, minLon, ma
}
// Intersects returns all object that are intersect an object or bounding box. Set obj to nil in order to use the bounding box.
func (c *Collection) Intersects(sparse uint8, obj geojson.Object, minLat, minLon, maxLat, maxLon, minZ, maxZ float64, iterator func(id string, obj geojson.Object, fields []float64) bool) bool {
func (c *Collection) Intersects(sparse uint8, obj geojson.Object, minLat, minLon, maxLat, maxLon, lat, lon, meters, minZ, maxZ float64, iterator func(id string, obj geojson.Object, fields []float64) bool) bool {
var keepon = true
var bbox geojson.BBox
center := geojson.Position{X: lon, Y: lat, Z: 0}
if obj != nil {
bbox = obj.CalculatedBBox()
if minZ == math.Inf(-1) && maxZ == math.Inf(+1) {
@ -488,6 +509,8 @@ func (c *Collection) Intersects(sparse uint8, obj geojson.Object, minLat, minLon
bbox.Max.Z = maxZ
}
}
} else if meters != -1 {
bbox = geojson.BBoxesFromCenter(lat, lon, meters)
} else {
bbox = geojson.BBox{Min: geojson.Position{X: minLon, Y: minLat, Z: minZ}, Max: geojson.Position{X: maxLon, Y: maxLat, Z: maxZ}}
}
@ -514,6 +537,15 @@ func (c *Collection) Intersects(sparse uint8, obj geojson.Object, minLat, minLon
}
return true
})
} else if meters != -1 {
keepon = c.geoSearch(bbox, func(id string, o geojson.Object, fields []float64) bool {
if o.IntersectsCircle(center, meters) {
if iterator(id, o, fields) {
return false
}
}
return true
})
}
if keepon {
keepon = c.geoSearch(bbox, func(id string, o geojson.Object, fields []float64) bool {
@ -538,6 +570,13 @@ func (c *Collection) Intersects(sparse uint8, obj geojson.Object, minLat, minLon
}
return true
})
} else if meters != -1 {
return c.geoSearch(bbox, func(id string, o geojson.Object, fields []float64) bool {
if o.IntersectsCircle(center, meters) {
return iterator(id, o, fields)
}
return true
})
}
return c.geoSearch(bbox, func(id string, o geojson.Object, fields []float64) bool {
if o.IntersectsBBox(bbox) {

View File

@ -87,6 +87,7 @@ func (c *Controller) cmdSearchArgs(cmd string, vs []resp.Value, types []string)
err = errInvalidArgument(typ)
return
}
s.meters = -1 // this will become non-negative if search is within a circle
switch ltyp {
case "point":
var slat, slon, smeters string
@ -127,6 +128,41 @@ func (c *Controller) cmdSearchArgs(cmd string, vs []resp.Value, types []string)
err = errInvalidArgument(smeters)
return
}
if s.meters < 0 {
err = errInvalidArgument(smeters)
return
}
}
case "circle":
var slat, slon, smeters string
if vs, slat, ok = tokenval(vs); !ok || slat == "" {
err = errInvalidNumberOfArguments
return
}
if vs, slon, ok = tokenval(vs); !ok || slon == "" {
err = errInvalidNumberOfArguments
return
}
if vs, smeters, ok = tokenval(vs); !ok || smeters == "" {
err = errInvalidArgument(slat)
return
}
if s.lat, err = strconv.ParseFloat(slat, 64); err != nil {
err = errInvalidArgument(slat)
return
}
if s.lon, err = strconv.ParseFloat(slon, 64); err != nil {
err = errInvalidArgument(slon)
return
}
if s.meters, err = strconv.ParseFloat(smeters, 64); err != nil {
err = errInvalidArgument(smeters)
return
}
if s.meters < 0 {
err = errInvalidArgument(smeters)
return
}
case "object":
var obj string
@ -292,7 +328,8 @@ func (c *Controller) cmdSearchArgs(cmd string, vs []resp.Value, types []string)
}
var nearbyTypes = []string{"point"}
var withinOrIntersectsTypes = []string{"geo", "bounds", "hash", "tile", "quadkey", "get", "object"}
var withinOrIntersectsTypes = []string{
"geo", "bounds", "hash", "tile", "quadkey", "get", "object", "circle"}
func (c *Controller) cmdNearby(msg *server.Message) (res resp.Value, err error) {
start := time.Now()
@ -449,7 +486,11 @@ func (c *Controller) cmdWithinOrIntersects(cmd string, msg *server.Message) (res
if sw.col != nil {
minZ, maxZ := zMinMaxFromWheres(s.wheres)
if cmd == "within" {
sw.col.Within(s.sparse, s.o, s.minLat, s.minLon, s.maxLat, s.maxLon, minZ, maxZ,
sw.col.Within(s.sparse,
s.o,
s.minLat, s.minLon, s.maxLat, s.maxLon,
s.lat, s.lon, s.meters,
minZ, maxZ,
func(id string, o geojson.Object, fields []float64) bool {
if c.hasExpired(s.key, id) {
return true
@ -463,7 +504,11 @@ func (c *Controller) cmdWithinOrIntersects(cmd string, msg *server.Message) (res
},
)
} else if cmd == "intersects" {
sw.col.Intersects(s.sparse, s.o, s.minLat, s.minLon, s.maxLat, s.maxLon, minZ, maxZ,
sw.col.Intersects(s.sparse,
s.o,
s.minLat, s.minLon, s.maxLat, s.maxLon,
s.lat, s.lon, s.meters,
minZ, maxZ,
func(id string, o geojson.Object, fields []float64) bool {
if c.hasExpired(s.key, id) {
return true

View File

@ -674,7 +674,7 @@
"group": "search"
},
"WITHIN": {
"summary": "Searches for ids that are nearby a point",
"summary": "Searches for ids that completely within the area",
"complexity": "O(log(N)) where N is the number of ids in the area",
"arguments":[
{
@ -836,6 +836,23 @@
}
]
},
{
"name": "CIRCLE",
"arguments": [
{
"name": "lat",
"type": "double"
},
{
"name": "lon",
"type": "double"
},
{
"name": "meters",
"type": "double"
}
]
},
{
"name": "TILE",
"arguments":[
@ -878,7 +895,7 @@
"group": "search"
},
"INTERSECTS": {
"summary": "Searches for ids that are nearby a point",
"summary": "Searches for ids that intersect an area",
"complexity": "O(log(N)) where N is the number of ids in the area",
"arguments":[
{
@ -1040,6 +1057,23 @@
}
]
},
{
"name": "CIRCLE",
"arguments": [
{
"name": "lat",
"type": "double"
},
{
"name": "lon",
"type": "double"
},
{
"name": "meters",
"type": "double"
}
]
},
{
"name": "TILE",
"arguments":[

View File

@ -840,7 +840,7 @@ var commandsJSON = `{
"group": "search"
},
"WITHIN": {
"summary": "Searches for ids that are nearby a point",
"summary": "Searches for ids that completely within the area",
"complexity": "O(log(N)) where N is the number of ids in the area",
"arguments":[
{
@ -1002,6 +1002,23 @@ var commandsJSON = `{
}
]
},
{
"name": "CIRCLE",
"arguments": [
{
"name": "lat",
"type": "double"
},
{
"name": "lon",
"type": "double"
},
{
"name": "meters",
"type": "double"
}
]
},
{
"name": "TILE",
"arguments":[
@ -1044,7 +1061,7 @@ var commandsJSON = `{
"group": "search"
},
"INTERSECTS": {
"summary": "Searches for ids that are nearby a point",
"summary": "Searches for ids that intersect an area",
"complexity": "O(log(N)) where N is the number of ids in the area",
"arguments":[
{
@ -1206,6 +1223,23 @@ var commandsJSON = `{
}
]
},
{
"name": "CIRCLE",
"arguments": [
{
"name": "lat",
"type": "double"
},
{
"name": "lon",
"type": "double"
},
{
"name": "meters",
"type": "double"
}
]
},
{
"name": "TILE",
"arguments":[

34
pkg/geojson/circle.go Normal file
View File

@ -0,0 +1,34 @@
package geojson
import (
"github.com/tidwall/tile38/pkg/geojson/geo"
)
func SegmentIntersectsCircle(start, end, center Position, meters float64) bool {
// These are faster checks. If they succeed there's no need do complicate things.
if center.DistanceTo(start) <= meters {
return true
}
if center.DistanceTo(end) <= meters {
return true
}
// Distance between start and end
l := geo.DistanceTo(start.Y, start.X, end.Y, end.X)
// Unit direction vector
dx := (end.X - start.X) / l
dy := (end.Y - start.Y) / l
// Point of the line closest to the center
t := dx * (center.X - start.X) + dy * (center.Y - start.Y)
px := t * dx + start.X
py := t * dy + start.Y
if px < start.X || px > end.X || py < start.Y || py > end.Y {
// closest point is outside the segment
return false
}
// Distance from the closest point to the center
return geo.DistanceTo(center.Y, center.X, py, px) <= meters
}

View File

@ -199,6 +199,11 @@ func (g Feature) Within(o Object) bool {
)
}
// WithinCircle detects if the object is fully contained inside a circle.
func (g Feature) WithinCircle(center Position, meters float64) bool {
return g.Geometry.WithinCircle(center, meters)
}
// Intersects detects if the object intersects another object.
func (g Feature) Intersects(o Object) bool {
return intersectsObjectShared(g, o,
@ -208,6 +213,11 @@ func (g Feature) Intersects(o Object) bool {
)
}
// IntersectsCircle detects if the object intersects a circle.
func (g Feature) IntersectsCircle(center Position, meters float64) bool {
return g.Geometry.IntersectsCircle(center, meters)
}
// Nearby detects if the object is nearby a position.
func (g Feature) Nearby(center Position, meters float64) bool {
return nearbyObjectShared(g, center.X, center.Y, meters)

View File

@ -193,6 +193,19 @@ func (g FeatureCollection) Within(o Object) bool {
)
}
// WithinCircle detects if the object is fully contained inside a circle.
func (g FeatureCollection) WithinCircle(center Position, meters float64) bool {
if len(g.Features) == 0 {
return false
}
for _, feature := range g.Features {
if !feature.WithinCircle(center, meters) {
return false
}
}
return true
}
// Intersects detects if the object intersects another object.
func (g FeatureCollection) Intersects(o Object) bool {
return intersectsObjectShared(g, o,
@ -211,6 +224,16 @@ func (g FeatureCollection) Intersects(o Object) bool {
)
}
// IntersectsCircle detects if the object intersects a circle.
func (g FeatureCollection) IntersectsCircle(center Position, meters float64) bool {
for _, feature := range g.Features {
if feature.IntersectsCircle(center, meters) {
return true
}
}
return false
}
// Nearby detects if the object is nearby a position.
func (g FeatureCollection) Nearby(center Position, meters float64) bool {
return nearbyObjectShared(g, center.X, center.Y, meters)

View File

@ -192,6 +192,19 @@ func (g GeometryCollection) Within(o Object) bool {
)
}
// WithinCircle detects if the object is fully contained inside a circle.
func (g GeometryCollection) WithinCircle(center Position, meters float64) bool {
if len(g.Geometries) == 0 {
return false
}
for _, geometry := range g.Geometries {
if !geometry.WithinCircle(center, meters) {
return false
}
}
return true
}
// Intersects detects if the object intersects another object.
func (g GeometryCollection) Intersects(o Object) bool {
return intersectsObjectShared(g, o,
@ -209,6 +222,16 @@ func (g GeometryCollection) Intersects(o Object) bool {
)
}
// IntersectsCircle detects if the object intersects a circle.
func (g GeometryCollection) IntersectsCircle(center Position, meters float64) bool {
for _, geometry := range g.Geometries {
if geometry.IntersectsCircle(center, meters) {
return true
}
}
return false
}
// Nearby detects if the object is nearby a position.
func (g GeometryCollection) Nearby(center Position, meters float64) bool {
return nearbyObjectShared(g, center.X, center.Y, meters)

View File

@ -105,6 +105,19 @@ func (g LineString) Within(o Object) bool {
)
}
// WithinCircle detects if the object is fully contained inside a circle.
func (g LineString) WithinCircle(center Position, meters float64) bool {
if len(g.Coordinates) == 0 {
return false
}
for _, position := range g.Coordinates {
if center.DistanceTo(position) >= meters {
return false
}
}
return true
}
// Intersects detects if the object intersects another object.
func (g LineString) Intersects(o Object) bool {
return intersectsObjectShared(g, o,
@ -114,6 +127,16 @@ func (g LineString) Intersects(o Object) bool {
)
}
// IntersectsCircle detects if the object intersects a circle.
func (g LineString) IntersectsCircle(center Position, meters float64) bool {
for i := 0; i < len(g.Coordinates) - 1 ; i++ {
if SegmentIntersectsCircle(g.Coordinates[i], g.Coordinates[i + 1], center, meters) {
return true
}
}
return false
}
// Nearby detects if the object is nearby a position.
func (g LineString) Nearby(center Position, meters float64) bool {
return nearbyObjectShared(g, center.X, center.Y, meters)

View File

@ -148,6 +148,24 @@ func (g MultiLineString) Within(o Object) bool {
)
}
// WithinCircle detects if the object is fully contained inside a circle.
func (g MultiLineString) WithinCircle(center Position, meters float64) bool {
if len(g.Coordinates) == 0 {
return false
}
for _, ls := range g.Coordinates {
if len(ls) == 0 {
return false
}
for _, position := range ls {
if center.DistanceTo(position) >= meters {
return false
}
}
}
return true
}
// Intersects detects if the object intersects another object.
func (g MultiLineString) Intersects(o Object) bool {
return intersectsObjectShared(g, o,
@ -165,6 +183,18 @@ func (g MultiLineString) Intersects(o Object) bool {
)
}
// IntersectsCircle detects if the object intersects a circle.
func (g MultiLineString) IntersectsCircle(center Position, meters float64) bool {
for _, ls := range g.Coordinates {
for i := 0; i < len(ls) - 1 ; i++ {
if SegmentIntersectsCircle(ls[i], ls[i + 1], center, meters) {
return true
}
}
}
return false
}
// Nearby detects if the object is nearby a position.
func (g MultiLineString) Nearby(center Position, meters float64) bool {
return nearbyObjectShared(g, center.X, center.Y, meters)

View File

@ -123,6 +123,19 @@ func (g MultiPoint) Within(o Object) bool {
)
}
// WithinCircle detects if the object is fully contained inside a circle.
func (g MultiPoint) WithinCircle(center Position, meters float64) bool {
if len(g.Coordinates) == 0 {
return false
}
for _, position := range g.Coordinates {
if center.DistanceTo(position) >= meters {
return false
}
}
return true
}
// Intersects detects if the object intersects another object.
func (g MultiPoint) Intersects(o Object) bool {
return intersectsObjectShared(g, o,
@ -140,6 +153,16 @@ func (g MultiPoint) Intersects(o Object) bool {
)
}
// IntersectsCircle detects if the object intersects a circle.
func (g MultiPoint) IntersectsCircle(center Position, meters float64) bool {
for _, position := range g.Coordinates {
if center.DistanceTo(position) <= meters {
return true
}
}
return false
}
// Nearby detects if the object is nearby a position.
func (g MultiPoint) Nearby(center Position, meters float64) bool {
return nearbyObjectShared(g, center.X, center.Y, meters)

View File

@ -161,6 +161,19 @@ func (g MultiPolygon) Within(o Object) bool {
)
}
// WithinCircle detects if the object is fully contained inside a circle.
func (g MultiPolygon) WithinCircle(center Position, meters float64) bool {
if len(g.polygons) == 0 {
return false
}
for _, polygon := range g.polygons {
if !polygon.WithinCircle(center, meters) {
return false
}
}
return true
}
// Intersects detects if the object intersects another object.
func (g MultiPolygon) Intersects(o Object) bool {
return intersectsObjectShared(g, o,
@ -176,6 +189,16 @@ func (g MultiPolygon) Intersects(o Object) bool {
)
}
// IntersectsCircle detects if the object intersects a circle.
func (g MultiPolygon) IntersectsCircle(center Position, meters float64) bool {
for _, polygon := range g.polygons {
if polygon.IntersectsCircle(center, meters) {
return true
}
}
return false
}
// Nearby detects if the object is nearby a position.
func (g MultiPolygon) Nearby(center Position, meters float64) bool {
return nearbyObjectShared(g, center.X, center.Y, meters)

View File

@ -68,6 +68,10 @@ type Object interface {
Within(o Object) bool
// Intersects detects if the object intersects another object.
Intersects(o Object) bool
// WithinCircle detects if the object is fully contained inside a circle.
WithinCircle(center Position, meters float64) bool
// IntersectsCircle detects if the object intersects a circle.
IntersectsCircle(center Position, meters float64) bool
// Nearby detects if the object is nearby a position.
Nearby(center Position, meters float64) bool
// CalculatedBBox is exterior bbox containing the object.

View File

@ -112,6 +112,11 @@ func (g Point) Within(o Object) bool {
)
}
// WithinCircle detects if the object is fully contained inside a circle.
func (g Point) WithinCircle(center Position, meters float64) bool {
return geo.DistanceTo(g.Coordinates.Y, g.Coordinates.X, center.Y, center.X) < meters
}
// Intersects detects if the object intersects another object.
func (g Point) Intersects(o Object) bool {
return intersectsObjectShared(g, o,
@ -121,6 +126,11 @@ func (g Point) Intersects(o Object) bool {
)
}
// IntersectsCircle detects if the object intersects a circle.
func (g Point) IntersectsCircle(center Position, meters float64) bool {
return geo.DistanceTo(g.Coordinates.Y, g.Coordinates.X, center.Y, center.X) <= meters
}
// Nearby detects if the object is nearby a position.
func (g Point) Nearby(center Position, meters float64) bool {
return geo.DistanceTo(g.Coordinates.Y, g.Coordinates.X, center.Y, center.X) <= meters

View File

@ -155,6 +155,19 @@ func (g Polygon) Within(o Object) bool {
)
}
// WithinCircle detects if the object is fully contained inside a circle.
func (g Polygon) WithinCircle(center Position, meters float64) bool {
if len(g.Coordinates) == 0 {
return false
}
for _, position := range g.Coordinates[0] {
if center.DistanceTo(position) >= meters {
return false
}
}
return true
}
// Intersects detects if the object intersects another object.
func (g Polygon) Intersects(o Object) bool {
return intersectsObjectShared(g, o,
@ -167,6 +180,21 @@ func (g Polygon) Intersects(o Object) bool {
)
}
// IntersectsCircle detects if the object intersects a circle.
func (g Polygon) IntersectsCircle(center Position, meters float64) bool {
if g.Intersects(New2DPoint(center.X, center.Y)) {
return true
}
for _, polygon := range g.Coordinates {
for i := 0; i < len(polygon) - 1 ; i++ {
if SegmentIntersectsCircle(polygon[i], polygon[i + 1], center, meters) {
return true
}
}
}
return false
}
// Nearby detects if the object is nearby a position.
func (g Polygon) Nearby(center Position, meters float64) bool {
return nearbyObjectShared(g, center.X, center.Y, meters)

View File

@ -94,6 +94,11 @@ func (g SimplePoint) Within(o Object) bool {
)
}
// WithinCircle detects if the object is fully contained inside a circle.
func (g SimplePoint) WithinCircle(center Position, meters float64) bool {
return geo.DistanceTo(center.Y, center.X, g.Y, g.X) < meters
}
// Intersects detects if the object intersects another object.
func (g SimplePoint) Intersects(o Object) bool {
return intersectsObjectShared(g, o,
@ -103,6 +108,11 @@ func (g SimplePoint) Intersects(o Object) bool {
)
}
// IntersectsCircle detects if the object intersects a circle.
func (g SimplePoint) IntersectsCircle(center Position, meters float64) bool {
return geo.DistanceTo(center.Y, center.X, g.Y, g.X) <= meters
}
// Nearby detects if the object is nearby a position.
func (g SimplePoint) Nearby(center Position, meters float64) bool {
return geo.DistanceTo(center.Y, center.X, g.Y, g.X) <= meters

View File

@ -25,10 +25,18 @@ func (s String) Within(o Object) bool {
return false
}
// WithinCircle detects if the object is fully contained inside a circle.
func (s String) WithinCircle(center Position, meters float64) bool {
return false
}
// Intersects detects if the object intersects another object.
func (s String) Intersects(o Object) bool {
return false
}
func (s String) IntersectsCircle(center Position, meters float64) bool {
return false
}
// Nearby detects if the object is nearby a position.
func (s String) Nearby(center Position, meters float64) bool {