diff --git a/controller/fence.go b/controller/fence.go index e9c3f795..92ebfc78 100644 --- a/controller/fence.go +++ b/controller/fence.go @@ -149,7 +149,13 @@ func fenceMatch(hookName string, sw *scanWriter, fence *liveFenceSwitches, metas sw.fmap = details.fmap sw.fullFields = true 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 { sw.mu.Unlock() return nil diff --git a/controller/scan.go b/controller/scan.go index 1ef61aff..ddfe407e 100644 --- a/controller/scan.go +++ b/controller/scan.go @@ -50,14 +50,22 @@ func (c *Controller) cmdScan(msg *server.Message) (res string, err error) { if g.Limits[0] == "" && g.Limits[1] == "" { s.cursor = sw.col.Scan(s.cursor, s.desc, 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 { s.cursor = sw.col.ScanRange( s.cursor, g.Limits[0], g.Limits[1], s.desc, 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, + }) }, ) } diff --git a/controller/scanner.go b/controller/scanner.go index 5cdc99ef..9ddc5a42 100644 --- a/controller/scanner.go +++ b/controller/scanner.go @@ -54,6 +54,14 @@ type scanWriter struct { matchValues bool } +type ScanWriterParams struct { + id string + o geojson.Object + fields []float64 + distance float64 + noLock bool +} + func (c *Controller) newScanWriter( wr *bytes.Buffer, msg *server.Message, key string, output outputT, precision uint64, globPattern string, matchValues bool, @@ -233,24 +241,25 @@ func (sw *scanWriter) fieldMatch(fields []float64, o geojson.Object) ([]float64, return sw.fvals, true } -func (sw *scanWriter) writeObject(id string, o geojson.Object, fields []float64, noLock bool) bool { - if !noLock { +//id string, o geojson.Object, fields []float64, noLock bool +func (sw *scanWriter) writeObject(opts ScanWriterParams) bool { + if !opts.noLock { sw.mu.Lock() defer sw.mu.Unlock() } keepGoing := true if !sw.globEverything { if sw.globSingle { - if sw.globPattern != id { + if sw.globPattern != opts.id { return true } keepGoing = false // return current object and stop iterating } else { var val string if sw.matchValues { - val = o.String() + val = opts.o.String() } else { - val = id + val = opts.id } ok, _ := glob.Match(sw.globPattern, val) 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 { return true } @@ -282,12 +291,12 @@ func (sw *scanWriter) writeObject(id string, o geojson.Object, fields []float64, jsfields = `,"fields":{` var i int for field, idx := range sw.fmap { - if len(fields) > idx { - if fields[idx] != 0 { + if len(opts.fields) > idx { + if opts.fields[idx] != 0 { if i > 0 { jsfields += `,` } - jsfields += jsonString(field) + ":" + strconv.FormatFloat(fields[idx], 'f', -1, 64) + jsfields += jsonString(field) + ":" + strconv.FormatFloat(opts.fields[idx], 'f', -1, 64) i++ } } @@ -307,38 +316,44 @@ func (sw *scanWriter) writeObject(id string, o geojson.Object, fields []float64, } } if sw.output == outputIDs { - wr.WriteString(jsonString(id)) + wr.WriteString(jsonString(opts.id)) } else { - wr.WriteString(`{"id":` + jsonString(id)) + wr.WriteString(`{"id":` + jsonString(opts.id)) switch sw.output { case outputObjects: - wr.WriteString(`,"object":` + o.JSON()) + wr.WriteString(`,"object":` + opts.o.JSON()) case outputPoints: - wr.WriteString(`,"point":` + o.CalculatedPoint().ExternalJSON()) + wr.WriteString(`,"point":` + opts.o.CalculatedPoint().ExternalJSON()) case outputHashes: - p, err := o.Geohash(int(sw.precision)) + p, err := opts.o.Geohash(int(sw.precision)) if err != nil { p = "" } wr.WriteString(`,"hash":"` + p + `"`) case outputBounds: - wr.WriteString(`,"bounds":` + o.CalculatedBBox().ExternalJSON()) + wr.WriteString(`,"bounds":` + opts.o.CalculatedBBox().ExternalJSON()) } + wr.WriteString(jsfields) + + if opts.distance > 0 { + wr.WriteString(`,"distance":` + strconv.FormatFloat(opts.distance, 'f', 2, 64)) + } + wr.WriteString(`}`) } sw.wr.Write(wr.Bytes()) case server.RESP: vals := make([]resp.Value, 1, 3) - vals[0] = resp.StringValue(id) + vals[0] = resp.StringValue(opts.id) if sw.output == outputIDs { sw.values = append(sw.values, vals[0]) } else { switch sw.output { case outputObjects: - vals = append(vals, resp.StringValue(o.String())) + vals = append(vals, resp.StringValue(opts.o.String())) case outputPoints: - point := o.CalculatedPoint() + point := opts.o.CalculatedPoint() if point.Z != 0 { vals = append(vals, resp.ArrayValue([]resp.Value{ resp.FloatValue(point.Y), @@ -352,13 +367,13 @@ func (sw *scanWriter) writeObject(id string, o geojson.Object, fields []float64, })) } case outputHashes: - p, err := o.Geohash(int(sw.precision)) + p, err := opts.o.Geohash(int(sw.precision)) if err != nil { p = "" } vals = append(vals, resp.StringValue(p)) case outputBounds: - bbox := o.CalculatedBBox() + bbox := opts.o.CalculatedBBox() vals = append(vals, resp.ArrayValue([]resp.Value{ resp.ArrayValue([]resp.Value{ 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 { fvals := make([]resp.Value, 0, len(fvs)*2) 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)) } + if opts.distance > 0 { + vals = append(vals, resp.FloatValue(opts.distance)) + } + sw.values = append(sw.values, resp.ArrayValue(vals)) } } diff --git a/controller/search.go b/controller/search.go index e31c6a2e..a256bff0 100644 --- a/controller/search.go +++ b/controller/search.go @@ -291,7 +291,18 @@ func (c *Controller) cmdNearby(msg *server.Message) (res string, err error) { sw.writeHead() 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 { - 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) @@ -337,13 +348,21 @@ func (c *Controller) cmdWithinOrIntersects(cmd string, msg *server.Message) (res if cmd == "within" { 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 { - return sw.writeObject(id, o, fields, false) + return sw.writeObject(ScanWriterParams{ + id: id, + o: o, + fields: fields, + }) }, ) } 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, 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] == "" { s.cursor = sw.col.SearchValues(s.cursor, s.desc, 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 { @@ -404,7 +427,11 @@ func (c *Controller) cmdSearch(msg *server.Message) (res string, err error) { s.cursor = sw.col.SearchValuesRange( s.cursor, g.Limits[0], g.Limits[1], s.desc, 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, + }) }, ) } diff --git a/controller/token.go b/controller/token.go index d9d9aaaa..0f0c2fb5 100644 --- a/controller/token.go +++ b/controller/token.go @@ -163,6 +163,7 @@ type searchScanBaseTokens struct { precision uint64 lineout string fence bool + distance bool detect map[string]bool accept map[string]bool glob string @@ -304,6 +305,14 @@ func parseSearchScanBaseTokens(cmd string, vs []resp.Value) (vsout []resp.Value, t.accept = nil } 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" { vs = nvs if t.detect != nil {