From 1d427c849ff5054b51a77dce110426f4a4b80d5d Mon Sep 17 00:00:00 2001 From: Josh Baker Date: Fri, 19 Aug 2016 08:33:58 -0700 Subject: [PATCH] Added BOUNDS command It's now possible to get the combined minimum bounding rectangle for all objects in a key by issuing the command "BOUNDS key". --- controller/collection/collection.go | 5 +++ controller/controller.go | 4 +- controller/crud.go | 57 +++++++++++++++++++++++++++++ core/commands.json | 12 ++++++ core/commands_gen.go | 12 ++++++ index/index.go | 5 +++ 6 files changed, 94 insertions(+), 1 deletion(-) diff --git a/controller/collection/collection.go b/controller/collection/collection.go index 067c999f..f455b4fe 100644 --- a/controller/collection/collection.go +++ b/controller/collection/collection.go @@ -86,6 +86,11 @@ func (c *Collection) TotalWeight() int { return c.weight } +// Bounds returns the bounds of all the items in the collection. +func (c *Collection) Bounds() (minX, minY, maxX, maxY float64) { + return c.index.Bounds() +} + // ReplaceOrInsert adds or replaces an object in the collection and returns the fields array. // If an item with the same id is already in the collection then the new item will adopt the old item's fields. // The fields argument is optional. diff --git a/controller/controller.go b/controller/controller.go index 3d5a789f..7902e6f0 100644 --- a/controller/controller.go +++ b/controller/controller.go @@ -333,7 +333,7 @@ func (c *Controller) handleInputCommand(conn *server.Conn, msg *server.Message, if c.config.ReadOnly { return writeErr(errors.New("read only")) } - case "get", "keys", "scan", "nearby", "within", "intersects", "hooks", "search", "ttl": + case "get", "keys", "scan", "nearby", "within", "intersects", "hooks", "search", "ttl", "bounds": // read operations c.mu.RLock() defer c.mu.RUnlock() @@ -444,6 +444,8 @@ func (c *Controller) command(msg *server.Message, w io.Writer) (res string, d co res, err = c.cmdIntersects(msg) case "search": res, err = c.cmdSearch(msg) + case "bounds": + res, err = c.cmdBounds(msg) case "get": res, err = c.cmdGet(msg) case "keys": diff --git a/controller/crud.go b/controller/crud.go index e3ba9fa1..fbdefd1b 100644 --- a/controller/crud.go +++ b/controller/crud.go @@ -47,6 +47,63 @@ func orderFields(fmap map[string]int, fields []float64) []fvt { sort.Sort(byField(fvs)) return fvs } +func (c *Controller) cmdBounds(msg *server.Message) (string, error) { + start := time.Now() + vs := msg.Values[1:] + + var ok bool + var key string + if vs, key, ok = tokenval(vs); !ok || key == "" { + return "", errInvalidNumberOfArguments + } + if len(vs) != 0 { + return "", errInvalidNumberOfArguments + } + + col := c.getCol(key) + if col == nil { + if msg.OutputType == server.RESP { + return "$-1\r\n", nil + } + return "", errKeyNotFound + } + + vals := make([]resp.Value, 0, 2) + var buf bytes.Buffer + if msg.OutputType == server.JSON { + buf.WriteString(`{"ok":true`) + } + bbox := geojson.New2DBBox(col.Bounds()) + if msg.OutputType == server.JSON { + buf.WriteString(`,"bounds":`) + buf.WriteString(bbox.ExternalJSON()) + } else { + vals = append(vals, resp.ArrayValue([]resp.Value{ + resp.ArrayValue([]resp.Value{ + resp.FloatValue(bbox.Min.Y), + resp.FloatValue(bbox.Min.X), + }), + resp.ArrayValue([]resp.Value{ + resp.FloatValue(bbox.Max.Y), + resp.FloatValue(bbox.Max.X), + }), + })) + } + switch msg.OutputType { + case server.JSON: + buf.WriteString(`,"elapsed":"` + time.Now().Sub(start).String() + "\"}") + return buf.String(), nil + case server.RESP: + var oval resp.Value + oval = vals[0] + data, err := oval.MarshalRESP() + if err != nil { + return "", err + } + return string(data), nil + } + return "", nil +} func (c *Controller) cmdGet(msg *server.Message) (string, error) { start := time.Now() diff --git a/core/commands.json b/core/commands.json index 135b6e34..904a19e6 100644 --- a/core/commands.json +++ b/core/commands.json @@ -176,6 +176,18 @@ "since": "1.0.0", "group": "keys" }, + "BOUNDS": { + "summary": "Get the combined bounds of all the objects in a key", + "complexity": "O(1)", + "arguments":[ + { + "name": "key", + "type": "string" + } + ], + "since": "1.3.0", + "group": "keys" + }, "GET": { "summary": "Get the object of an id", "complexity": "O(1)", diff --git a/core/commands_gen.go b/core/commands_gen.go index 8e9a4fee..fc5e52ea 100644 --- a/core/commands_gen.go +++ b/core/commands_gen.go @@ -338,6 +338,18 @@ var commandsJSON = `{ "since": "1.0.0", "group": "keys" }, + "BOUNDS": { + "summary": "Get the combined bounds of all the objects in a key", + "complexity": "O(1)", + "arguments":[ + { + "name": "key", + "type": "string" + } + ], + "since": "1.3.0", + "group": "keys" + }, "GET": { "summary": "Get the object of an id", "complexity": "O(1)", diff --git a/index/index.go b/index/index.go index 8db768dc..2b400225 100644 --- a/index/index.go +++ b/index/index.go @@ -99,6 +99,11 @@ func (ix *Index) Count() int { return count } +// Bounds returns the minimum bounding rectangle of all items in the index. +func (ix *Index) Bounds() (MinX, MinY, MaxX, MaxY float64) { + return ix.r.Bounds() +} + // RemoveAll removes all items from the index. func (ix *Index) RemoveAll() { ix.r.RemoveAll()