From f7ba43360e4031be1e59be90be2fc96140122a47 Mon Sep 17 00:00:00 2001 From: Alex Roitman Date: Fri, 8 Feb 2019 13:56:07 -0800 Subject: [PATCH 1/9] Typo in error messages --- internal/server/search.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/server/search.go b/internal/server/search.go index 2788107f..9315d77a 100644 --- a/internal/server/search.go +++ b/internal/server/search.go @@ -106,7 +106,7 @@ func (server *Server) cmdSearchArgs( fallthrough case "circle": if s.clip { - err = errInvalidArgument("cannnot clip with " + ltyp) + err = errInvalidArgument("cannot clip with " + ltyp) return } var slat, slon, smeters string @@ -158,7 +158,7 @@ func (server *Server) cmdSearchArgs( s.obj = geojson.NewCircle(geometry.Point{X: lon, Y: lat}, meters, defaultCircleSteps) case "object": if s.clip { - err = errInvalidArgument("cannnot clip with object") + err = errInvalidArgument("cannot clip with object") return } var obj string @@ -272,7 +272,7 @@ func (server *Server) cmdSearchArgs( }) case "get": if s.clip { - err = errInvalidArgument("cannnot clip with get") + err = errInvalidArgument("cannot clip with get") } var key, id string if vs, key, ok = tokenval(vs); !ok || key == "" { From a300cb2bf8d99fd1f955602d2a9cfe9a4ac95409 Mon Sep 17 00:00:00 2001 From: Alex Roitman Date: Fri, 8 Feb 2019 13:56:43 -0800 Subject: [PATCH 2/9] Typo causing a bug in polygon clipping --- internal/clip/polygon.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/clip/polygon.go b/internal/clip/polygon.go index 25de61d7..a2bebe4d 100644 --- a/internal/clip/polygon.go +++ b/internal/clip/polygon.go @@ -35,5 +35,5 @@ func clipPolygon( if newPoly.Empty() { return geojson.NewMultiPolygon(nil) } - return polygon + return newPoly } From c849ab19acb292a9b08553e1d2b20f04427f10da Mon Sep 17 00:00:00 2001 From: Alex Roitman Date: Fri, 8 Feb 2019 13:57:29 -0800 Subject: [PATCH 3/9] Implement test command --- internal/server/scripts.go | 8 +- internal/server/server.go | 2 + internal/server/test.go | 300 +++++++++++++++++++++++++++++++++++++ 3 files changed, 307 insertions(+), 3 deletions(-) create mode 100644 internal/server/test.go diff --git a/internal/server/scripts.go b/internal/server/scripts.go index c30ad0a4..c37708ec 100644 --- a/internal/server/scripts.go +++ b/internal/server/scripts.go @@ -620,6 +620,8 @@ func (c *Server) commandInScript(msg *Message) ( res, err = c.cmdType(msg) case "keys": res, err = c.cmdKeys(msg) + case "test": + res, err = c.cmdTest(msg) } return } @@ -668,7 +670,7 @@ func (c *Server) luaTile38AtomicRW(msg *Message) (resp.Value, error) { return resp.NullValue(), errReadOnly } case "get", "keys", "scan", "nearby", "within", "intersects", "hooks", "search", - "ttl", "bounds", "server", "info", "type", "jget": + "ttl", "bounds", "server", "info", "type", "jget", "test": // read operations if c.config.followHost() != "" && !c.fcuponce { return resp.NullValue(), errCatchingUp @@ -700,7 +702,7 @@ func (c *Server) luaTile38AtomicRO(msg *Message) (resp.Value, error) { return resp.NullValue(), errReadOnly case "get", "keys", "scan", "nearby", "within", "intersects", "hooks", "search", - "ttl", "bounds", "server", "info", "type", "jget": + "ttl", "bounds", "server", "info", "type", "jget", "test": // read operations if c.config.followHost() != "" && !c.fcuponce { return resp.NullValue(), errCatchingUp @@ -735,7 +737,7 @@ func (c *Server) luaTile38NonAtomic(msg *Message) (resp.Value, error) { return resp.NullValue(), errReadOnly } case "get", "keys", "scan", "nearby", "within", "intersects", "hooks", "search", - "ttl", "bounds", "server", "info", "type", "jget": + "ttl", "bounds", "server", "info", "type", "jget", "test": // read operations c.mu.RLock() defer c.mu.RUnlock() diff --git a/internal/server/server.go b/internal/server/server.go index 69471948..4f94571e 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -1203,6 +1203,8 @@ func (server *Server) command(msg *Message, client *Client) ( res, err = server.cmdPsubscribe(msg) case "publish": res, err = server.cmdPublish(msg) + case "test": + res, err = server.cmdTest(msg) } return } diff --git a/internal/server/test.go b/internal/server/test.go new file mode 100644 index 00000000..6aecae33 --- /dev/null +++ b/internal/server/test.go @@ -0,0 +1,300 @@ +// TEST command: spatial tests without walking the tree. +package server + +import ( + "bytes" + "fmt" + "strconv" + "strings" + "time" + + "github.com/mmcloughlin/geohash" + "github.com/tidwall/geojson" + "github.com/tidwall/geojson/geometry" + "github.com/tidwall/resp" + "github.com/tidwall/tile38/internal/bing" + "github.com/tidwall/tile38/internal/clip" +) + +func (s * Server) parseArea(ovs[]string, doClip bool) (vs []string, o geojson.Object, err error) { + var ok bool + var typ string + vs = ovs[:] + if vs, typ, ok = tokenval(vs); !ok || typ == "" { + err = errInvalidNumberOfArguments + return + } + ltyp := strings.ToLower(typ) + switch ltyp { + case "point": + var slat, slon string + if vs, slat, ok = tokenval(vs); !ok || slat == "" { + err = errInvalidNumberOfArguments + return + } + if vs, slon, ok = tokenval(vs); !ok || slon == "" { + err = errInvalidNumberOfArguments + return + } + var lat, lon float64 + if lat, err = strconv.ParseFloat(slat, 64); err != nil { + err = errInvalidArgument(slat) + return + } + if lon, err = strconv.ParseFloat(slon, 64); err != nil { + err = errInvalidArgument(slon) + return + } + o = geojson.NewPoint(geometry.Point{X: lon, Y: lat}) + case "circle": + if doClip { + err = errInvalidArgument("cannot clip with " + ltyp) + return + } + 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 + } + var lat, lon, meters float64 + if lat, err = strconv.ParseFloat(slat, 64); err != nil { + err = errInvalidArgument(slat) + return + } + if lon, err = strconv.ParseFloat(slon, 64); err != nil { + err = errInvalidArgument(slon) + return + } + if vs, smeters, ok = tokenval(vs); !ok || smeters == "" { + err = errInvalidNumberOfArguments + return + } + if meters, err = strconv.ParseFloat(smeters, 64); err != nil { + err = errInvalidArgument(smeters) + return + } + if meters < 0 { + err = errInvalidArgument(smeters) + return + } + o = geojson.NewCircle(geometry.Point{X: lon, Y: lat}, meters, defaultCircleSteps) + case "object": + if doClip { + err = errInvalidArgument("cannot clip with " + ltyp) + return + } + var obj string + if vs, obj, ok = tokenval(vs); !ok || obj == "" { + err = errInvalidNumberOfArguments + return + } + o, err = geojson.Parse(obj, &s.geomParseOpts) + if err != nil { + return + } + case "bounds": + var sminLat, sminLon, smaxlat, smaxlon string + if vs, sminLat, ok = tokenval(vs); !ok || sminLat == "" { + err = errInvalidNumberOfArguments + return + } + if vs, sminLon, ok = tokenval(vs); !ok || sminLon == "" { + err = errInvalidNumberOfArguments + return + } + if vs, smaxlat, ok = tokenval(vs); !ok || smaxlat == "" { + err = errInvalidNumberOfArguments + return + } + if vs, smaxlon, ok = tokenval(vs); !ok || smaxlon == "" { + err = errInvalidNumberOfArguments + return + } + var minLat, minLon, maxLat, maxLon float64 + if minLat, err = strconv.ParseFloat(sminLat, 64); err != nil { + err = errInvalidArgument(sminLat) + return + } + if minLon, err = strconv.ParseFloat(sminLon, 64); err != nil { + err = errInvalidArgument(sminLon) + return + } + if maxLat, err = strconv.ParseFloat(smaxlat, 64); err != nil { + err = errInvalidArgument(smaxlat) + return + } + if maxLon, err = strconv.ParseFloat(smaxlon, 64); err != nil { + err = errInvalidArgument(smaxlon) + return + } + o = geojson.NewRect(geometry.Rect{ + Min: geometry.Point{X: minLon, Y: minLat}, + Max: geometry.Point{X: maxLon, Y: maxLat}, + }) + case "hash": + var hash string + if vs, hash, ok = tokenval(vs); !ok || hash == "" { + err = errInvalidNumberOfArguments + return + } + box := geohash.BoundingBox(hash) + o = geojson.NewRect(geometry.Rect{ + Min: geometry.Point{X: box.MinLng, Y: box.MinLat}, + Max: geometry.Point{X: box.MaxLng, Y: box.MaxLat}, + }) + case "quadkey": + var key string + if vs, key, ok = tokenval(vs); !ok || key == "" { + err = errInvalidNumberOfArguments + return + } + var minLat, minLon, maxLat, maxLon float64 + minLat, minLon, maxLat, maxLon, err = bing.QuadKeyToBounds(key) + if err != nil { + err = errInvalidArgument(key) + return + } + o = geojson.NewRect(geometry.Rect{ + Min: geometry.Point{X: minLon, Y: minLat}, + Max: geometry.Point{X: maxLon, Y: maxLat}, + }) + case "tile": + var sx, sy, sz string + if vs, sx, ok = tokenval(vs); !ok || sx == "" { + err = errInvalidNumberOfArguments + return + } + if vs, sy, ok = tokenval(vs); !ok || sy == "" { + err = errInvalidNumberOfArguments + return + } + if vs, sz, ok = tokenval(vs); !ok || sz == "" { + err = errInvalidNumberOfArguments + return + } + var x, y int64 + var z uint64 + if x, err = strconv.ParseInt(sx, 10, 64); err != nil { + err = errInvalidArgument(sx) + return + } + if y, err = strconv.ParseInt(sy, 10, 64); err != nil { + err = errInvalidArgument(sy) + return + } + if z, err = strconv.ParseUint(sz, 10, 64); err != nil { + err = errInvalidArgument(sz) + return + } + var minLat, minLon, maxLat, maxLon float64 + minLat, minLon, maxLat, maxLon = bing.TileXYToBounds(x, y, z) + o = geojson.NewRect(geometry.Rect{ + Min: geometry.Point{X: minLon, Y: minLat}, + Max: geometry.Point{X: maxLon, Y: maxLat}, + }) + case "get": + if doClip { + err = errInvalidArgument("cannot clip with " + ltyp) + return + } + var key, id string + if vs, key, ok = tokenval(vs); !ok || key == "" { + err = errInvalidNumberOfArguments + return + } + if vs, id, ok = tokenval(vs); !ok || id == "" { + err = errInvalidNumberOfArguments + return + } + col := s.getCol(key) + if col == nil { + err = errKeyNotFound + return + } + o, _, ok = col.Get(id) + if !ok { + err = errIDNotFound + return + } + } + return +} + +func (s *Server) cmdTest (msg *Message) (res resp.Value, err error) { + start := time.Now() + vs := msg.Args[1:] + + var ok bool + var test string + var obj1, obj2, clipped geojson.Object + if vs, obj1, err = s.parseArea(vs, false); err != nil { + return + } + if vs, test, ok = tokenval(vs); !ok || test == "" { + err = errInvalidNumberOfArguments + return + } + lTest := strings.ToLower(test) + if lTest != "within" && lTest != "intersects" { + err = errInvalidArgument(test) + return + } + var wtok string + var nvs []string + var doClip bool + nvs, wtok, ok = tokenval(vs) + if ok && len(wtok) > 0 { + switch strings.ToLower(wtok) { + case "clip": + vs = nvs + if lTest != "intersects" { + err = errInvalidArgument("cannot clip with" + wtok) + return + } + doClip = true + } + } + if vs, obj2, err = s.parseArea(vs, doClip); err != nil { + return + } + if len(vs) != 0 { + err = errInvalidNumberOfArguments + } + + var result int + if lTest == "within" { + if obj1.Within(obj2) { + result = 1 + } + } else if lTest == "intersects" { + if obj1.Intersects(obj2) { + result = 1 + if doClip { + clipped = clip.Clip(obj1, obj2) + } + } + } + switch msg.OutputType { + case JSON: + var buf bytes.Buffer + buf.WriteString(`{"ok":true`) + buf.WriteString(fmt.Sprintf(`,"result":%d`, result)) + if clipped != nil { + buf.WriteString(`,"object":` + clipped.JSON()) + } + buf.WriteString(`,"elapsed":"` + time.Now().Sub(start).String() + "\"}") + return resp.StringValue(buf.String()), nil + case RESP: + if clipped != nil { + return resp.ArrayValue([]resp.Value{ + resp.IntegerValue(result), + resp.StringValue(clipped.JSON())}), nil + } + return resp.IntegerValue(result), nil + } + return NOMessage, nil +} From a3b17258c9c0d4c4a53b9a6ccc933f58400249b1 Mon Sep 17 00:00:00 2001 From: Alex Roitman Date: Fri, 8 Feb 2019 13:57:48 -0800 Subject: [PATCH 4/9] Add command description --- core/commands.json | 225 +++++++++++++++++++++++++++++++++++++++++++ core/commands_gen.go | 225 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 450 insertions(+) diff --git a/core/commands.json b/core/commands.json index 7936a695..8fe9b3b7 100644 --- a/core/commands.json +++ b/core/commands.json @@ -1785,5 +1785,230 @@ "complexity": "O(1)", "since": "1.10.0", "group": "scripting" + }, + "TEST":{ + "summary": "Performs spatial test", + "complexity": "One test per command, complexity depends on the test", + "arguments": [ + { + "name": "area1", + "enumargs": [ + { + "name": "GET", + "arguments": [ + { + "name": "key", + "type": "string" + }, + { + "name": "id", + "type": "string" + } + ] + }, + { + "name": "BOUNDS", + "arguments":[ + { + "name": "minlat", + "type": "double" + }, + { + "name": "minlon", + "type": "double" + }, + { + "name": "maxlat", + "type": "double" + }, + { + "name": "maxlon", + "type": "double" + } + ] + }, + { + "name": "OBJECT", + "arguments":[ + { + "name": "geojson", + "type": "geojson" + } + ] + }, + { + "name": "CIRCLE", + "arguments": [ + { + "name": "lat", + "type": "double" + }, + { + "name": "lon", + "type": "double" + }, + { + "name": "meters", + "type": "double" + } + ] + }, + { + "name": "TILE", + "arguments":[ + { + "name": "x", + "type": "double" + }, + { + "name": "y", + "type": "double" + }, + { + "name": "z", + "type": "double" + } + ] + }, + { + "name": "QUADKEY", + "arguments":[ + { + "name": "quadkey", + "type": "string" + } + ] + }, + { + "name": "HASH", + "arguments": [ + { + "name": "geohash", + "type": "geohash" + } + ] + } + ] + }, + { + "name": "test", + "enumargs": [ + { + "name": "INTERSECTS" + }, + { + "name": "WITHIN" + } + ] + }, + { + "command": "CLIP", + "name": [], + "type": [], + "optional": true + }, + { + "name": "area2", + "enumargs": [ + { + "name": "GET", + "arguments": [ + { + "name": "key", + "type": "string" + }, + { + "name": "id", + "type": "string" + } + ] + }, + { + "name": "BOUNDS", + "arguments":[ + { + "name": "minlat", + "type": "double" + }, + { + "name": "minlon", + "type": "double" + }, + { + "name": "maxlat", + "type": "double" + }, + { + "name": "maxlon", + "type": "double" + } + ] + }, + { + "name": "OBJECT", + "arguments":[ + { + "name": "geojson", + "type": "geojson" + } + ] + }, + { + "name": "CIRCLE", + "arguments": [ + { + "name": "lat", + "type": "double" + }, + { + "name": "lon", + "type": "double" + }, + { + "name": "meters", + "type": "double" + } + ] + }, + { + "name": "TILE", + "arguments":[ + { + "name": "x", + "type": "double" + }, + { + "name": "y", + "type": "double" + }, + { + "name": "z", + "type": "double" + } + ] + }, + { + "name": "QUADKEY", + "arguments":[ + { + "name": "quadkey", + "type": "string" + } + ] + }, + { + "name": "HASH", + "arguments": [ + { + "name": "geohash", + "type": "geohash" + } + ] + } + ] + } + ], + "since": "1.16.0", + "group": "tests" } } diff --git a/core/commands_gen.go b/core/commands_gen.go index fddaa3b2..5b4157a5 100644 --- a/core/commands_gen.go +++ b/core/commands_gen.go @@ -1951,5 +1951,230 @@ var commandsJSON = `{ "complexity": "O(1)", "since": "1.10.0", "group": "scripting" + }, + "TEST":{ + "summary": "Performs spatial tests", + "complexity": "One test per command, complexity depends on the test", + "arguments": [ + { + "name": "area1", + "enumargs": [ + { + "name": "GET", + "arguments": [ + { + "name": "key", + "type": "string" + }, + { + "name": "id", + "type": "string" + } + ] + }, + { + "name": "BOUNDS", + "arguments":[ + { + "name": "minlat", + "type": "double" + }, + { + "name": "minlon", + "type": "double" + }, + { + "name": "maxlat", + "type": "double" + }, + { + "name": "maxlon", + "type": "double" + } + ] + }, + { + "name": "OBJECT", + "arguments":[ + { + "name": "geojson", + "type": "geojson" + } + ] + }, + { + "name": "CIRCLE", + "arguments": [ + { + "name": "lat", + "type": "double" + }, + { + "name": "lon", + "type": "double" + }, + { + "name": "meters", + "type": "double" + } + ] + }, + { + "name": "TILE", + "arguments":[ + { + "name": "x", + "type": "double" + }, + { + "name": "y", + "type": "double" + }, + { + "name": "z", + "type": "double" + } + ] + }, + { + "name": "QUADKEY", + "arguments":[ + { + "name": "quadkey", + "type": "string" + } + ] + }, + { + "name": "HASH", + "arguments": [ + { + "name": "geohash", + "type": "geohash" + } + ] + } + ] + }, + { + "name": "test", + "enumargs": [ + { + "name": "INTERSECTS" + }, + { + "name": "WITHIN" + } + ] + }, + { + "command": "CLIP", + "name": [], + "type": [], + "optional": true + }, + { + "name": "area2", + "enumargs": [ + { + "name": "GET", + "arguments": [ + { + "name": "key", + "type": "string" + }, + { + "name": "id", + "type": "string" + } + ] + }, + { + "name": "BOUNDS", + "arguments":[ + { + "name": "minlat", + "type": "double" + }, + { + "name": "minlon", + "type": "double" + }, + { + "name": "maxlat", + "type": "double" + }, + { + "name": "maxlon", + "type": "double" + } + ] + }, + { + "name": "OBJECT", + "arguments":[ + { + "name": "geojson", + "type": "geojson" + } + ] + }, + { + "name": "CIRCLE", + "arguments": [ + { + "name": "lat", + "type": "double" + }, + { + "name": "lon", + "type": "double" + }, + { + "name": "meters", + "type": "double" + } + ] + }, + { + "name": "TILE", + "arguments":[ + { + "name": "x", + "type": "double" + }, + { + "name": "y", + "type": "double" + }, + { + "name": "z", + "type": "double" + } + ] + }, + { + "name": "QUADKEY", + "arguments":[ + { + "name": "quadkey", + "type": "string" + } + ] + }, + { + "name": "HASH", + "arguments": [ + { + "name": "geohash", + "type": "geohash" + } + ] + } + ] + } + ], + "since": "1.16.0", + "group": "tests" } }` From 1cffdcc31fbdcbe733b4532f72d5ab790709e24a Mon Sep 17 00:00:00 2001 From: Alex Roitman Date: Fri, 8 Feb 2019 13:58:04 -0800 Subject: [PATCH 5/9] Add tests for the TEST comand --- tests/testcmd_test.go | 116 ++++++++++++++++++++++++++++++++++++++++++ tests/tests_test.go | 1 + 2 files changed, 117 insertions(+) create mode 100644 tests/testcmd_test.go diff --git a/tests/testcmd_test.go b/tests/testcmd_test.go new file mode 100644 index 00000000..f2cef7af --- /dev/null +++ b/tests/testcmd_test.go @@ -0,0 +1,116 @@ +package tests + +import ( + "testing" +) + +func subTestTestCmd(t *testing.T, mc *mockServer) { + runStep(t, mc, "WITHIN", testcmd_WITHIN_test) + runStep(t, mc, "INTERSECTS", testcmd_INTERSECTS_test) + runStep(t, mc, "INTERSECTS_CLIP", testcmd_INTERSECTS_CLIP_test) +} + +func testcmd_WITHIN_test(mc *mockServer) error { + poly := `{ + "type": "Polygon", + "coordinates": [ + [ + [-122.44126439094543,37.72906137107], + [-122.43980526924135,37.72906137107], + [-122.43980526924135,37.73421283683962], + [-122.44126439094543,37.73421283683962], + [-122.44126439094543,37.72906137107] + ] + ] + }` + poly8 := `{"type":"Polygon","coordinates":[[[-122.4408378,37.7341129],[-122.4408378,37.733],[-122.44,37.733],[-122.44,37.7341129],[-122.4408378,37.7341129]],[[-122.44060993194579,37.73345766902749],[-122.44044363498686,37.73345766902749],[-122.44044363498686,37.73355524732416],[-122.44060993194579,37.73355524732416],[-122.44060993194579,37.73345766902749]],[[-122.44060724973677,37.7336888869566],[-122.4402102828026,37.7336888869566],[-122.4402102828026,37.7339752567853],[-122.44060724973677,37.7339752567853],[-122.44060724973677,37.7336888869566]]]}` + poly9 := `{"type":"Polygon","coordinates":[[[-122.44037926197052,37.73313523548048],[-122.44017541408539,37.73313523548048],[-122.44017541408539,37.73336857568778],[-122.44037926197052,37.73336857568778],[-122.44037926197052,37.73313523548048]]]}` + poly10 := `{"type":"Polygon","coordinates":[[[-122.44040071964262,37.73359343010089],[-122.4402666091919,37.73359343010089],[-122.4402666091919,37.73373767596864],[-122.44040071964262,37.73373767596864],[-122.44040071964262,37.73359343010089]]]}` + + return mc.DoBatch([][]interface{}{ + {"SET", "mykey", "point1", "POINT", 37.7335, -122.4412}, {"OK"}, + {"SET", "mykey", "point2", "POINT", 37.7335, -122.44121}, {"OK"}, + {"SET", "mykey", "line3", "OBJECT", `{"type":"LineString","coordinates":[[-122.4408378,37.7341129],[-122.4408378,37.733]]}`}, {"OK"}, + {"SET", "mykey", "poly4", "OBJECT", `{"type":"Polygon","coordinates":[[[-122.4408378,37.7341129],[-122.4408378,37.733],[-122.44,37.733],[-122.44,37.7341129],[-122.4408378,37.7341129]]]}`}, {"OK"}, + {"SET", "mykey", "multipoly5", "OBJECT", `{"type":"MultiPolygon","coordinates":[[[[-122.4408378,37.7341129],[-122.4408378,37.733],[-122.44,37.733],[-122.44,37.7341129],[-122.4408378,37.7341129]]],[[[-122.44091033935547,37.731981251280985],[-122.43994474411011,37.731981251280985],[-122.43994474411011,37.73254976045042],[-122.44091033935547,37.73254976045042],[-122.44091033935547,37.731981251280985]]]]}`}, {"OK"}, + {"SET", "mykey", "point6", "POINT", -5, 5}, {"OK"}, + {"SET", "mykey", "point7", "POINT", 33, 21}, {"OK"}, + {"SET", "mykey", "poly8", "OBJECT", poly8}, {"OK"}, + + {"TEST", "GET", "mykey", "point1", "WITHIN", "OBJECT", poly}, {"1"}, + {"TEST", "GET", "mykey", "line3", "WITHIN", "OBJECT", poly}, {"1"}, + {"TEST", "GET", "mykey", "poly4", "WITHIN", "OBJECT", poly}, {"1"}, + {"TEST", "GET", "mykey", "multipoly5", "WITHIN", "OBJECT", poly}, {"1"}, + {"TEST", "GET", "mykey", "poly8", "WITHIN", "OBJECT", poly}, {"1"}, + + {"TEST", "GET", "mykey", "point6", "WITHIN", "OBJECT", poly}, {"0"}, + {"TEST", "GET", "mykey", "point7", "WITHIN", "OBJECT", poly}, {"0"}, + + {"TEST", "OBJECT", poly9, "WITHIN", "OBJECT", poly8}, {"1"}, + {"TEST", "OBJECT", poly10, "WITHIN", "OBJECT", poly8}, {"0"}, + }) +} + +func testcmd_INTERSECTS_test(mc *mockServer) error { + poly := `{ + "type": "Polygon", + "coordinates": [ + [ + [-122.44126439094543,37.732906137107], + [-122.43980526924135,37.732906137107], + [-122.43980526924135,37.73421283683962], + [-122.44126439094543,37.73421283683962], + [-122.44126439094543,37.732906137107] + ] + ] + }` + poly8 := `{"type":"Polygon","coordinates":[[[-122.4408378,37.7341129],[-122.4408378,37.733],[-122.44,37.733],[-122.44,37.7341129],[-122.4408378,37.7341129]],[[-122.44060993194579,37.73345766902749],[-122.44044363498686,37.73345766902749],[-122.44044363498686,37.73355524732416],[-122.44060993194579,37.73355524732416],[-122.44060993194579,37.73345766902749]],[[-122.44060724973677,37.7336888869566],[-122.4402102828026,37.7336888869566],[-122.4402102828026,37.7339752567853],[-122.44060724973677,37.7339752567853],[-122.44060724973677,37.7336888869566]]]}` + poly9 := `{"type": "Polygon","coordinates": [[[-122.44037926197052,37.73313523548048],[-122.44017541408539,37.73313523548048],[-122.44017541408539,37.73336857568778],[-122.44037926197052,37.73336857568778],[-122.44037926197052,37.73313523548048]]]}` + poly10 := `{"type": "Polygon","coordinates": [[[-122.44040071964262,37.73359343010089],[-122.4402666091919,37.73359343010089],[-122.4402666091919,37.73373767596864],[-122.44040071964262,37.73373767596864],[-122.44040071964262,37.73359343010089]]]}` + poly101 := `{"type":"Polygon","coordinates":[[[-122.44051605463028,37.73375464605226],[-122.44028002023695,37.73375464605226],[-122.44028002023695,37.733903134117966],[-122.44051605463028,37.733903134117966],[-122.44051605463028,37.73375464605226]]]}` + + return mc.DoBatch([][]interface{}{ + {"SET", "mykey", "point1", "POINT", 37.7335, -122.4412}, {"OK"}, + {"SET", "mykey", "point2", "POINT", 37.7335, -122.44121}, {"OK"}, + {"SET", "mykey", "line3", "OBJECT", `{"type":"LineString","coordinates":[[-122.4408378,37.7341129],[-122.4408378,37.733]]}`}, {"OK"}, + {"SET", "mykey", "poly4", "OBJECT", `{"type":"Polygon","coordinates":[[[-122.4408378,37.7341129],[-122.4408378,37.733],[-122.44,37.733],[-122.44,37.7341129],[-122.4408378,37.7341129]]]}`}, {"OK"}, + {"SET", "mykey", "multipoly5", "OBJECT", `{"type":"MultiPolygon","coordinates":[[[[-122.4408378,37.7341129],[-122.4408378,37.733],[-122.44,37.733],[-122.44,37.7341129],[-122.4408378,37.7341129]]],[[[-122.44091033935547,37.731981251280985],[-122.43994474411011,37.731981251280985],[-122.43994474411011,37.73254976045042],[-122.44091033935547,37.73254976045042],[-122.44091033935547,37.731981251280985]]]]}`}, {"OK"}, + {"SET", "mykey", "point6", "POINT", -5, 5}, {"OK"}, + {"SET", "mykey", "point7", "POINT", 33, 21}, {"OK"}, + {"SET", "mykey", "poly8", "OBJECT", poly8}, {"OK"}, + + {"TEST", "GET", "mykey", "point1", "INTERSECTS", "OBJECT", poly}, {"1"}, + {"TEST", "GET", "mykey", "point2", "INTERSECTS", "OBJECT", poly}, {"1"}, + {"TEST", "GET", "mykey", "line3", "INTERSECTS", "OBJECT", poly}, {"1"}, + {"TEST", "GET", "mykey", "poly4", "INTERSECTS", "OBJECT", poly}, {"1"}, + {"TEST", "GET", "mykey", "multipoly5", "INTERSECTS", "OBJECT", poly}, {"1"}, + {"TEST", "GET", "mykey", "poly8", "INTERSECTS", "OBJECT", poly}, {"1"}, + + {"TEST", "GET", "mykey", "point6", "INTERSECTS", "OBJECT", poly}, {"0"}, + {"TEST", "GET", "mykey", "point7", "INTERSECTS", "OBJECT", poly}, {"0"}, + + {"TEST", "OBJECT", poly9, "INTERSECTS", "OBJECT", poly8}, {"1"}, + {"TEST", "OBJECT", poly10, "INTERSECTS", "OBJECT", poly8}, {"1"}, + {"TEST", "OBJECT", poly101, "INTERSECTS", "OBJECT", poly8}, {"0"}, + }) +} + +func testcmd_INTERSECTS_CLIP_test(mc *mockServer) error { + poly8 := `{"type":"Polygon","coordinates":[[[-122.4408378,37.7341129],[-122.4408378,37.733],[-122.44,37.733],[-122.44,37.7341129],[-122.4408378,37.7341129]],[[-122.44060993194579,37.73345766902749],[-122.44044363498686,37.73345766902749],[-122.44044363498686,37.73355524732416],[-122.44060993194579,37.73355524732416],[-122.44060993194579,37.73345766902749]],[[-122.44060724973677,37.7336888869566],[-122.4402102828026,37.7336888869566],[-122.4402102828026,37.7339752567853],[-122.44060724973677,37.7339752567853],[-122.44060724973677,37.7336888869566]]]}` + poly9 := `{"type":"Polygon","coordinates":[[[-122.44037926197052,37.73313523548048],[-122.44017541408539,37.73313523548048],[-122.44017541408539,37.73336857568778],[-122.44037926197052,37.73336857568778],[-122.44037926197052,37.73313523548048]]]}` + multipoly5 := `{"type":"MultiPolygon","coordinates":[[[[-122.4408378,37.7341129],[-122.4408378,37.733],[-122.44,37.733],[-122.44,37.7341129],[-122.4408378,37.7341129]]],[[[-122.44091033935547,37.731981251280985],[-122.43994474411011,37.731981251280985],[-122.43994474411011,37.73254976045042],[-122.44091033935547,37.73254976045042],[-122.44091033935547,37.731981251280985]]]]}` + poly101 := `{"type":"Polygon","coordinates":[[[-122.44051605463028,37.73375464605226],[-122.44028002023695,37.73375464605226],[-122.44028002023695,37.733903134117966],[-122.44051605463028,37.733903134117966],[-122.44051605463028,37.73375464605226]]]}` + + return mc.DoBatch([][]interface{}{ + {"SET", "mykey", "point1", "POINT", 37.7335, -122.4412}, {"OK"}, + + {"TEST", "OBJECT", poly9, "INTERSECTS", "CLIP", "OBJECT", "{}"}, {"ERR invalid argument 'cannot clip with object'"}, + {"TEST", "OBJECT", poly9, "INTERSECTS", "CLIP", "CIRCLE", "1", "2", "3"}, {"ERR invalid argument 'cannot clip with circle'"}, + {"TEST", "OBJECT", poly9, "INTERSECTS", "CLIP", "GET", "mykey", "point1"}, {"ERR invalid argument 'cannot clip with get'"}, + + {"TEST", "OBJECT", poly9, "INTERSECTS", "CLIP", "BOUNDS", 37.732906137107, -122.44126439094543, 37.73421283683962, -122.43980526924135}, {"[1 " + poly9 + "]"}, + {"TEST", "OBJECT", poly8, "INTERSECTS", "CLIP", "BOUNDS", 37.733, -122.4408378, 37.7341129, -122.44}, {"[1 " + poly8 + "]"}, + {"TEST", "OBJECT", multipoly5, "INTERSECTS", "CLIP", "BOUNDS", 37.73227823422744, -122.44120001792908, 37.73319038868677, -122.43955314159392}, {"[1 " + `{"type":"FeatureCollection","features":[{"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[-122.4408378,37.73319038868677],[-122.4408378,37.733],[-122.44,37.733],[-122.44,37.73319038868677],[-122.4408378,37.73319038868677]]]}},{"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[-122.44091033935547,37.73227823422744],[-122.43994474411011,37.73227823422744],[-122.43994474411011,37.73254976045042],[-122.44091033935547,37.73254976045042],[-122.44091033935547,37.73227823422744]]]}}]}` + "]"}, + {"TEST", "OBJECT", poly101, "INTERSECTS", "CLIP", "BOUNDS", 37.73315644825698, -122.44054287672043, 37.73349585185455, -122.44008690118788}, {"0"}, + }) +} diff --git a/tests/tests_test.go b/tests/tests_test.go index 4de3d18a..f342df1b 100644 --- a/tests/tests_test.go +++ b/tests/tests_test.go @@ -42,6 +42,7 @@ func TestAll(t *testing.T) { runSubTest(t, "keys", mc, subTestKeys) runSubTest(t, "json", mc, subTestJSON) runSubTest(t, "search", mc, subTestSearch) + runSubTest(t, "testcmd", mc, subTestTestCmd) runSubTest(t, "fence", mc, subTestFence) runSubTest(t, "scripts", mc, subTestScripts) runSubTest(t, "info", mc, subTestInfo) From d179a8f2a338d244dc5289c76a15938f698acae8 Mon Sep 17 00:00:00 2001 From: Alex Roitman Date: Sun, 10 Feb 2019 14:56:11 -0800 Subject: [PATCH 6/9] Forgot to add POINT to commands.json --- core/commands.json | 26 ++++++++++++++++++++++++++ core/commands_gen.go | 28 +++++++++++++++++++++++++++- 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/core/commands.json b/core/commands.json index 8fe9b3b7..93adc1bb 100644 --- a/core/commands.json +++ b/core/commands.json @@ -1793,6 +1793,19 @@ { "name": "area1", "enumargs": [ + { + "name": "POINT", + "arguments": [ + { + "name": "lat", + "type": "double" + }, + { + "name": "lon", + "type": "double" + } + ] + }, { "name": "GET", "arguments": [ @@ -1910,6 +1923,19 @@ { "name": "area2", "enumargs": [ + { + "name": "POINT", + "arguments": [ + { + "name": "lat", + "type": "double" + }, + { + "name": "lon", + "type": "double" + } + ] + }, { "name": "GET", "arguments": [ diff --git a/core/commands_gen.go b/core/commands_gen.go index 5b4157a5..70e7769d 100644 --- a/core/commands_gen.go +++ b/core/commands_gen.go @@ -1953,12 +1953,25 @@ var commandsJSON = `{ "group": "scripting" }, "TEST":{ - "summary": "Performs spatial tests", + "summary": "Performs spatial test", "complexity": "One test per command, complexity depends on the test", "arguments": [ { "name": "area1", "enumargs": [ + { + "name": "POINT", + "arguments": [ + { + "name": "lat", + "type": "double" + }, + { + "name": "lon", + "type": "double" + } + ] + }, { "name": "GET", "arguments": [ @@ -2076,6 +2089,19 @@ var commandsJSON = `{ { "name": "area2", "enumargs": [ + { + "name": "POINT", + "arguments": [ + { + "name": "lat", + "type": "double" + }, + { + "name": "lon", + "type": "double" + } + ] + }, { "name": "GET", "arguments": [ From fb7259b10ba5dc064595c29a4ef7d899ae094691 Mon Sep 17 00:00:00 2001 From: tidwall Date: Tue, 12 Feb 2019 05:33:20 -0700 Subject: [PATCH 7/9] Changed clip errors and json result type --- internal/server/test.go | 21 +++++++++++++-------- tests/testcmd_test.go | 7 ++++--- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/internal/server/test.go b/internal/server/test.go index 6aecae33..267f3c27 100644 --- a/internal/server/test.go +++ b/internal/server/test.go @@ -1,6 +1,7 @@ -// TEST command: spatial tests without walking the tree. package server +// TEST command: spatial tests without walking the tree. + import ( "bytes" "fmt" @@ -16,7 +17,7 @@ import ( "github.com/tidwall/tile38/internal/clip" ) -func (s * Server) parseArea(ovs[]string, doClip bool) (vs []string, o geojson.Object, err error) { +func (s *Server) parseArea(ovs []string, doClip bool) (vs []string, o geojson.Object, err error) { var ok bool var typ string vs = ovs[:] @@ -48,7 +49,7 @@ func (s * Server) parseArea(ovs[]string, doClip bool) (vs []string, o geojson.Ob o = geojson.NewPoint(geometry.Point{X: lon, Y: lat}) case "circle": if doClip { - err = errInvalidArgument("cannot clip with " + ltyp) + err = fmt.Errorf("invalid clip type '%s'", typ) return } var slat, slon, smeters string @@ -84,7 +85,7 @@ func (s * Server) parseArea(ovs[]string, doClip bool) (vs []string, o geojson.Ob o = geojson.NewCircle(geometry.Point{X: lon, Y: lat}, meters, defaultCircleSteps) case "object": if doClip { - err = errInvalidArgument("cannot clip with " + ltyp) + err = fmt.Errorf("invalid clip type '%s'", typ) return } var obj string @@ -198,7 +199,7 @@ func (s * Server) parseArea(ovs[]string, doClip bool) (vs []string, o geojson.Ob }) case "get": if doClip { - err = errInvalidArgument("cannot clip with " + ltyp) + err = fmt.Errorf("invalid clip type '%s'", typ) return } var key, id string @@ -224,7 +225,7 @@ func (s * Server) parseArea(ovs[]string, doClip bool) (vs []string, o geojson.Ob return } -func (s *Server) cmdTest (msg *Message) (res resp.Value, err error) { +func (s *Server) cmdTest(msg *Message) (res resp.Value, err error) { start := time.Now() vs := msg.Args[1:] @@ -252,7 +253,7 @@ func (s *Server) cmdTest (msg *Message) (res resp.Value, err error) { case "clip": vs = nvs if lTest != "intersects" { - err = errInvalidArgument("cannot clip with" + wtok) + err = errInvalidArgument(wtok) return } doClip = true @@ -282,7 +283,11 @@ func (s *Server) cmdTest (msg *Message) (res resp.Value, err error) { case JSON: var buf bytes.Buffer buf.WriteString(`{"ok":true`) - buf.WriteString(fmt.Sprintf(`,"result":%d`, result)) + if result != 0 { + buf.WriteString(`,"result":true`) + } else { + buf.WriteString(`,"result":false`) + } if clipped != nil { buf.WriteString(`,"object":` + clipped.JSON()) } diff --git a/tests/testcmd_test.go b/tests/testcmd_test.go index f2cef7af..5a2151a3 100644 --- a/tests/testcmd_test.go +++ b/tests/testcmd_test.go @@ -104,9 +104,10 @@ func testcmd_INTERSECTS_CLIP_test(mc *mockServer) error { return mc.DoBatch([][]interface{}{ {"SET", "mykey", "point1", "POINT", 37.7335, -122.4412}, {"OK"}, - {"TEST", "OBJECT", poly9, "INTERSECTS", "CLIP", "OBJECT", "{}"}, {"ERR invalid argument 'cannot clip with object'"}, - {"TEST", "OBJECT", poly9, "INTERSECTS", "CLIP", "CIRCLE", "1", "2", "3"}, {"ERR invalid argument 'cannot clip with circle'"}, - {"TEST", "OBJECT", poly9, "INTERSECTS", "CLIP", "GET", "mykey", "point1"}, {"ERR invalid argument 'cannot clip with get'"}, + {"TEST", "OBJECT", poly9, "INTERSECTS", "CLIP", "OBJECT", "{}"}, {"ERR invalid clip type 'OBJECT'"}, + {"TEST", "OBJECT", poly9, "INTERSECTS", "CLIP", "CIRCLE", "1", "2", "3"}, {"ERR invalid clip type 'CIRCLE'"}, + {"TEST", "OBJECT", poly9, "INTERSECTS", "CLIP", "GET", "mykey", "point1"}, {"ERR invalid clip type 'GET'"}, + {"TEST", "OBJECT", poly9, "WITHIN", "CLIP", "BOUNDS", 10, 10, 20, 20}, {"ERR invalid argument 'CLIP'"}, {"TEST", "OBJECT", poly9, "INTERSECTS", "CLIP", "BOUNDS", 37.732906137107, -122.44126439094543, 37.73421283683962, -122.43980526924135}, {"[1 " + poly9 + "]"}, {"TEST", "OBJECT", poly8, "INTERSECTS", "CLIP", "BOUNDS", 37.733, -122.4408378, 37.7341129, -122.44}, {"[1 " + poly8 + "]"}, From 30f903bd5150b539e14621948713cb616f285d25 Mon Sep 17 00:00:00 2001 From: tidwall Date: Tue, 12 Feb 2019 06:49:13 -0700 Subject: [PATCH 8/9] Require properties member for geojson features --- Gopkg.lock | 6 +- Gopkg.toml | 5 +- internal/collection/string.go | 5 ++ tests/json_test.go | 2 +- tests/testcmd_test.go | 2 +- tests/tests_test.go | 2 + vendor/github.com/tidwall/geojson/circle.go | 5 ++ .../github.com/tidwall/geojson/collection.go | 5 ++ vendor/github.com/tidwall/geojson/feature.go | 7 +- .../tidwall/geojson/feature_test.go | 68 +++++++++++++++---- .../tidwall/geojson/featurecollection.go | 7 +- .../tidwall/geojson/geometrycollection.go | 7 +- .../github.com/tidwall/geojson/linestring.go | 7 +- .../tidwall/geojson/multilinestring.go | 7 +- .../github.com/tidwall/geojson/multipoint.go | 7 +- .../tidwall/geojson/multipolygon.go | 7 +- vendor/github.com/tidwall/geojson/object.go | 14 +++- .../github.com/tidwall/geojson/object_test.go | 4 +- vendor/github.com/tidwall/geojson/point.go | 7 +- vendor/github.com/tidwall/geojson/polygon.go | 7 +- vendor/github.com/tidwall/geojson/rect.go | 5 ++ 21 files changed, 151 insertions(+), 35 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index b49f46e7..bab07235 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -242,8 +242,7 @@ version = "v1.0.2" [[projects]] - branch = "master" - digest = "1:0a6da4a08925415ab3326e140985ec628417903637461e9b68c07a03dfcc918d" + digest = "1:fc81262a6ad5aeec27e1bd15356f790e6b2d8fd14acb6bd5ff3f0f51bf67417f" name = "github.com/tidwall/geojson" packages = [ ".", @@ -251,7 +250,8 @@ "geometry", ] pruneopts = "" - revision = "d0a98d02b48e887b4de454d80c61ef5fb9312482" + revision = "6baab67ab6a9bac4abf153ab779c736254a37fd1" + version = "v1.1.0" [[projects]] digest = "1:3ddca2bd5496c6922a2a9e636530e178a43c2a534ea6634211acdc7d10222794" diff --git a/Gopkg.toml b/Gopkg.toml index cca1c7b3..fbf4b850 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -23,7 +23,6 @@ required = [ "github.com/tidwall/lotsa", "github.com/mmcloughlin/geohash", - "github.com/tidwall/geojson", "github.com/tidwall/evio" ] @@ -35,6 +34,10 @@ required = [ branch = "master" name = "github.com/tidwall/boxtree" +[[constraint]] + name = "github.com/tidwall/geojson" + version = "1.1.0" + [[constraint]] name = "github.com/Shopify/sarama" version = "1.13.0" diff --git a/internal/collection/string.go b/internal/collection/string.go index b7207957..ad7dc042 100644 --- a/internal/collection/string.go +++ b/internal/collection/string.go @@ -58,6 +58,11 @@ func (s String) JSON() string { return string(s.AppendJSON(nil)) } +// MarshalJSON ... +func (s String) MarshalJSON() ([]byte, error) { + return s.AppendJSON(nil), nil +} + // Within ... func (s String) Within(obj geojson.Object) bool { return false diff --git a/tests/json_test.go b/tests/json_test.go index 17ee8a68..e0d02c1d 100644 --- a/tests/json_test.go +++ b/tests/json_test.go @@ -30,7 +30,7 @@ func json_JSET_geojson_test(mc *mockServer) error { {"JSET", "mykey", "myid1", "coordinates.1", 44}, {"OK"}, {"JGET", "mykey", "myid1"}, {`{"type":"Point","coordinates":[-115,44]}`}, {"SET", "mykey", "myid1", "OBJECT", `{"type":"Feature","geometry":{"type":"Point","coordinates":[-115,44]}}`}, {"OK"}, - {"JGET", "mykey", "myid1"}, {`{"type":"Feature","geometry":{"type":"Point","coordinates":[-115,44]}}`}, + {"JGET", "mykey", "myid1"}, {`{"type":"Feature","geometry":{"type":"Point","coordinates":[-115,44]},"properties":{}}`}, {"JGET", "mykey", "myid1", "geometry.type"}, {"Point"}, {"JSET", "mykey", "myid1", "properties.tags.-1", "southwest"}, {"OK"}, {"JSET", "mykey", "myid1", "properties.tags.-1", "united states"}, {"OK"}, diff --git a/tests/testcmd_test.go b/tests/testcmd_test.go index 5a2151a3..d6d65231 100644 --- a/tests/testcmd_test.go +++ b/tests/testcmd_test.go @@ -111,7 +111,7 @@ func testcmd_INTERSECTS_CLIP_test(mc *mockServer) error { {"TEST", "OBJECT", poly9, "INTERSECTS", "CLIP", "BOUNDS", 37.732906137107, -122.44126439094543, 37.73421283683962, -122.43980526924135}, {"[1 " + poly9 + "]"}, {"TEST", "OBJECT", poly8, "INTERSECTS", "CLIP", "BOUNDS", 37.733, -122.4408378, 37.7341129, -122.44}, {"[1 " + poly8 + "]"}, - {"TEST", "OBJECT", multipoly5, "INTERSECTS", "CLIP", "BOUNDS", 37.73227823422744, -122.44120001792908, 37.73319038868677, -122.43955314159392}, {"[1 " + `{"type":"FeatureCollection","features":[{"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[-122.4408378,37.73319038868677],[-122.4408378,37.733],[-122.44,37.733],[-122.44,37.73319038868677],[-122.4408378,37.73319038868677]]]}},{"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[-122.44091033935547,37.73227823422744],[-122.43994474411011,37.73227823422744],[-122.43994474411011,37.73254976045042],[-122.44091033935547,37.73254976045042],[-122.44091033935547,37.73227823422744]]]}}]}` + "]"}, + {"TEST", "OBJECT", multipoly5, "INTERSECTS", "CLIP", "BOUNDS", 37.73227823422744, -122.44120001792908, 37.73319038868677, -122.43955314159392}, {"[1 " + `{"type":"FeatureCollection","features":[{"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[-122.4408378,37.73319038868677],[-122.4408378,37.733],[-122.44,37.733],[-122.44,37.73319038868677],[-122.4408378,37.73319038868677]]]},"properties":{}},{"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[-122.44091033935547,37.73227823422744],[-122.43994474411011,37.73227823422744],[-122.43994474411011,37.73254976045042],[-122.44091033935547,37.73254976045042],[-122.44091033935547,37.73227823422744]]]},"properties":{}}]}` + "]"}, {"TEST", "OBJECT", poly101, "INTERSECTS", "CLIP", "BOUNDS", 37.73315644825698, -122.44054287672043, 37.73349585185455, -122.44008690118788}, {"0"}, }) } diff --git a/tests/tests_test.go b/tests/tests_test.go index f342df1b..6624fa64 100644 --- a/tests/tests_test.go +++ b/tests/tests_test.go @@ -57,7 +57,9 @@ func runSubTest(t *testing.T, name string, mc *mockServer, test func(t *testing. } func runStep(t *testing.T, mc *mockServer, name string, step func(mc *mockServer) error) { + t.Helper() t.Run(name, func(t *testing.T) { + t.Helper() if err := func() error { // reset the current server mc.ResetConn() diff --git a/vendor/github.com/tidwall/geojson/circle.go b/vendor/github.com/tidwall/geojson/circle.go index a27dd8d1..127dfe24 100644 --- a/vendor/github.com/tidwall/geojson/circle.go +++ b/vendor/github.com/tidwall/geojson/circle.go @@ -53,6 +53,11 @@ func (g *Circle) JSON() string { return string(g.AppendJSON(nil)) } +// MarshalJSON ... +func (g *Circle) MarshalJSON() ([]byte, error) { + return g.AppendJSON(nil), nil +} + // String ... func (g *Circle) String() string { return string(g.AppendJSON(nil)) diff --git a/vendor/github.com/tidwall/geojson/collection.go b/vendor/github.com/tidwall/geojson/collection.go index 9ddd89e4..0edd0493 100644 --- a/vendor/github.com/tidwall/geojson/collection.go +++ b/vendor/github.com/tidwall/geojson/collection.go @@ -90,6 +90,11 @@ func (g *collection) JSON() string { return string(g.AppendJSON(nil)) } +// MarshalJSON ... +func (g *collection) MarshalJSON() ([]byte, error) { + return g.AppendJSON(nil), nil +} + // String ... func (g *collection) String() string { return string(g.AppendJSON(nil)) diff --git a/vendor/github.com/tidwall/geojson/feature.go b/vendor/github.com/tidwall/geojson/feature.go index 62b9b7ac..da5e405f 100644 --- a/vendor/github.com/tidwall/geojson/feature.go +++ b/vendor/github.com/tidwall/geojson/feature.go @@ -77,7 +77,7 @@ func (g *Feature) Members() string { func (g *Feature) AppendJSON(dst []byte) []byte { dst = append(dst, `{"type":"Feature","geometry":`...) dst = g.base.AppendJSON(dst) - dst = g.extra.appendJSONExtra(dst) + dst = g.extra.appendJSONExtra(dst, true) dst = append(dst, '}') return dst @@ -93,6 +93,11 @@ func (g *Feature) JSON() string { return string(g.AppendJSON(nil)) } +// MarshalJSON ... +func (g *Feature) MarshalJSON() ([]byte, error) { + return g.AppendJSON(nil), nil +} + // Spatial ... func (g *Feature) Spatial() Spatial { return g diff --git a/vendor/github.com/tidwall/geojson/feature_test.go b/vendor/github.com/tidwall/geojson/feature_test.go index 5ba83c42..f851b28b 100644 --- a/vendor/github.com/tidwall/geojson/feature_test.go +++ b/vendor/github.com/tidwall/geojson/feature_test.go @@ -1,28 +1,32 @@ package geojson -import "testing" +import ( + "testing" + + "github.com/tidwall/gjson" +) func TestFeatureParse(t *testing.T) { - p := expectJSON(t, `{"type":"Feature","geometry":{"type":"Point","coordinates":[1,2,3]}}`, nil) + p := expectJSON(t, `{"type":"Feature","geometry":{"type":"Point","coordinates":[1,2,3]},"properties":{}}`, nil) expect(t, p.Center() == P(1, 2)) expectJSON(t, `{"type":"Feature"}`, errGeometryMissing) expectJSON(t, `{"type":"Feature","geometry":null}`, errDataInvalid) - expectJSON(t, `{"type":"Feature","geometry":{"type":"Point","coordinates":[1,2,3]},"bbox":null}`, nil) - expectJSON(t, `{"type":"Feature","geometry":{"type":"Point","coordinates":[1,2]},"id":[4,true]}`, nil) + expectJSON(t, `{"type":"Feature","geometry":{"type":"Point","coordinates":[1,2,3]},"bbox":null,"properties":{}}`, nil) + expectJSON(t, `{"type":"Feature","geometry":{"type":"Point","coordinates":[1,2]},"id":[4,true],"properties":{}}`, nil) expectJSON(t, `{"type":"Feature","geometry":{"type":"Point","coordinates":[1,2]},"id":"15","properties":{"a":"b"}}`, nil) - expectJSON(t, `{"type":"Feature","geometry":{"type":"Point","coordinates":[1,2,3]},"bbox":[1,2,3,4]}`, nil) - expectJSON(t, `{"type":"Feature","geometry":{"type":"Point","coordinates":[1,2],"bbox":[1,2,3,4]},"id":[4,true]}`, nil) + expectJSON(t, `{"type":"Feature","geometry":{"type":"Point","coordinates":[1,2,3]},"bbox":[1,2,3,4],"properties":{}}`, nil) + expectJSON(t, `{"type":"Feature","geometry":{"type":"Point","coordinates":[1,2],"bbox":[1,2,3,4]},"id":[4,true],"properties":{}}`, nil) } func TestFeatureVarious(t *testing.T) { - var g = expectJSON(t, `{"type":"Feature","geometry":{"type":"Point","coordinates":[1,2,3]}}`, nil) - expect(t, string(g.AppendJSON(nil)) == `{"type":"Feature","geometry":{"type":"Point","coordinates":[1,2,3]}}`) + var g = expectJSON(t, `{"type":"Feature","geometry":{"type":"Point","coordinates":[1,2,3]},"properties":{}}`, nil) + expect(t, string(g.AppendJSON(nil)) == `{"type":"Feature","geometry":{"type":"Point","coordinates":[1,2,3]},"properties":{}}`) expect(t, g.Rect() == R(1, 2, 1, 2)) expect(t, g.Center() == P(1, 2)) expect(t, !g.Empty()) g = expectJSONOpts(t, - `{"type":"Feature","geometry":{"type":"Point","coordinates":[1,2,3]},"bbox":[1,2,3,4]}`, + `{"type":"Feature","geometry":{"type":"Point","coordinates":[1,2,3]},"bbox":[1,2,3,4],"properties":{}}`, nil, nil) expect(t, !g.Empty()) expect(t, g.Rect() == R(1, 2, 1, 2)) @@ -30,10 +34,44 @@ func TestFeatureVarious(t *testing.T) { } -// func TestFeaturePoly(t *testing.T) { -// p := expectJSON(t, `{"type":"Feature","geometry":{"type":"Point","coordinates":[1,2]}}`, nil) -// expect(t, p.Intersects(PO(1, 2))) -// expect(t, p.Contains(PO(1, 2))) -// expect(t, p.Within(PO(1, 2))) +func TestFeatureProperties(t *testing.T) { + obj, err := Parse(`{"type":"Feature","geometry":{"type":"Point","coordinates":[1,2]}}`, nil) + if err != nil { + t.Fatal(err) + } + json := obj.JSON() + if !gjson.Valid(json) { + t.Fatal("invalid json") + } + if !gjson.Get(json, "properties").Exists() { + t.Fatal("expected 'properties' member") + } -// } + obj, err = Parse(`{"type":"Feature","geometry":{"type":"Point","coordinates":[1,2]},"properties":true}`, nil) + if err != nil { + t.Fatal(err) + } + json = obj.JSON() + if !gjson.Valid(json) { + t.Fatal("invalid json") + } + if gjson.Get(json, "properties").Type != gjson.True { + t.Fatal("expected 'properties' member to be 'true'") + } + + obj, err = Parse(`{"type":"Feature","geometry":{"type":"Point","coordinates":[1,2]},"id":{}}`, nil) + if err != nil { + t.Fatal(err) + } + json = obj.JSON() + if !gjson.Valid(json) { + t.Fatal("invalid json") + } + if !gjson.Get(json, "properties").Exists() { + t.Fatal("expected 'properties' member") + } + if gjson.Get(json, "id").String() != "{}" { + t.Fatal("expected 'id' member") + } + +} diff --git a/vendor/github.com/tidwall/geojson/featurecollection.go b/vendor/github.com/tidwall/geojson/featurecollection.go index 87ce6a87..43e05872 100644 --- a/vendor/github.com/tidwall/geojson/featurecollection.go +++ b/vendor/github.com/tidwall/geojson/featurecollection.go @@ -28,7 +28,7 @@ func (g *FeatureCollection) AppendJSON(dst []byte) []byte { } dst = append(dst, ']') if g.extra != nil { - dst = g.extra.appendJSONExtra(dst) + dst = g.extra.appendJSONExtra(dst, false) } dst = append(dst, '}') strings.Index("", " ") @@ -45,6 +45,11 @@ func (g *FeatureCollection) JSON() string { return string(g.AppendJSON(nil)) } +// MarshalJSON ... +func (g *FeatureCollection) MarshalJSON() ([]byte, error) { + return g.AppendJSON(nil), nil +} + func parseJSONFeatureCollection( keys *parseKeys, opts *ParseOptions, ) (Object, error) { diff --git a/vendor/github.com/tidwall/geojson/geometrycollection.go b/vendor/github.com/tidwall/geojson/geometrycollection.go index 1d2cec49..2cb64045 100644 --- a/vendor/github.com/tidwall/geojson/geometrycollection.go +++ b/vendor/github.com/tidwall/geojson/geometrycollection.go @@ -28,7 +28,7 @@ func (g *GeometryCollection) AppendJSON(dst []byte) []byte { } dst = append(dst, ']') if g.extra != nil { - dst = g.extra.appendJSONExtra(dst) + dst = g.extra.appendJSONExtra(dst, false) } dst = append(dst, '}') strings.Index("", " ") @@ -45,6 +45,11 @@ func (g *GeometryCollection) JSON() string { return string(g.AppendJSON(nil)) } +// MarshalJSON ... +func (g *GeometryCollection) MarshalJSON() ([]byte, error) { + return g.AppendJSON(nil), nil +} + func parseJSONGeometryCollection( keys *parseKeys, opts *ParseOptions, ) (Object, error) { diff --git a/vendor/github.com/tidwall/geojson/linestring.go b/vendor/github.com/tidwall/geojson/linestring.go index a94101f6..3047bff9 100644 --- a/vendor/github.com/tidwall/geojson/linestring.go +++ b/vendor/github.com/tidwall/geojson/linestring.go @@ -46,7 +46,7 @@ func (g *LineString) AppendJSON(dst []byte) []byte { dst = append(dst, `{"type":"LineString","coordinates":`...) dst, _ = appendJSONSeries(dst, &g.base, g.extra, 0) if g.extra != nil { - dst = g.extra.appendJSONExtra(dst) + dst = g.extra.appendJSONExtra(dst, false) } dst = append(dst, '}') return dst @@ -62,6 +62,11 @@ func (g *LineString) JSON() string { return string(g.AppendJSON(nil)) } +// MarshalJSON ... +func (g *LineString) MarshalJSON() ([]byte, error) { + return g.AppendJSON(nil), nil +} + // Spatial ... func (g *LineString) Spatial() Spatial { return g diff --git a/vendor/github.com/tidwall/geojson/multilinestring.go b/vendor/github.com/tidwall/geojson/multilinestring.go index d780ca59..65506e27 100644 --- a/vendor/github.com/tidwall/geojson/multilinestring.go +++ b/vendor/github.com/tidwall/geojson/multilinestring.go @@ -30,7 +30,7 @@ func (g *MultiLineString) AppendJSON(dst []byte) []byte { } dst = append(dst, ']') if g.extra != nil { - dst = g.extra.appendJSONExtra(dst) + dst = g.extra.appendJSONExtra(dst, false) } dst = append(dst, '}') return dst @@ -58,6 +58,11 @@ func (g *MultiLineString) JSON() string { return string(g.AppendJSON(nil)) } +// MarshalJSON ... +func (g *MultiLineString) MarshalJSON() ([]byte, error) { + return g.AppendJSON(nil), nil +} + func parseJSONMultiLineString( keys *parseKeys, opts *ParseOptions, ) (Object, error) { diff --git a/vendor/github.com/tidwall/geojson/multipoint.go b/vendor/github.com/tidwall/geojson/multipoint.go index d0d299df..a0e2d72a 100644 --- a/vendor/github.com/tidwall/geojson/multipoint.go +++ b/vendor/github.com/tidwall/geojson/multipoint.go @@ -30,7 +30,7 @@ func (g *MultiPoint) AppendJSON(dst []byte) []byte { } dst = append(dst, ']') if g.extra != nil { - dst = g.extra.appendJSONExtra(dst) + dst = g.extra.appendJSONExtra(dst, false) } dst = append(dst, '}') return dst @@ -46,6 +46,11 @@ func (g *MultiPoint) JSON() string { return string(g.AppendJSON(nil)) } +// MarshalJSON ... +func (g *MultiPoint) MarshalJSON() ([]byte, error) { + return g.AppendJSON(nil), nil +} + func parseJSONMultiPoint(keys *parseKeys, opts *ParseOptions) (Object, error) { var g MultiPoint var err error diff --git a/vendor/github.com/tidwall/geojson/multipolygon.go b/vendor/github.com/tidwall/geojson/multipolygon.go index 023f689f..543b58eb 100644 --- a/vendor/github.com/tidwall/geojson/multipolygon.go +++ b/vendor/github.com/tidwall/geojson/multipolygon.go @@ -30,7 +30,7 @@ func (g *MultiPolygon) AppendJSON(dst []byte) []byte { } dst = append(dst, ']') if g.extra != nil { - dst = g.extra.appendJSONExtra(dst) + dst = g.extra.appendJSONExtra(dst, false) } dst = append(dst, '}') return dst @@ -57,6 +57,11 @@ func (g *MultiPolygon) JSON() string { return string(g.AppendJSON(nil)) } +// MarshalJSON ... +func (g *MultiPolygon) MarshalJSON() ([]byte, error) { + return g.AppendJSON(nil), nil +} + func parseJSONMultiPolygon( keys *parseKeys, opts *ParseOptions, ) (Object, error) { diff --git a/vendor/github.com/tidwall/geojson/object.go b/vendor/github.com/tidwall/geojson/object.go index 2b1c89c0..fe023f83 100644 --- a/vendor/github.com/tidwall/geojson/object.go +++ b/vendor/github.com/tidwall/geojson/object.go @@ -43,6 +43,7 @@ type Object interface { NumPoints() int ForEach(iter func(geom Object) bool) bool Spatial() Spatial + MarshalJSON() ([]byte, error) } var _ = []Object{ @@ -88,7 +89,7 @@ type ParseOptions struct { // IndexGeometryKind is the kind of index implementation. // Default is QuadTreeCompressed IndexGeometryKind geometry.IndexKind - RequireValid bool + RequireValid bool } // DefaultParseOptions ... @@ -96,7 +97,7 @@ var DefaultParseOptions = &ParseOptions{ IndexChildren: 64, IndexGeometry: 64, IndexGeometryKind: geometry.QuadTree, - RequireValid: false, + RequireValid: false, } // Parse a GeoJSON object @@ -244,10 +245,17 @@ func appendJSONPoint(dst []byte, point geometry.Point, ex *extra, idx int) []byt return dst } -func (ex *extra) appendJSONExtra(dst []byte) []byte { +func (ex *extra) appendJSONExtra(dst []byte, propertiesRequired bool) []byte { if ex != nil && ex.members != "" { dst = append(dst, ',') dst = append(dst, ex.members[1:len(ex.members)-1]...) + if propertiesRequired { + if !gjson.Get(ex.members, "properties").Exists() { + dst = append(dst, `,"properties":{}`...) + } + } + } else if propertiesRequired { + dst = append(dst, `,"properties":{}`...) } return dst diff --git a/vendor/github.com/tidwall/geojson/object_test.go b/vendor/github.com/tidwall/geojson/object_test.go index b73880af..355cffae 100644 --- a/vendor/github.com/tidwall/geojson/object_test.go +++ b/vendor/github.com/tidwall/geojson/object_test.go @@ -101,9 +101,9 @@ func expect(t testing.TB, what bool) { } if !what { if t == nil { - panic("exception failure") + panic("expectation failure") } else { - t.Fatal("expection failure") + t.Fatal("expectation failure") } } } diff --git a/vendor/github.com/tidwall/geojson/point.go b/vendor/github.com/tidwall/geojson/point.go index e18a4fe8..fa955404 100644 --- a/vendor/github.com/tidwall/geojson/point.go +++ b/vendor/github.com/tidwall/geojson/point.go @@ -63,7 +63,7 @@ func (g *Point) Base() geometry.Point { func (g *Point) AppendJSON(dst []byte) []byte { dst = append(dst, `{"type":"Point","coordinates":`...) dst = appendJSONPoint(dst, g.base, g.extra, 0) - dst = g.extra.appendJSONExtra(dst) + dst = g.extra.appendJSONExtra(dst, false) dst = append(dst, '}') return dst } @@ -73,6 +73,11 @@ func (g *Point) JSON() string { return string(g.AppendJSON(nil)) } +// MarshalJSON ... +func (g *Point) MarshalJSON() ([]byte, error) { + return g.AppendJSON(nil), nil +} + // String ... func (g *Point) String() string { return string(g.AppendJSON(nil)) diff --git a/vendor/github.com/tidwall/geojson/polygon.go b/vendor/github.com/tidwall/geojson/polygon.go index f235eb5f..d5ab34dd 100644 --- a/vendor/github.com/tidwall/geojson/polygon.go +++ b/vendor/github.com/tidwall/geojson/polygon.go @@ -52,7 +52,7 @@ func (g *Polygon) AppendJSON(dst []byte) []byte { } dst = append(dst, ']') if g.extra != nil { - dst = g.extra.appendJSONExtra(dst) + dst = g.extra.appendJSONExtra(dst, false) } dst = append(dst, '}') return dst @@ -63,6 +63,11 @@ func (g *Polygon) JSON() string { return string(g.AppendJSON(nil)) } +// MarshalJSON ... +func (g *Polygon) MarshalJSON() ([]byte, error) { + return g.AppendJSON(nil), nil +} + // String ... func (g *Polygon) String() string { return string(g.AppendJSON(nil)) diff --git a/vendor/github.com/tidwall/geojson/rect.go b/vendor/github.com/tidwall/geojson/rect.go index 4b2f42ad..19dd43d9 100644 --- a/vendor/github.com/tidwall/geojson/rect.go +++ b/vendor/github.com/tidwall/geojson/rect.go @@ -56,6 +56,11 @@ func (g *Rect) JSON() string { return string(g.AppendJSON(nil)) } +// MarshalJSON ... +func (g *Rect) MarshalJSON() ([]byte, error) { + return g.AppendJSON(nil), nil +} + // String ... func (g *Rect) String() string { return string(g.AppendJSON(nil)) From 99508c9af75cf6317c84e2bce795ae8af78cec8e Mon Sep 17 00:00:00 2001 From: tidwall Date: Tue, 12 Feb 2019 06:58:43 -0700 Subject: [PATCH 9/9] Allow slashes for MQTT topic, fixes #410 --- internal/endpoint/endpoint.go | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/internal/endpoint/endpoint.go b/internal/endpoint/endpoint.go index d612ccfb..195d0e92 100644 --- a/internal/endpoint/endpoint.go +++ b/internal/endpoint/endpoint.go @@ -404,10 +404,15 @@ func parseEndpoint(s string) (Endpoint, error) { // Parsing MQTT queue name if len(sp) > 1 { var err error - endpoint.MQTT.QueueName, err = url.QueryUnescape(sp[1]) - if err != nil { - return endpoint, errors.New("invalid MQTT topic name") + var parts []string + for _, part := range sp[1:] { + part, err = url.QueryUnescape(part) + if err != nil { + return endpoint, errors.New("invalid MQTT topic name") + } + parts = append(parts, part) } + endpoint.MQTT.QueueName = strings.Join(parts, "/") } // Parsing additional params