2016-03-05 02:08:16 +03:00
|
|
|
package controller
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"io"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
2016-03-06 17:55:00 +03:00
|
|
|
"github.com/tidwall/tile38/controller/bing"
|
2016-03-05 02:08:16 +03:00
|
|
|
"github.com/tidwall/tile38/geojson"
|
|
|
|
"github.com/tidwall/tile38/geojson/geohash"
|
|
|
|
)
|
|
|
|
|
|
|
|
type liveFenceSwitches struct {
|
|
|
|
searchScanBaseTokens
|
|
|
|
lat, lon, meters float64
|
|
|
|
o geojson.Object
|
|
|
|
minLat, minLon float64
|
|
|
|
maxLat, maxLon float64
|
|
|
|
cmd string
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s liveFenceSwitches) Error() string {
|
|
|
|
return "going live"
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Controller) cmdSearchArgs(cmd, line string, types []string) (s liveFenceSwitches, err error) {
|
|
|
|
if line, s.searchScanBaseTokens, err = parseSearchScanBaseTokens(cmd, line); err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
var typ string
|
|
|
|
if line, typ = token(line); typ == "" {
|
|
|
|
err = errInvalidNumberOfArguments
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if s.searchScanBaseTokens.output == outputBounds {
|
|
|
|
if cmd == "within" || cmd == "intersects" {
|
|
|
|
if _, err := strconv.ParseFloat(typ, 64); err == nil {
|
|
|
|
// It's likely that the output was not specified, but rather the search bounds.
|
|
|
|
s.searchScanBaseTokens.output = defaultSearchOutput
|
|
|
|
line = typ + " " + line
|
|
|
|
typ = "BOUNDS"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
var found bool
|
|
|
|
for _, t := range types {
|
|
|
|
if strings.ToLower(typ) == t {
|
|
|
|
found = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !found {
|
|
|
|
err = errInvalidArgument(typ)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
switch strings.ToLower(typ) {
|
|
|
|
case "point":
|
|
|
|
var slat, slon, smeters string
|
|
|
|
if line, slat = token(line); slat == "" {
|
|
|
|
err = errInvalidNumberOfArguments
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if line, slon = token(line); slon == "" {
|
|
|
|
err = errInvalidNumberOfArguments
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if line, smeters = token(line); smeters == "" {
|
|
|
|
err = errInvalidNumberOfArguments
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if s.lat, err = strconv.ParseFloat(slat, 64); err != nil {
|
|
|
|
err = errInvalidArgument(slat)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if s.lon, err = strconv.ParseFloat(slon, 64); err != nil {
|
|
|
|
err = errInvalidArgument(slon)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if s.meters, err = strconv.ParseFloat(smeters, 64); err != nil {
|
|
|
|
err = errInvalidArgument(smeters)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
case "object":
|
|
|
|
if line == "" {
|
|
|
|
err = errInvalidNumberOfArguments
|
|
|
|
return
|
|
|
|
}
|
|
|
|
s.o, err = geojson.ObjectJSON(line)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
line = "" // since we read the remaining bytes
|
|
|
|
case "bounds":
|
|
|
|
var sminLat, sminLon, smaxlat, smaxlon string
|
|
|
|
if line, sminLat = token(line); sminLat == "" {
|
|
|
|
err = errInvalidNumberOfArguments
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if line, sminLon = token(line); sminLon == "" {
|
|
|
|
err = errInvalidNumberOfArguments
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if line, smaxlat = token(line); smaxlat == "" {
|
|
|
|
err = errInvalidNumberOfArguments
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if line, smaxlon = token(line); smaxlon == "" {
|
|
|
|
err = errInvalidNumberOfArguments
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if s.minLat, err = strconv.ParseFloat(sminLat, 64); err != nil {
|
|
|
|
err = errInvalidArgument(sminLat)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if s.minLon, err = strconv.ParseFloat(sminLon, 64); err != nil {
|
|
|
|
err = errInvalidArgument(sminLon)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if s.maxLat, err = strconv.ParseFloat(smaxlat, 64); err != nil {
|
|
|
|
err = errInvalidArgument(smaxlat)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if s.maxLon, err = strconv.ParseFloat(smaxlon, 64); err != nil {
|
|
|
|
err = errInvalidArgument(smaxlon)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
case "hash":
|
|
|
|
var hash string
|
|
|
|
if line, hash = token(line); hash == "" {
|
|
|
|
err = errInvalidNumberOfArguments
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if s.minLat, s.minLon, s.maxLat, s.maxLon, err = geohash.Bounds(hash); err != nil {
|
|
|
|
err = errInvalidArgument(hash)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
case "quadkey":
|
|
|
|
var key string
|
|
|
|
if line, key = token(line); key == "" {
|
|
|
|
err = errInvalidNumberOfArguments
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if s.minLat, s.minLon, s.maxLat, s.maxLon, err = bing.QuadKeyToBounds(key); err != nil {
|
|
|
|
err = errInvalidArgument(key)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
case "tile":
|
|
|
|
var sx, sy, sz string
|
|
|
|
if line, sx = token(line); sx == "" {
|
|
|
|
err = errInvalidNumberOfArguments
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if line, sy = token(line); sy == "" {
|
|
|
|
err = errInvalidNumberOfArguments
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if line, sz = token(line); 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
|
|
|
|
}
|
|
|
|
s.minLat, s.minLon, s.maxLat, s.maxLon = bing.TileXYToBounds(x, y, z)
|
|
|
|
case "get":
|
|
|
|
var key, id string
|
|
|
|
if line, key = token(line); key == "" {
|
|
|
|
err = errInvalidNumberOfArguments
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if line, id = token(line); id == "" {
|
|
|
|
err = errInvalidNumberOfArguments
|
|
|
|
return
|
|
|
|
}
|
|
|
|
col := c.getCol(key)
|
|
|
|
if col == nil {
|
|
|
|
err = errKeyNotFound
|
|
|
|
return
|
|
|
|
}
|
|
|
|
o, _, ok := col.Get(id)
|
|
|
|
if !ok {
|
|
|
|
err = errIDNotFound
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if o.IsBBoxDefined() {
|
|
|
|
bbox := o.CalculatedBBox()
|
|
|
|
s.minLat = bbox.Min.Y
|
|
|
|
s.minLon = bbox.Min.X
|
|
|
|
s.maxLat = bbox.Max.Y
|
|
|
|
s.maxLon = bbox.Max.X
|
|
|
|
} else {
|
|
|
|
s.o = o
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if line != "" {
|
|
|
|
err = errInvalidNumberOfArguments
|
|
|
|
return
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-03-19 17:16:19 +03:00
|
|
|
var nearbyTypes = []string{"point"}
|
|
|
|
var withinOrIntersectsTypes = []string{"geo", "bounds", "hash", "tile", "quadkey", "get"}
|
|
|
|
|
2016-03-05 02:08:16 +03:00
|
|
|
func (c *Controller) cmdNearby(line string, w io.Writer) error {
|
|
|
|
start := time.Now()
|
|
|
|
wr := &bytes.Buffer{}
|
2016-03-19 17:16:19 +03:00
|
|
|
s, err := c.cmdSearchArgs("nearby", line, nearbyTypes)
|
2016-03-05 02:08:16 +03:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
s.cmd = "nearby"
|
|
|
|
if s.fence {
|
|
|
|
return s
|
|
|
|
}
|
|
|
|
sw, err := c.newScanWriter(wr, s.key, s.output, s.precision, s.glob, s.limit, s.wheres, s.nofields)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
wr.WriteString(`{"ok":true`)
|
|
|
|
sw.writeHead()
|
|
|
|
if sw.col != nil {
|
|
|
|
s.cursor = sw.col.Nearby(s.cursor, s.sparse, s.lat, s.lon, s.meters, func(id string, o geojson.Object, fields []float64) bool {
|
|
|
|
return sw.writeObject(id, o, fields)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
sw.writeFoot(s.cursor)
|
|
|
|
wr.WriteString(`,"elapsed":"` + time.Now().Sub(start).String() + "\"}")
|
|
|
|
w.Write(wr.Bytes())
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Controller) cmdWithin(line string, w io.Writer) error {
|
|
|
|
return c.cmdWithinOrIntersects("within", line, w)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Controller) cmdIntersects(line string, w io.Writer) error {
|
|
|
|
return c.cmdWithinOrIntersects("intersects", line, w)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Controller) cmdWithinOrIntersects(cmd string, line string, w io.Writer) error {
|
|
|
|
start := time.Now()
|
|
|
|
wr := &bytes.Buffer{}
|
2016-03-19 17:16:19 +03:00
|
|
|
s, err := c.cmdSearchArgs(cmd, line, withinOrIntersectsTypes)
|
2016-03-05 02:08:16 +03:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
s.cmd = cmd
|
|
|
|
if s.fence {
|
|
|
|
return s
|
|
|
|
}
|
|
|
|
sw, err := c.newScanWriter(wr, s.key, s.output, s.precision, s.glob, s.limit, s.wheres, s.nofields)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
wr.WriteString(`{"ok":true`)
|
|
|
|
sw.writeHead()
|
|
|
|
if cmd == "within" {
|
|
|
|
s.cursor = sw.col.Within(s.cursor, s.sparse, s.o, s.minLat, s.minLon, s.maxLat, s.maxLon,
|
|
|
|
func(id string, o geojson.Object, fields []float64) bool {
|
|
|
|
return sw.writeObject(id, o, fields)
|
|
|
|
},
|
|
|
|
)
|
|
|
|
} else if cmd == "intersects" {
|
|
|
|
s.cursor = sw.col.Intersects(s.cursor, s.sparse, s.o, s.minLat, s.minLon, s.maxLat, s.maxLon,
|
|
|
|
func(id string, o geojson.Object, fields []float64) bool {
|
|
|
|
return sw.writeObject(id, o, fields)
|
|
|
|
},
|
|
|
|
)
|
|
|
|
}
|
|
|
|
sw.writeFoot(s.cursor)
|
|
|
|
wr.WriteString(`,"elapsed":"` + time.Now().Sub(start).String() + "\"}")
|
|
|
|
w.Write(wr.Bytes())
|
|
|
|
return nil
|
|
|
|
}
|