tile38/controller/search.go

289 lines
7.0 KiB
Go

package controller
import (
"bytes"
"io"
"strconv"
"strings"
"time"
"github.com/tidwall/tile38/controller/bing"
"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
}
var nearbyTypes = []string{"point"}
var withinOrIntersectsTypes = []string{"geo", "bounds", "hash", "tile", "quadkey", "get"}
func (c *Controller) cmdNearby(line string, w io.Writer) error {
start := time.Now()
wr := &bytes.Buffer{}
s, err := c.cmdSearchArgs("nearby", line, nearbyTypes)
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{}
s, err := c.cmdSearchArgs(cmd, line, withinOrIntersectsTypes)
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
}