mirror of https://github.com/tidwall/tile38.git
Merge pull request #119 from m1ome/nearby-distance
Added distance to NEARBY command
This commit is contained in:
commit
83975f80cc
|
@ -149,7 +149,13 @@ func fenceMatch(hookName string, sw *scanWriter, fence *liveFenceSwitches, metas
|
||||||
sw.fmap = details.fmap
|
sw.fmap = details.fmap
|
||||||
sw.fullFields = true
|
sw.fullFields = true
|
||||||
sw.msg.OutputType = server.JSON
|
sw.msg.OutputType = server.JSON
|
||||||
sw.writeObject(details.id, details.obj, details.fields, true)
|
sw.writeObject(ScanWriterParams{
|
||||||
|
id:details.id,
|
||||||
|
o: details.obj,
|
||||||
|
fields: details.fields,
|
||||||
|
noLock: true,
|
||||||
|
})
|
||||||
|
|
||||||
if sw.wr.Len() == 0 {
|
if sw.wr.Len() == 0 {
|
||||||
sw.mu.Unlock()
|
sw.mu.Unlock()
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -50,14 +50,22 @@ func (c *Controller) cmdScan(msg *server.Message) (res string, err error) {
|
||||||
if g.Limits[0] == "" && g.Limits[1] == "" {
|
if g.Limits[0] == "" && g.Limits[1] == "" {
|
||||||
s.cursor = sw.col.Scan(s.cursor, s.desc,
|
s.cursor = sw.col.Scan(s.cursor, s.desc,
|
||||||
func(id string, o geojson.Object, fields []float64) bool {
|
func(id string, o geojson.Object, fields []float64) bool {
|
||||||
return sw.writeObject(id, o, fields, false)
|
return sw.writeObject(ScanWriterParams{
|
||||||
|
id: id,
|
||||||
|
o: o,
|
||||||
|
fields: fields,
|
||||||
|
})
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
s.cursor = sw.col.ScanRange(
|
s.cursor = sw.col.ScanRange(
|
||||||
s.cursor, g.Limits[0], g.Limits[1], s.desc,
|
s.cursor, g.Limits[0], g.Limits[1], s.desc,
|
||||||
func(id string, o geojson.Object, fields []float64) bool {
|
func(id string, o geojson.Object, fields []float64) bool {
|
||||||
return sw.writeObject(id, o, fields, false)
|
return sw.writeObject(ScanWriterParams{
|
||||||
|
id: id,
|
||||||
|
o: o,
|
||||||
|
fields: fields,
|
||||||
|
})
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,6 +54,14 @@ type scanWriter struct {
|
||||||
matchValues bool
|
matchValues bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ScanWriterParams struct {
|
||||||
|
id string
|
||||||
|
o geojson.Object
|
||||||
|
fields []float64
|
||||||
|
distance float64
|
||||||
|
noLock bool
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Controller) newScanWriter(
|
func (c *Controller) newScanWriter(
|
||||||
wr *bytes.Buffer, msg *server.Message, key string, output outputT,
|
wr *bytes.Buffer, msg *server.Message, key string, output outputT,
|
||||||
precision uint64, globPattern string, matchValues bool,
|
precision uint64, globPattern string, matchValues bool,
|
||||||
|
@ -233,24 +241,25 @@ func (sw *scanWriter) fieldMatch(fields []float64, o geojson.Object) ([]float64,
|
||||||
return sw.fvals, true
|
return sw.fvals, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sw *scanWriter) writeObject(id string, o geojson.Object, fields []float64, noLock bool) bool {
|
//id string, o geojson.Object, fields []float64, noLock bool
|
||||||
if !noLock {
|
func (sw *scanWriter) writeObject(opts ScanWriterParams) bool {
|
||||||
|
if !opts.noLock {
|
||||||
sw.mu.Lock()
|
sw.mu.Lock()
|
||||||
defer sw.mu.Unlock()
|
defer sw.mu.Unlock()
|
||||||
}
|
}
|
||||||
keepGoing := true
|
keepGoing := true
|
||||||
if !sw.globEverything {
|
if !sw.globEverything {
|
||||||
if sw.globSingle {
|
if sw.globSingle {
|
||||||
if sw.globPattern != id {
|
if sw.globPattern != opts.id {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
keepGoing = false // return current object and stop iterating
|
keepGoing = false // return current object and stop iterating
|
||||||
} else {
|
} else {
|
||||||
var val string
|
var val string
|
||||||
if sw.matchValues {
|
if sw.matchValues {
|
||||||
val = o.String()
|
val = opts.o.String()
|
||||||
} else {
|
} else {
|
||||||
val = id
|
val = opts.id
|
||||||
}
|
}
|
||||||
ok, _ := glob.Match(sw.globPattern, val)
|
ok, _ := glob.Match(sw.globPattern, val)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -258,7 +267,7 @@ func (sw *scanWriter) writeObject(id string, o geojson.Object, fields []float64,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
nfields, ok := sw.fieldMatch(fields, o)
|
nfields, ok := sw.fieldMatch(opts.fields, opts.o)
|
||||||
if !ok {
|
if !ok {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -282,12 +291,12 @@ func (sw *scanWriter) writeObject(id string, o geojson.Object, fields []float64,
|
||||||
jsfields = `,"fields":{`
|
jsfields = `,"fields":{`
|
||||||
var i int
|
var i int
|
||||||
for field, idx := range sw.fmap {
|
for field, idx := range sw.fmap {
|
||||||
if len(fields) > idx {
|
if len(opts.fields) > idx {
|
||||||
if fields[idx] != 0 {
|
if opts.fields[idx] != 0 {
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
jsfields += `,`
|
jsfields += `,`
|
||||||
}
|
}
|
||||||
jsfields += jsonString(field) + ":" + strconv.FormatFloat(fields[idx], 'f', -1, 64)
|
jsfields += jsonString(field) + ":" + strconv.FormatFloat(opts.fields[idx], 'f', -1, 64)
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -307,38 +316,44 @@ func (sw *scanWriter) writeObject(id string, o geojson.Object, fields []float64,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if sw.output == outputIDs {
|
if sw.output == outputIDs {
|
||||||
wr.WriteString(jsonString(id))
|
wr.WriteString(jsonString(opts.id))
|
||||||
} else {
|
} else {
|
||||||
wr.WriteString(`{"id":` + jsonString(id))
|
wr.WriteString(`{"id":` + jsonString(opts.id))
|
||||||
switch sw.output {
|
switch sw.output {
|
||||||
case outputObjects:
|
case outputObjects:
|
||||||
wr.WriteString(`,"object":` + o.JSON())
|
wr.WriteString(`,"object":` + opts.o.JSON())
|
||||||
case outputPoints:
|
case outputPoints:
|
||||||
wr.WriteString(`,"point":` + o.CalculatedPoint().ExternalJSON())
|
wr.WriteString(`,"point":` + opts.o.CalculatedPoint().ExternalJSON())
|
||||||
case outputHashes:
|
case outputHashes:
|
||||||
p, err := o.Geohash(int(sw.precision))
|
p, err := opts.o.Geohash(int(sw.precision))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p = ""
|
p = ""
|
||||||
}
|
}
|
||||||
wr.WriteString(`,"hash":"` + p + `"`)
|
wr.WriteString(`,"hash":"` + p + `"`)
|
||||||
case outputBounds:
|
case outputBounds:
|
||||||
wr.WriteString(`,"bounds":` + o.CalculatedBBox().ExternalJSON())
|
wr.WriteString(`,"bounds":` + opts.o.CalculatedBBox().ExternalJSON())
|
||||||
}
|
}
|
||||||
|
|
||||||
wr.WriteString(jsfields)
|
wr.WriteString(jsfields)
|
||||||
|
|
||||||
|
if opts.distance > 0 {
|
||||||
|
wr.WriteString(`,"distance":` + strconv.FormatFloat(opts.distance, 'f', 2, 64))
|
||||||
|
}
|
||||||
|
|
||||||
wr.WriteString(`}`)
|
wr.WriteString(`}`)
|
||||||
}
|
}
|
||||||
sw.wr.Write(wr.Bytes())
|
sw.wr.Write(wr.Bytes())
|
||||||
case server.RESP:
|
case server.RESP:
|
||||||
vals := make([]resp.Value, 1, 3)
|
vals := make([]resp.Value, 1, 3)
|
||||||
vals[0] = resp.StringValue(id)
|
vals[0] = resp.StringValue(opts.id)
|
||||||
if sw.output == outputIDs {
|
if sw.output == outputIDs {
|
||||||
sw.values = append(sw.values, vals[0])
|
sw.values = append(sw.values, vals[0])
|
||||||
} else {
|
} else {
|
||||||
switch sw.output {
|
switch sw.output {
|
||||||
case outputObjects:
|
case outputObjects:
|
||||||
vals = append(vals, resp.StringValue(o.String()))
|
vals = append(vals, resp.StringValue(opts.o.String()))
|
||||||
case outputPoints:
|
case outputPoints:
|
||||||
point := o.CalculatedPoint()
|
point := opts.o.CalculatedPoint()
|
||||||
if point.Z != 0 {
|
if point.Z != 0 {
|
||||||
vals = append(vals, resp.ArrayValue([]resp.Value{
|
vals = append(vals, resp.ArrayValue([]resp.Value{
|
||||||
resp.FloatValue(point.Y),
|
resp.FloatValue(point.Y),
|
||||||
|
@ -352,13 +367,13 @@ func (sw *scanWriter) writeObject(id string, o geojson.Object, fields []float64,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
case outputHashes:
|
case outputHashes:
|
||||||
p, err := o.Geohash(int(sw.precision))
|
p, err := opts.o.Geohash(int(sw.precision))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p = ""
|
p = ""
|
||||||
}
|
}
|
||||||
vals = append(vals, resp.StringValue(p))
|
vals = append(vals, resp.StringValue(p))
|
||||||
case outputBounds:
|
case outputBounds:
|
||||||
bbox := o.CalculatedBBox()
|
bbox := opts.o.CalculatedBBox()
|
||||||
vals = append(vals, resp.ArrayValue([]resp.Value{
|
vals = append(vals, resp.ArrayValue([]resp.Value{
|
||||||
resp.ArrayValue([]resp.Value{
|
resp.ArrayValue([]resp.Value{
|
||||||
resp.FloatValue(bbox.Min.Y),
|
resp.FloatValue(bbox.Min.Y),
|
||||||
|
@ -371,7 +386,7 @@ func (sw *scanWriter) writeObject(id string, o geojson.Object, fields []float64,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
fvs := orderFields(sw.fmap, fields)
|
fvs := orderFields(sw.fmap, opts.fields)
|
||||||
if len(fvs) > 0 {
|
if len(fvs) > 0 {
|
||||||
fvals := make([]resp.Value, 0, len(fvs)*2)
|
fvals := make([]resp.Value, 0, len(fvs)*2)
|
||||||
for i, fv := range fvs {
|
for i, fv := range fvs {
|
||||||
|
@ -381,6 +396,10 @@ func (sw *scanWriter) writeObject(id string, o geojson.Object, fields []float64,
|
||||||
vals = append(vals, resp.ArrayValue(fvals))
|
vals = append(vals, resp.ArrayValue(fvals))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if opts.distance > 0 {
|
||||||
|
vals = append(vals, resp.FloatValue(opts.distance))
|
||||||
|
}
|
||||||
|
|
||||||
sw.values = append(sw.values, resp.ArrayValue(vals))
|
sw.values = append(sw.values, resp.ArrayValue(vals))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -291,7 +291,18 @@ func (c *Controller) cmdNearby(msg *server.Message) (res string, err error) {
|
||||||
sw.writeHead()
|
sw.writeHead()
|
||||||
if sw.col != nil {
|
if sw.col != nil {
|
||||||
s.cursor = sw.col.Nearby(s.cursor, s.sparse, s.lat, s.lon, s.meters, minZ, maxZ, func(id string, o geojson.Object, fields []float64) bool {
|
s.cursor = sw.col.Nearby(s.cursor, s.sparse, s.lat, s.lon, s.meters, minZ, maxZ, func(id string, o geojson.Object, fields []float64) bool {
|
||||||
return sw.writeObject(id, o, fields, false)
|
// Calculate distance if we need to
|
||||||
|
distance := 0.0
|
||||||
|
if s.distance {
|
||||||
|
distance = o.CalculatedPoint().DistanceTo(geojson.Position{X: s.lon, Y: s.lat, Z: 0})
|
||||||
|
}
|
||||||
|
|
||||||
|
return sw.writeObject(ScanWriterParams{
|
||||||
|
id: id,
|
||||||
|
o: o,
|
||||||
|
fields: fields,
|
||||||
|
distance: distance,
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
sw.writeFoot(s.cursor)
|
sw.writeFoot(s.cursor)
|
||||||
|
@ -337,13 +348,21 @@ func (c *Controller) cmdWithinOrIntersects(cmd string, msg *server.Message) (res
|
||||||
if cmd == "within" {
|
if cmd == "within" {
|
||||||
s.cursor = sw.col.Within(s.cursor, s.sparse, s.o, s.minLat, s.minLon, s.maxLat, s.maxLon, minZ, maxZ,
|
s.cursor = sw.col.Within(s.cursor, s.sparse, s.o, s.minLat, s.minLon, s.maxLat, s.maxLon, minZ, maxZ,
|
||||||
func(id string, o geojson.Object, fields []float64) bool {
|
func(id string, o geojson.Object, fields []float64) bool {
|
||||||
return sw.writeObject(id, o, fields, false)
|
return sw.writeObject(ScanWriterParams{
|
||||||
|
id: id,
|
||||||
|
o: o,
|
||||||
|
fields: fields,
|
||||||
|
})
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
} else if cmd == "intersects" {
|
} else if cmd == "intersects" {
|
||||||
s.cursor = sw.col.Intersects(s.cursor, s.sparse, s.o, s.minLat, s.minLon, s.maxLat, s.maxLon, minZ, maxZ,
|
s.cursor = sw.col.Intersects(s.cursor, s.sparse, s.o, s.minLat, s.minLon, s.maxLat, s.maxLon, minZ, maxZ,
|
||||||
func(id string, o geojson.Object, fields []float64) bool {
|
func(id string, o geojson.Object, fields []float64) bool {
|
||||||
return sw.writeObject(id, o, fields, false)
|
return sw.writeObject(ScanWriterParams{
|
||||||
|
id: id,
|
||||||
|
o: o,
|
||||||
|
fields: fields,
|
||||||
|
})
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -394,7 +413,11 @@ func (c *Controller) cmdSearch(msg *server.Message) (res string, err error) {
|
||||||
if g.Limits[0] == "" && g.Limits[1] == "" {
|
if g.Limits[0] == "" && g.Limits[1] == "" {
|
||||||
s.cursor = sw.col.SearchValues(s.cursor, s.desc,
|
s.cursor = sw.col.SearchValues(s.cursor, s.desc,
|
||||||
func(id string, o geojson.Object, fields []float64) bool {
|
func(id string, o geojson.Object, fields []float64) bool {
|
||||||
return sw.writeObject(id, o, fields, false)
|
return sw.writeObject(ScanWriterParams{
|
||||||
|
id: id,
|
||||||
|
o: o,
|
||||||
|
fields: fields,
|
||||||
|
})
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
|
@ -404,7 +427,11 @@ func (c *Controller) cmdSearch(msg *server.Message) (res string, err error) {
|
||||||
s.cursor = sw.col.SearchValuesRange(
|
s.cursor = sw.col.SearchValuesRange(
|
||||||
s.cursor, g.Limits[0], g.Limits[1], s.desc,
|
s.cursor, g.Limits[0], g.Limits[1], s.desc,
|
||||||
func(id string, o geojson.Object, fields []float64) bool {
|
func(id string, o geojson.Object, fields []float64) bool {
|
||||||
return sw.writeObject(id, o, fields, false)
|
return sw.writeObject(ScanWriterParams{
|
||||||
|
id: id,
|
||||||
|
o: o,
|
||||||
|
fields: fields,
|
||||||
|
})
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -163,6 +163,7 @@ type searchScanBaseTokens struct {
|
||||||
precision uint64
|
precision uint64
|
||||||
lineout string
|
lineout string
|
||||||
fence bool
|
fence bool
|
||||||
|
distance bool
|
||||||
detect map[string]bool
|
detect map[string]bool
|
||||||
accept map[string]bool
|
accept map[string]bool
|
||||||
glob string
|
glob string
|
||||||
|
@ -304,6 +305,14 @@ func parseSearchScanBaseTokens(cmd string, vs []resp.Value) (vsout []resp.Value,
|
||||||
t.accept = nil
|
t.accept = nil
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
|
} else if (wtok[0] == 'D' || wtok[0] == 'd') && strings.ToLower(wtok) == "distance" {
|
||||||
|
vs = nvs
|
||||||
|
if t.distance {
|
||||||
|
err = errDuplicateArgument(strings.ToUpper(wtok))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.distance = true
|
||||||
|
continue
|
||||||
} else if (wtok[0] == 'D' || wtok[0] == 'd') && strings.ToLower(wtok) == "detect" {
|
} else if (wtok[0] == 'D' || wtok[0] == 'd') && strings.ToLower(wtok) == "detect" {
|
||||||
vs = nvs
|
vs = nvs
|
||||||
if t.detect != nil {
|
if t.detect != nil {
|
||||||
|
|
|
@ -483,6 +483,12 @@
|
||||||
"type": "pattern",
|
"type": "pattern",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"command": "DISTANCE",
|
||||||
|
"name": [],
|
||||||
|
"type": [],
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"command": "WHERE",
|
"command": "WHERE",
|
||||||
"name": ["field","min","max"],
|
"name": ["field","min","max"],
|
||||||
|
|
|
@ -645,6 +645,12 @@ var commandsJSON = `{
|
||||||
"type": "pattern",
|
"type": "pattern",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"command": "DISTANCE",
|
||||||
|
"name": [],
|
||||||
|
"type": [],
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"command": "WHERE",
|
"command": "WHERE",
|
||||||
"name": ["field","min","max"],
|
"name": ["field","min","max"],
|
||||||
|
|
Loading…
Reference in New Issue