mirror of https://github.com/tidwall/tile38.git
Add clipby subcommand to INTERSECTS/WITHIN
This commit is contained in:
parent
f02dee3db2
commit
34cb2affdc
|
@ -14,6 +14,7 @@ import (
|
|||
"github.com/tidwall/geojson/geometry"
|
||||
"github.com/tidwall/resp"
|
||||
"github.com/tidwall/tile38/internal/bing"
|
||||
"github.com/tidwall/tile38/internal/clip"
|
||||
"github.com/tidwall/tile38/internal/deadline"
|
||||
"github.com/tidwall/tile38/internal/glob"
|
||||
)
|
||||
|
@ -57,6 +58,119 @@ func (s liveFenceSwitches) usingLua() bool {
|
|||
return len(s.whereevals) > 0
|
||||
}
|
||||
|
||||
func parseRectArea(ltyp string, vs []string) (nvs []string, rect *geojson.Rect, err error) {
|
||||
|
||||
var ok bool
|
||||
|
||||
switch ltyp {
|
||||
default:
|
||||
err = errNotRectangle
|
||||
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
|
||||
}
|
||||
rect = 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)
|
||||
rect = 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
|
||||
}
|
||||
rect = 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)
|
||||
rect = geojson.NewRect(geometry.Rect{
|
||||
Min: geometry.Point{X: minLon, Y: minLat},
|
||||
Max: geometry.Point{X: maxLon, Y: maxLat},
|
||||
})
|
||||
}
|
||||
nvs = vs
|
||||
return
|
||||
}
|
||||
|
||||
func (server *Server) cmdSearchArgs(
|
||||
fromFenceCmd bool, cmd string, vs []string, types []string,
|
||||
) (s liveFenceSwitches, err error) {
|
||||
|
@ -170,106 +284,11 @@ func (server *Server) cmdSearchArgs(
|
|||
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
|
||||
}
|
||||
s.obj = 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)
|
||||
s.obj = 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)
|
||||
case "bounds", "hash", "tile", "quadkey":
|
||||
vs, s.obj, err = parseRectArea(ltyp, vs)
|
||||
if err != nil {
|
||||
err = errInvalidArgument(key)
|
||||
return
|
||||
}
|
||||
s.obj = 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)
|
||||
s.obj = geojson.NewRect(geometry.Rect{
|
||||
Min: geometry.Point{X: minLon, Y: minLat},
|
||||
Max: geometry.Point{X: maxLon, Y: maxLat},
|
||||
})
|
||||
case "get":
|
||||
if s.clip {
|
||||
err = errInvalidArgument("cannot clip with get")
|
||||
|
@ -326,9 +345,38 @@ func (server *Server) cmdSearchArgs(
|
|||
s.roam.scan = scan
|
||||
}
|
||||
}
|
||||
if len(vs) != 0 {
|
||||
err = errInvalidNumberOfArguments
|
||||
return
|
||||
|
||||
var clip_rect *geojson.Rect
|
||||
var tok, ltok string
|
||||
for len(vs) > 0 {
|
||||
if vs, tok, ok = tokenval(vs); !ok || tok == "" {
|
||||
err = errInvalidNumberOfArguments
|
||||
return
|
||||
}
|
||||
if strings.ToLower(tok) != "clipby" {
|
||||
err = errInvalidNumberOfArguments
|
||||
return
|
||||
}
|
||||
if vs, tok, ok = tokenval(vs); !ok || tok == "" {
|
||||
err = errInvalidNumberOfArguments
|
||||
return
|
||||
}
|
||||
ltok = strings.ToLower(tok)
|
||||
switch ltok {
|
||||
case "bounds", "hash", "tile", "quadkey":
|
||||
vs, clip_rect, err = parseRectArea(ltok, vs)
|
||||
if err == errNotRectangle {
|
||||
err = errInvalidArgument("cannot clipby " + ltok)
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.obj = clip.Clip(s.obj, clip_rect, &server.geomIndexOpts)
|
||||
default:
|
||||
err = errInvalidArgument("cannot clipby " + ltok)
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ var errIDNotFound = errors.New("id not found")
|
|||
var errIDAlreadyExists = errors.New("id already exists")
|
||||
var errPathNotFound = errors.New("path not found")
|
||||
var errKeyHasHooksSet = errors.New("key has hooks set")
|
||||
var errNotRectangle = errors.New("not a rectangle")
|
||||
|
||||
func errInvalidArgument(arg string) error {
|
||||
return fmt.Errorf("invalid argument '%s'", arg)
|
||||
|
|
|
@ -13,8 +13,10 @@ func subTestSearch(t *testing.T, mc *mockServer) {
|
|||
runStep(t, mc, "INTERSECTS_CIRCLE", keys_INTERSECTS_CIRCLE_test)
|
||||
runStep(t, mc, "WITHIN", keys_WITHIN_test)
|
||||
runStep(t, mc, "WITHIN_CURSOR", keys_WITHIN_CURSOR_test)
|
||||
runStep(t, mc, "WITHIN_CLIPBY", keys_WITHIN_CLIPBY_test)
|
||||
runStep(t, mc, "INTERSECTS", keys_INTERSECTS_test)
|
||||
runStep(t, mc, "INTERSECTS_CURSOR", keys_INTERSECTS_CURSOR_test)
|
||||
runStep(t, mc, "INTERSECTS_CLIPBY", keys_INTERSECTS_CLIPBY_test)
|
||||
runStep(t, mc, "SCAN_CURSOR", keys_SCAN_CURSOR_test)
|
||||
runStep(t, mc, "SEARCH_CURSOR", keys_SEARCH_CURSOR_test)
|
||||
runStep(t, mc, "MATCH", keys_MATCH_test)
|
||||
|
@ -142,6 +144,50 @@ func keys_WITHIN_CURSOR_test(mc *mockServer) error {
|
|||
})
|
||||
}
|
||||
|
||||
func keys_WITHIN_CLIPBY_test(mc *mockServer) error {
|
||||
jagged := `{
|
||||
"type":"Polygon",
|
||||
"coordinates":[[
|
||||
[-122.47781753540039,37.74655746554895],
|
||||
[-122.48777389526366,37.7355619376922],
|
||||
[-122.4707794189453,37.73271097867418],
|
||||
[-122.46528625488281,37.735969208590504],
|
||||
[-122.45189666748047,37.73922729512254],
|
||||
[-122.4565315246582,37.75008654795525],
|
||||
[-122.46683120727538,37.75307256315459],
|
||||
[-122.47781753540039,37.74655746554895]
|
||||
]]
|
||||
}`
|
||||
|
||||
return mc.DoBatch([][]interface{}{
|
||||
{"SET", "mykey", "point1", "FIELD", "foo", 1, "POINT", 37.73963454585715, -122.4810791015625}, {"OK"},
|
||||
{"SET", "mykey", "point2", "FIELD", "foo", 2, "POINT", 37.75130811419222, -122.47438430786133}, {"OK"},
|
||||
{"SET", "mykey", "point3", "FIELD", "foo", 1, "POINT", 37.74816932695052, -122.47713088989258}, {"OK"},
|
||||
{"SET", "mykey", "point4", "FIELD", "foo", 2, "POINT", 37.74503040657439, -122.47571468353271}, {"OK"},
|
||||
{"SET", "other", "jagged", "OBJECT", jagged}, {"OK"},
|
||||
|
||||
{"WITHIN", "mykey", "IDS", "GET", "other", "jagged"}, {"[0 [point1 point4]]"},
|
||||
{"WITHIN", "mykey", "IDS", "BOUNDS",
|
||||
37.737734023260884, -122.47816085815431, 37.74886496155229, -122.45464324951172,
|
||||
}, {"[0 [point3 point4]]"},
|
||||
{"WITHIN", "mykey", "IDS", "GET", "other", "jagged", "CLIPBY", "BOUNDS",
|
||||
37.737734023260884, -122.47816085815431, 37.74886496155229, -122.45464324951172,
|
||||
}, {"[0 [point4]]"},
|
||||
{"WITHIN", "mykey", "IDS", "BOUNDS",
|
||||
37.74411415606583, -122.48034954071045, 37.7536833241461, -122.47163772583008,
|
||||
}, {"[0 [point2 point3 point4]]"},
|
||||
{"WITHIN", "mykey", "IDS", "GET", "other", "jagged", "CLIPBY", "BOUNDS",
|
||||
37.74411415606583, -122.48034954071045, 37.7536833241461, -122.47163772583008,
|
||||
}, {"[0 [point4]]"},
|
||||
{"WITHIN", "mykey", "IDS", "GET", "other", "jagged",
|
||||
"CLIPBY", "BOUNDS",
|
||||
37.74411415606583, -122.48034954071045, 37.7536833241461, -122.47163772583008,
|
||||
"CLIPBY", "BOUNDS",
|
||||
37.737734023260884, -122.47816085815431, 37.74886496155229, -122.45464324951172,
|
||||
}, {"[0 [point4]]"},
|
||||
})
|
||||
}
|
||||
|
||||
func keys_INTERSECTS_test(mc *mockServer) error {
|
||||
return mc.DoBatch([][]interface{}{
|
||||
{"SET", "mykey", "point1", "POINT", 37.7335, -122.4412}, {"OK"},
|
||||
|
@ -192,6 +238,50 @@ func keys_INTERSECTS_test(mc *mockServer) error {
|
|||
})
|
||||
}
|
||||
|
||||
func keys_INTERSECTS_CLIPBY_test(mc *mockServer) error {
|
||||
jagged := `{
|
||||
"type":"Polygon",
|
||||
"coordinates":[[
|
||||
[-122.47781753540039,37.74655746554895],
|
||||
[-122.48777389526366,37.7355619376922],
|
||||
[-122.4707794189453,37.73271097867418],
|
||||
[-122.46528625488281,37.735969208590504],
|
||||
[-122.45189666748047,37.73922729512254],
|
||||
[-122.4565315246582,37.75008654795525],
|
||||
[-122.46683120727538,37.75307256315459],
|
||||
[-122.47781753540039,37.74655746554895]
|
||||
]]
|
||||
}`
|
||||
|
||||
return mc.DoBatch([][]interface{}{
|
||||
{"SET", "mykey", "point1", "FIELD", "foo", 1, "POINT", 37.73963454585715, -122.4810791015625}, {"OK"},
|
||||
{"SET", "mykey", "point2", "FIELD", "foo", 2, "POINT", 37.75130811419222, -122.47438430786133}, {"OK"},
|
||||
{"SET", "mykey", "point3", "FIELD", "foo", 1, "POINT", 37.74816932695052, -122.47713088989258}, {"OK"},
|
||||
{"SET", "mykey", "point4", "FIELD", "foo", 2, "POINT", 37.74503040657439, -122.47571468353271}, {"OK"},
|
||||
{"SET", "other", "jagged", "OBJECT", jagged}, {"OK"},
|
||||
|
||||
{"INTERSECTS", "mykey", "IDS", "GET", "other", "jagged"}, {"[0 [point1 point4]]"},
|
||||
{"INTERSECTS", "mykey", "IDS", "BOUNDS",
|
||||
37.737734023260884, -122.47816085815431, 37.74886496155229, -122.45464324951172,
|
||||
}, {"[0 [point3 point4]]"},
|
||||
{"INTERSECTS", "mykey", "IDS", "GET", "other", "jagged", "CLIPBY", "BOUNDS",
|
||||
37.737734023260884, -122.47816085815431, 37.74886496155229, -122.45464324951172,
|
||||
}, {"[0 [point4]]"},
|
||||
{"INTERSECTS", "mykey", "IDS", "BOUNDS",
|
||||
37.74411415606583, -122.48034954071045, 37.7536833241461, -122.47163772583008,
|
||||
}, {"[0 [point2 point3 point4]]"},
|
||||
{"INTERSECTS", "mykey", "IDS", "GET", "other", "jagged", "CLIPBY", "BOUNDS",
|
||||
37.74411415606583, -122.48034954071045, 37.7536833241461, -122.47163772583008,
|
||||
}, {"[0 [point4]]"},
|
||||
{"INTERSECTS", "mykey", "IDS", "GET", "other", "jagged",
|
||||
"CLIPBY", "BOUNDS",
|
||||
37.74411415606583, -122.48034954071045, 37.7536833241461, -122.47163772583008,
|
||||
"CLIPBY", "BOUNDS",
|
||||
37.737734023260884, -122.47816085815431, 37.74886496155229, -122.45464324951172,
|
||||
}, {"[0 [point4]]"},
|
||||
})
|
||||
}
|
||||
|
||||
func keys_INTERSECTS_CURSOR_test(mc *mockServer) error {
|
||||
testArea := `{
|
||||
"type": "Polygon",
|
||||
|
|
Loading…
Reference in New Issue