mirror of https://github.com/tidwall/tile38.git
wip: MVT output
This commit is contained in:
parent
036017db4f
commit
f5efc40d48
1
go.mod
1
go.mod
|
@ -85,6 +85,7 @@ require (
|
||||||
github.com/tidwall/cities v0.1.0 // indirect
|
github.com/tidwall/cities v0.1.0 // indirect
|
||||||
github.com/tidwall/grect v0.1.4 // indirect
|
github.com/tidwall/grect v0.1.4 // indirect
|
||||||
github.com/tidwall/lotsa v1.0.2 // indirect
|
github.com/tidwall/lotsa v1.0.2 // indirect
|
||||||
|
github.com/tidwall/mvt v0.1.2 // indirect
|
||||||
github.com/tidwall/rtred v0.1.2 // indirect
|
github.com/tidwall/rtred v0.1.2 // indirect
|
||||||
github.com/tidwall/tinyqueue v0.1.1 // indirect
|
github.com/tidwall/tinyqueue v0.1.1 // indirect
|
||||||
github.com/xdg/stringprep v1.0.3 // indirect
|
github.com/xdg/stringprep v1.0.3 // indirect
|
||||||
|
|
2
go.sum
2
go.sum
|
@ -348,6 +348,8 @@ github.com/tidwall/lotsa v1.0.2 h1:dNVBH5MErdaQ/xd9s769R31/n2dXavsQ0Yf4TMEHHw8=
|
||||||
github.com/tidwall/lotsa v1.0.2/go.mod h1:X6NiU+4yHA3fE3Puvpnn1XMDrFZrE9JO2/w+UMuqgR8=
|
github.com/tidwall/lotsa v1.0.2/go.mod h1:X6NiU+4yHA3fE3Puvpnn1XMDrFZrE9JO2/w+UMuqgR8=
|
||||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||||
|
github.com/tidwall/mvt v0.1.2 h1:hRNN7ZybDmfksMEF/H61F7v65uBmG9uFDr+xTOu2Opw=
|
||||||
|
github.com/tidwall/mvt v0.1.2/go.mod h1:UDWI77bePzGClhFHsrPWM9SyzYr9NMZy/uB7BcHXymQ=
|
||||||
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
|
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
|
||||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||||
github.com/tidwall/redbench v0.1.0 h1:UZYUMhwMMObQRq5xU4SA3lmlJRztXzqtushDii+AmPo=
|
github.com/tidwall/redbench v0.1.0 h1:UZYUMhwMMObQRq5xU4SA3lmlJRztXzqtushDii+AmPo=
|
||||||
|
|
|
@ -145,7 +145,7 @@ func (s *Server) cmdSetHook(msg *Message) (
|
||||||
hook.ScanWriter, err = s.newScanWriter(
|
hook.ScanWriter, err = s.newScanWriter(
|
||||||
&wr, cmsg, args.key, args.output, args.precision, args.glob, false,
|
&wr, cmsg, args.key, args.output, args.precision, args.glob, false,
|
||||||
args.cursor, args.limit, args.wheres, args.whereins, args.whereevals,
|
args.cursor, args.limit, args.wheres, args.whereins, args.whereevals,
|
||||||
args.nofields)
|
args.nofields, args.mvt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
||||||
return NOMessage, d, err
|
return NOMessage, d, err
|
||||||
|
|
|
@ -107,7 +107,8 @@ func (s *Server) goLive(
|
||||||
s.mu.RLock()
|
s.mu.RLock()
|
||||||
sw, err = s.newScanWriter(
|
sw, err = s.newScanWriter(
|
||||||
&wr, msg, lfs.key, lfs.output, lfs.precision, lfs.glob, false,
|
&wr, msg, lfs.key, lfs.output, lfs.precision, lfs.glob, false,
|
||||||
lfs.cursor, lfs.limit, lfs.wheres, lfs.whereins, lfs.whereevals, lfs.nofields)
|
lfs.cursor, lfs.limit, lfs.wheres, lfs.whereins, lfs.whereevals,
|
||||||
|
lfs.nofields, lfs.mvt)
|
||||||
s.mu.RUnlock()
|
s.mu.RUnlock()
|
||||||
|
|
||||||
// everything below if for live SCAN, NEARBY, WITHIN, INTERSECTS
|
// everything below if for live SCAN, NEARBY, WITHIN, INTERSECTS
|
||||||
|
|
|
@ -48,7 +48,7 @@ func (s *Server) cmdScan(msg *Message) (res resp.Value, err error) {
|
||||||
sw, err := s.newScanWriter(
|
sw, err := s.newScanWriter(
|
||||||
wr, msg, args.key, args.output, args.precision, args.glob, false,
|
wr, msg, args.key, args.output, args.precision, args.glob, false,
|
||||||
args.cursor, args.limit, args.wheres, args.whereins, args.whereevals,
|
args.cursor, args.limit, args.wheres, args.whereins, args.whereevals,
|
||||||
args.nofields)
|
args.nofields, args.mvt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return NOMessage, err
|
return NOMessage, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
"math"
|
"math"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -9,6 +10,7 @@ import (
|
||||||
|
|
||||||
"github.com/mmcloughlin/geohash"
|
"github.com/mmcloughlin/geohash"
|
||||||
"github.com/tidwall/geojson"
|
"github.com/tidwall/geojson"
|
||||||
|
"github.com/tidwall/mvt"
|
||||||
"github.com/tidwall/resp"
|
"github.com/tidwall/resp"
|
||||||
"github.com/tidwall/tile38/internal/clip"
|
"github.com/tidwall/tile38/internal/clip"
|
||||||
"github.com/tidwall/tile38/internal/collection"
|
"github.com/tidwall/tile38/internal/collection"
|
||||||
|
@ -56,7 +58,9 @@ type scanWriter struct {
|
||||||
globSingle bool
|
globSingle bool
|
||||||
fullFields bool
|
fullFields bool
|
||||||
values []resp.Value
|
values []resp.Value
|
||||||
|
mvtObjs []geojson.Object
|
||||||
matchValues bool
|
matchValues bool
|
||||||
|
mvt bool
|
||||||
respOut resp.Value
|
respOut resp.Value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,7 +81,7 @@ func (s *Server) newScanWriter(
|
||||||
wr *bytes.Buffer, msg *Message, key string, output outputT,
|
wr *bytes.Buffer, msg *Message, key string, output outputT,
|
||||||
precision uint64, globPattern string, matchValues bool,
|
precision uint64, globPattern string, matchValues bool,
|
||||||
cursor, limit uint64, wheres []whereT, whereins []whereinT,
|
cursor, limit uint64, wheres []whereT, whereins []whereinT,
|
||||||
whereevals []whereevalT, nofields bool,
|
whereevals []whereevalT, nofields, mvt bool,
|
||||||
) (
|
) (
|
||||||
*scanWriter, error,
|
*scanWriter, error,
|
||||||
) {
|
) {
|
||||||
|
@ -97,6 +101,7 @@ func (s *Server) newScanWriter(
|
||||||
s: s,
|
s: s,
|
||||||
wr: wr,
|
wr: wr,
|
||||||
msg: msg,
|
msg: msg,
|
||||||
|
mvt: mvt,
|
||||||
limit: limit,
|
limit: limit,
|
||||||
cursor: cursor,
|
cursor: cursor,
|
||||||
output: output,
|
output: output,
|
||||||
|
@ -155,6 +160,9 @@ func (sw *scanWriter) hasFieldsOutput() bool {
|
||||||
func (sw *scanWriter) writeHead() {
|
func (sw *scanWriter) writeHead() {
|
||||||
sw.mu.Lock()
|
sw.mu.Lock()
|
||||||
defer sw.mu.Unlock()
|
defer sw.mu.Unlock()
|
||||||
|
if sw.mvt {
|
||||||
|
sw.wr.WriteString(`,"mvt":"`)
|
||||||
|
} else {
|
||||||
switch sw.msg.OutputType {
|
switch sw.msg.OutputType {
|
||||||
case JSON:
|
case JSON:
|
||||||
if len(sw.farr) > 0 && sw.hasFieldsOutput() {
|
if len(sw.farr) > 0 && sw.hasFieldsOutput() {
|
||||||
|
@ -184,6 +192,27 @@ func (sw *scanWriter) writeHead() {
|
||||||
case RESP:
|
case RESP:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sw *scanWriter) compileMVT() []byte {
|
||||||
|
var tile mvt.Tile
|
||||||
|
l := tile.AddLayer("default")
|
||||||
|
l.SetExtent(4096)
|
||||||
|
|
||||||
|
for _, g := range sw.mvtObjs {
|
||||||
|
_ = g
|
||||||
|
f := l.AddFeature(mvt.Polygon)
|
||||||
|
// f.MoveTo(128, 96)
|
||||||
|
// f.LineTo(148, 128)
|
||||||
|
// f.LineTo(108, 128)
|
||||||
|
// f.LineTo(128, 96)
|
||||||
|
f.ClosePath()
|
||||||
|
}
|
||||||
|
|
||||||
|
// println(sw.mvtObjs)
|
||||||
|
|
||||||
|
return tile.Render()
|
||||||
|
}
|
||||||
|
|
||||||
func (sw *scanWriter) writeFoot() {
|
func (sw *scanWriter) writeFoot() {
|
||||||
sw.mu.Lock()
|
sw.mu.Lock()
|
||||||
|
@ -192,13 +221,22 @@ func (sw *scanWriter) writeFoot() {
|
||||||
if !sw.hitLimit {
|
if !sw.hitLimit {
|
||||||
cursor = 0
|
cursor = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var mvtTile []byte
|
||||||
|
if sw.mvt {
|
||||||
|
mvtTile = sw.compileMVT()
|
||||||
|
}
|
||||||
switch sw.msg.OutputType {
|
switch sw.msg.OutputType {
|
||||||
case JSON:
|
case JSON:
|
||||||
|
if sw.mvt {
|
||||||
|
sw.wr.WriteString(base64.RawStdEncoding.EncodeToString(mvtTile))
|
||||||
|
sw.wr.WriteByte('"')
|
||||||
|
} else {
|
||||||
switch sw.output {
|
switch sw.output {
|
||||||
default:
|
default:
|
||||||
sw.wr.WriteByte(']')
|
sw.wr.WriteByte(']')
|
||||||
case outputCount:
|
case outputCount:
|
||||||
|
}
|
||||||
}
|
}
|
||||||
sw.wr.WriteString(`,"count":` + strconv.FormatUint(sw.count, 10))
|
sw.wr.WriteString(`,"count":` + strconv.FormatUint(sw.count, 10))
|
||||||
sw.wr.WriteString(`,"cursor":` + strconv.FormatUint(cursor, 10))
|
sw.wr.WriteString(`,"cursor":` + strconv.FormatUint(cursor, 10))
|
||||||
|
@ -206,9 +244,11 @@ func (sw *scanWriter) writeFoot() {
|
||||||
if sw.output == outputCount {
|
if sw.output == outputCount {
|
||||||
sw.respOut = resp.IntegerValue(int(sw.count))
|
sw.respOut = resp.IntegerValue(int(sw.count))
|
||||||
} else {
|
} else {
|
||||||
values := []resp.Value{
|
values := []resp.Value{resp.IntegerValue(int(cursor))}
|
||||||
resp.IntegerValue(int(cursor)),
|
if sw.mvt {
|
||||||
resp.ArrayValue(sw.values),
|
values = append(values, resp.BytesValue(mvtTile))
|
||||||
|
} else {
|
||||||
|
values = append(values, resp.ArrayValue(sw.values))
|
||||||
}
|
}
|
||||||
sw.respOut = resp.ArrayValue(values)
|
sw.respOut = resp.ArrayValue(values)
|
||||||
}
|
}
|
||||||
|
@ -384,6 +424,9 @@ func (sw *scanWriter) writeObject(opts ScanWriterParams) bool {
|
||||||
if opts.clip != nil {
|
if opts.clip != nil {
|
||||||
opts.o = clip.Clip(opts.o, opts.clip, &sw.s.geomIndexOpts)
|
opts.o = clip.Clip(opts.o, opts.clip, &sw.s.geomIndexOpts)
|
||||||
}
|
}
|
||||||
|
if sw.mvt {
|
||||||
|
sw.mvtObjs = append(sw.mvtObjs, opts.o)
|
||||||
|
} else {
|
||||||
switch sw.msg.OutputType {
|
switch sw.msg.OutputType {
|
||||||
case JSON:
|
case JSON:
|
||||||
var wr bytes.Buffer
|
var wr bytes.Buffer
|
||||||
|
@ -514,6 +557,7 @@ func (sw *scanWriter) writeObject(opts ScanWriterParams) bool {
|
||||||
sw.values = append(sw.values, resp.ArrayValue(vals))
|
sw.values = append(sw.values, resp.ArrayValue(vals))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
sw.numberItems++
|
sw.numberItems++
|
||||||
if sw.numberItems == sw.limit {
|
if sw.numberItems == sw.limit {
|
||||||
sw.hitLimit = true
|
sw.hitLimit = true
|
||||||
|
|
|
@ -131,7 +131,7 @@ func parseRectArea(ltyp string, vs []string) (nvs []string, rect *geojson.Rect,
|
||||||
Min: geometry.Point{X: minLon, Y: minLat},
|
Min: geometry.Point{X: minLon, Y: minLat},
|
||||||
Max: geometry.Point{X: maxLon, Y: maxLat},
|
Max: geometry.Point{X: maxLon, Y: maxLat},
|
||||||
})
|
})
|
||||||
case "tile":
|
case "tile", "mvt":
|
||||||
var sx, sy, sz string
|
var sx, sy, sz string
|
||||||
if vs, sx, ok = tokenval(vs); !ok || sx == "" {
|
if vs, sx, ok = tokenval(vs); !ok || sx == "" {
|
||||||
err = errInvalidNumberOfArguments
|
err = errInvalidNumberOfArguments
|
||||||
|
@ -188,6 +188,7 @@ func (s *Server) cmdSearchArgs(
|
||||||
err = errInvalidNumberOfArguments
|
err = errInvalidNumberOfArguments
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if lfs.searchScanBaseTokens.output == outputBounds {
|
if lfs.searchScanBaseTokens.output == outputBounds {
|
||||||
if cmd == "within" || cmd == "intersects" {
|
if cmd == "within" || cmd == "intersects" {
|
||||||
if _, err := strconv.ParseFloat(typ, 64); err == nil {
|
if _, err := strconv.ParseFloat(typ, 64); err == nil {
|
||||||
|
@ -208,6 +209,7 @@ func (s *Server) cmdSearchArgs(
|
||||||
err = errInvalidArgument(typ)
|
err = errInvalidArgument(typ)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
switch ltyp {
|
switch ltyp {
|
||||||
case "point":
|
case "point":
|
||||||
var slat, slon, smeters string
|
var slat, slon, smeters string
|
||||||
|
@ -352,11 +354,12 @@ func (s *Server) cmdSearchArgs(
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case "bounds", "hash", "tile", "quadkey":
|
case "bounds", "hash", "tile", "mvt", "quadkey":
|
||||||
vs, lfs.obj, err = parseRectArea(ltyp, vs)
|
vs, lfs.obj, err = parseRectArea(ltyp, vs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
lfs.mvt = ltyp == "mvt"
|
||||||
case "get":
|
case "get":
|
||||||
if lfs.clip {
|
if lfs.clip {
|
||||||
err = errInvalidArgument("cannot clip with get")
|
err = errInvalidArgument("cannot clip with get")
|
||||||
|
@ -463,6 +466,7 @@ var nearbyTypes = map[string]bool{
|
||||||
var withinOrIntersectsTypes = map[string]bool{
|
var withinOrIntersectsTypes = map[string]bool{
|
||||||
"geo": true, "bounds": true, "hash": true, "tile": true, "quadkey": true,
|
"geo": true, "bounds": true, "hash": true, "tile": true, "quadkey": true,
|
||||||
"get": true, "object": true, "circle": true, "point": true, "sector": true,
|
"get": true, "object": true, "circle": true, "point": true, "sector": true,
|
||||||
|
"mvt": true,
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) cmdNearby(msg *Message) (res resp.Value, err error) {
|
func (s *Server) cmdNearby(msg *Message) (res resp.Value, err error) {
|
||||||
|
@ -489,7 +493,8 @@ func (s *Server) cmdNearby(msg *Message) (res resp.Value, err error) {
|
||||||
}
|
}
|
||||||
sw, err := s.newScanWriter(
|
sw, err := s.newScanWriter(
|
||||||
wr, msg, sargs.key, sargs.output, sargs.precision, sargs.glob, false,
|
wr, msg, sargs.key, sargs.output, sargs.precision, sargs.glob, false,
|
||||||
sargs.cursor, sargs.limit, sargs.wheres, sargs.whereins, sargs.whereevals, sargs.nofields)
|
sargs.cursor, sargs.limit, sargs.wheres, sargs.whereins,
|
||||||
|
sargs.whereevals, sargs.nofields, sargs.mvt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return NOMessage, err
|
return NOMessage, err
|
||||||
}
|
}
|
||||||
|
@ -581,7 +586,8 @@ func (s *Server) cmdWithinOrIntersects(cmd string, msg *Message) (res resp.Value
|
||||||
}
|
}
|
||||||
sw, err := s.newScanWriter(
|
sw, err := s.newScanWriter(
|
||||||
wr, msg, sargs.key, sargs.output, sargs.precision, sargs.glob, false,
|
wr, msg, sargs.key, sargs.output, sargs.precision, sargs.glob, false,
|
||||||
sargs.cursor, sargs.limit, sargs.wheres, sargs.whereins, sargs.whereevals, sargs.nofields)
|
sargs.cursor, sargs.limit, sargs.wheres, sargs.whereins,
|
||||||
|
sargs.whereevals, sargs.nofields, sargs.mvt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return NOMessage, err
|
return NOMessage, err
|
||||||
}
|
}
|
||||||
|
@ -665,7 +671,8 @@ func (s *Server) cmdSearch(msg *Message) (res resp.Value, err error) {
|
||||||
}
|
}
|
||||||
sw, err := s.newScanWriter(
|
sw, err := s.newScanWriter(
|
||||||
wr, msg, sargs.key, sargs.output, sargs.precision, sargs.glob, true,
|
wr, msg, sargs.key, sargs.output, sargs.precision, sargs.glob, true,
|
||||||
sargs.cursor, sargs.limit, sargs.wheres, sargs.whereins, sargs.whereevals, sargs.nofields)
|
sargs.cursor, sargs.limit, sargs.wheres, sargs.whereins,
|
||||||
|
sargs.whereevals, sargs.nofields, sargs.mvt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return NOMessage, err
|
return NOMessage, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -213,6 +213,7 @@ type searchScanBaseTokens struct {
|
||||||
clip bool
|
clip bool
|
||||||
buffer float64
|
buffer float64
|
||||||
hasbuffer bool
|
hasbuffer bool
|
||||||
|
mvt bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) parseSearchScanBaseTokens(
|
func (s *Server) parseSearchScanBaseTokens(
|
||||||
|
|
Loading…
Reference in New Issue