2016-03-05 02:08:16 +03:00
|
|
|
package controller
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"errors"
|
|
|
|
"math"
|
|
|
|
"strconv"
|
|
|
|
|
2016-03-06 17:55:00 +03:00
|
|
|
"github.com/tidwall/tile38/controller/collection"
|
2016-03-05 02:08:16 +03:00
|
|
|
"github.com/tidwall/tile38/geojson"
|
|
|
|
)
|
|
|
|
|
|
|
|
const limitItems = 100
|
|
|
|
const capLimit = 100000
|
|
|
|
|
|
|
|
type outputT int
|
|
|
|
|
|
|
|
const (
|
|
|
|
outputUnknown outputT = iota
|
|
|
|
outputIDs
|
|
|
|
outputObjects
|
|
|
|
outputCount
|
|
|
|
outputPoints
|
|
|
|
outputHashes
|
|
|
|
outputBounds
|
|
|
|
)
|
|
|
|
|
|
|
|
type scanWriter struct {
|
|
|
|
wr *bytes.Buffer
|
|
|
|
col *collection.Collection
|
|
|
|
fmap map[string]int
|
|
|
|
farr []string
|
|
|
|
fvals []float64
|
|
|
|
output outputT
|
|
|
|
wheres []whereT
|
|
|
|
numberItems uint64
|
|
|
|
nofields bool
|
|
|
|
limit uint64
|
|
|
|
hitLimit bool
|
|
|
|
once bool
|
|
|
|
count uint64
|
|
|
|
precision uint64
|
|
|
|
glob string
|
|
|
|
globEverything bool
|
|
|
|
globSingle bool
|
|
|
|
fullFields bool
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Controller) newScanWriter(
|
|
|
|
wr *bytes.Buffer, key string, output outputT, precision uint64, glob string, limit uint64, wheres []whereT, nofields bool,
|
|
|
|
) (
|
|
|
|
*scanWriter, error,
|
|
|
|
) {
|
|
|
|
if limit == 0 {
|
|
|
|
limit = limitItems
|
|
|
|
} else if limit > capLimit {
|
|
|
|
limit = capLimit
|
|
|
|
}
|
|
|
|
switch output {
|
|
|
|
default:
|
|
|
|
return nil, errors.New("invalid output type")
|
|
|
|
case outputIDs, outputObjects, outputCount, outputBounds, outputPoints, outputHashes:
|
|
|
|
}
|
|
|
|
sw := &scanWriter{
|
|
|
|
wr: wr,
|
|
|
|
output: output,
|
|
|
|
wheres: wheres,
|
|
|
|
precision: precision,
|
|
|
|
nofields: nofields,
|
|
|
|
glob: glob,
|
|
|
|
limit: limit,
|
|
|
|
}
|
|
|
|
if glob == "*" || glob == "" {
|
|
|
|
sw.globEverything = true
|
|
|
|
} else {
|
|
|
|
if !globIsGlob(glob) {
|
|
|
|
sw.globSingle = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
sw.col = c.getCol(key)
|
|
|
|
if sw.col != nil {
|
|
|
|
sw.fmap = sw.col.FieldMap()
|
|
|
|
sw.farr = sw.col.FieldArr()
|
|
|
|
}
|
|
|
|
sw.fvals = make([]float64, len(sw.farr))
|
|
|
|
return sw, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (sw *scanWriter) hasFieldsOutput() bool {
|
|
|
|
switch sw.output {
|
|
|
|
default:
|
|
|
|
return false
|
|
|
|
case outputObjects, outputPoints, outputHashes, outputBounds:
|
|
|
|
return !sw.nofields
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (sw *scanWriter) writeHead() {
|
|
|
|
if len(sw.farr) > 0 && sw.hasFieldsOutput() {
|
|
|
|
sw.wr.WriteString(`,"fields":[`)
|
|
|
|
for i, field := range sw.farr {
|
|
|
|
if i > 0 {
|
|
|
|
sw.wr.WriteByte(',')
|
|
|
|
}
|
|
|
|
sw.wr.WriteString(jsonString(field))
|
|
|
|
}
|
|
|
|
sw.wr.WriteByte(']')
|
|
|
|
}
|
|
|
|
switch sw.output {
|
|
|
|
case outputIDs:
|
|
|
|
sw.wr.WriteString(`,"ids":[`)
|
|
|
|
case outputObjects:
|
|
|
|
sw.wr.WriteString(`,"objects":[`)
|
|
|
|
case outputPoints:
|
|
|
|
sw.wr.WriteString(`,"points":[`)
|
|
|
|
case outputBounds:
|
|
|
|
sw.wr.WriteString(`,"bounds":[`)
|
|
|
|
case outputHashes:
|
|
|
|
sw.wr.WriteString(`,"hashes":[`)
|
|
|
|
case outputCount:
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (sw *scanWriter) writeFoot(cursor uint64) {
|
|
|
|
if !sw.hitLimit {
|
|
|
|
cursor = 0
|
|
|
|
}
|
|
|
|
switch sw.output {
|
|
|
|
default:
|
|
|
|
sw.wr.WriteByte(']')
|
|
|
|
case outputCount:
|
|
|
|
|
|
|
|
}
|
|
|
|
sw.wr.WriteString(`,"count":` + strconv.FormatUint(sw.count, 10))
|
|
|
|
sw.wr.WriteString(`,"cursor":` + strconv.FormatUint(cursor, 10))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (sw *scanWriter) fieldMatch(fields []float64, o geojson.Object) ([]float64, bool) {
|
|
|
|
var z float64
|
|
|
|
var gotz bool
|
|
|
|
if !sw.hasFieldsOutput() || sw.fullFields {
|
|
|
|
for _, where := range sw.wheres {
|
|
|
|
if where.field == "z" {
|
|
|
|
if !gotz {
|
|
|
|
z = o.CalculatedPoint().Z
|
|
|
|
}
|
|
|
|
if !where.match(z) {
|
|
|
|
return sw.fvals, false
|
|
|
|
}
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
var value float64
|
|
|
|
idx, ok := sw.fmap[where.field]
|
|
|
|
if ok {
|
|
|
|
if len(fields) > idx {
|
|
|
|
value = fields[idx]
|
|
|
|
if math.IsNaN(value) {
|
|
|
|
value = 0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !where.match(value) {
|
|
|
|
return sw.fvals, false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
for idx := range sw.farr {
|
|
|
|
var value float64
|
|
|
|
if len(fields) > idx {
|
|
|
|
value = fields[idx]
|
|
|
|
if math.IsNaN(value) {
|
|
|
|
value = 0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
sw.fvals[idx] = value
|
|
|
|
}
|
|
|
|
for _, where := range sw.wheres {
|
|
|
|
if where.field == "z" {
|
|
|
|
if !gotz {
|
|
|
|
z = o.CalculatedPoint().Z
|
|
|
|
}
|
|
|
|
if !where.match(z) {
|
|
|
|
return sw.fvals, false
|
|
|
|
}
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
var value float64
|
|
|
|
idx, ok := sw.fmap[where.field]
|
|
|
|
if ok {
|
|
|
|
value = sw.fvals[idx]
|
|
|
|
}
|
|
|
|
if !where.match(value) {
|
|
|
|
return sw.fvals, false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return sw.fvals, true
|
|
|
|
}
|
|
|
|
|
|
|
|
func (sw *scanWriter) writeObject(id string, o geojson.Object, fields []float64) bool {
|
|
|
|
keepGoing := true
|
|
|
|
if !sw.globEverything {
|
|
|
|
if sw.globSingle {
|
|
|
|
if sw.glob != id {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
keepGoing = false // return current object and stop iterating
|
|
|
|
} else {
|
|
|
|
ok, _ := globMatch(sw.glob, id)
|
|
|
|
if !ok {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
nfields, ok := sw.fieldMatch(fields, o)
|
|
|
|
if !ok {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
sw.count++
|
|
|
|
if sw.output == outputCount {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
var wr bytes.Buffer
|
|
|
|
if sw.once {
|
|
|
|
wr.WriteByte(',')
|
|
|
|
} else {
|
|
|
|
sw.once = true
|
|
|
|
}
|
|
|
|
var jsfields string
|
|
|
|
|
|
|
|
if sw.hasFieldsOutput() {
|
|
|
|
if sw.fullFields {
|
|
|
|
if len(sw.fmap) > 0 {
|
|
|
|
jsfields = `,"fields":{`
|
|
|
|
var i int
|
|
|
|
for field, idx := range sw.fmap {
|
|
|
|
if len(fields) > idx {
|
|
|
|
if !math.IsNaN(fields[idx]) {
|
|
|
|
if i > 0 {
|
|
|
|
jsfields += `,`
|
|
|
|
}
|
|
|
|
jsfields += jsonString(field) + ":" + strconv.FormatFloat(fields[idx], 'f', -1, 64)
|
|
|
|
i++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
jsfields += `}`
|
|
|
|
}
|
|
|
|
} else if len(sw.farr) > 0 {
|
|
|
|
jsfields = `,"fields":[`
|
|
|
|
for i, field := range nfields {
|
|
|
|
if i > 0 {
|
|
|
|
jsfields += ","
|
|
|
|
}
|
|
|
|
jsfields += strconv.FormatFloat(field, 'f', -1, 64)
|
|
|
|
}
|
|
|
|
jsfields += `]`
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if sw.output == outputIDs {
|
|
|
|
wr.WriteString(jsonString(id))
|
|
|
|
} else {
|
|
|
|
wr.WriteString(`{"id":` + jsonString(id))
|
|
|
|
switch sw.output {
|
|
|
|
case outputObjects:
|
|
|
|
wr.WriteString(`,"object":` + o.JSON())
|
|
|
|
case outputPoints:
|
|
|
|
wr.WriteString(`,"point":` + o.CalculatedPoint().ExternalJSON())
|
|
|
|
case outputHashes:
|
|
|
|
p, err := o.Geohash(int(sw.precision))
|
|
|
|
if err != nil {
|
|
|
|
p = ""
|
|
|
|
}
|
|
|
|
wr.WriteString(`,"hash":"` + p + `"`)
|
|
|
|
case outputBounds:
|
|
|
|
wr.WriteString(`,"bounds":` + o.CalculatedBBox().ExternalJSON())
|
|
|
|
}
|
|
|
|
wr.WriteString(jsfields)
|
|
|
|
wr.WriteString(`}`)
|
|
|
|
}
|
|
|
|
sw.wr.Write(wr.Bytes())
|
|
|
|
sw.numberItems++
|
|
|
|
if sw.numberItems == sw.limit {
|
|
|
|
sw.hitLimit = true
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return keepGoing
|
|
|
|
}
|