mirror of https://github.com/tidwall/tile38.git
Merge net and circle optz
This commit is contained in:
commit
766e0c941e
|
@ -243,7 +243,7 @@
|
|||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:145703130ac1de36086ab350337777161f9c1d791e81a73659ac1f569e15b5e5"
|
||||
digest = "1:3307384a763736cbcfa625076939fe9a240e5f5c9d6ace507fa4fd1f4f6944d6"
|
||||
name = "github.com/tidwall/geojson"
|
||||
packages = [
|
||||
".",
|
||||
|
@ -251,7 +251,7 @@
|
|||
"geometry",
|
||||
]
|
||||
pruneopts = ""
|
||||
revision = "dbcb73c57c65ff784ce2ccaad3f062c9787d6f81"
|
||||
revision = "553da6f08f84f544b5482743fe73c3989facc578"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:3ddca2bd5496c6922a2a9e636530e178a43c2a534ea6634211acdc7d10222794"
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -8,10 +8,13 @@ import (
|
|||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/tidwall/redbench"
|
||||
"github.com/tidwall/redcon"
|
||||
"github.com/tidwall/tile38/cmd/tile38-benchmark/az"
|
||||
"github.com/tidwall/tile38/core"
|
||||
)
|
||||
|
||||
|
@ -24,7 +27,8 @@ var (
|
|||
pipeline = 1
|
||||
csv = false
|
||||
json = false
|
||||
tests = "PING,SET,GET,INTERSECTS,WITHIN,NEARBY,EVAL"
|
||||
allTests = "PING,SET,GET,INTERSECTS,WITHIN,NEARBY,EVAL"
|
||||
tests = allTests
|
||||
redis = false
|
||||
)
|
||||
|
||||
|
@ -179,7 +183,34 @@ func main() {
|
|||
}
|
||||
opts := fillOpts()
|
||||
addr = fmt.Sprintf("%s:%d", hostname, port)
|
||||
|
||||
testsArr := strings.Split(allTests, ",")
|
||||
var subtract bool
|
||||
var add bool
|
||||
for _, test := range strings.Split(tests, ",") {
|
||||
if strings.HasPrefix(test, "-") {
|
||||
if add {
|
||||
os.Stderr.Write([]byte("test flag cannot mix add and subtract\n"))
|
||||
os.Exit(1)
|
||||
}
|
||||
subtract = true
|
||||
for i := range testsArr {
|
||||
if strings.ToLower(testsArr[i]) == strings.ToLower(test[1:]) {
|
||||
testsArr = append(testsArr[:i], testsArr[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
} else if subtract {
|
||||
add = true
|
||||
os.Stderr.Write([]byte("test flag cannot mix add and subtract\n"))
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
if !subtract {
|
||||
testsArr = strings.Split(tests, ",")
|
||||
}
|
||||
|
||||
for _, test := range testsArr {
|
||||
switch strings.ToUpper(strings.TrimSpace(test)) {
|
||||
case "PING":
|
||||
redbench.Bench("PING", addr, opts, prepFn,
|
||||
|
@ -280,8 +311,9 @@ func main() {
|
|||
)
|
||||
}
|
||||
case "INTERSECTS",
|
||||
"INTERSECTS-RECT", "INTERSECTS-RECT-1000", "INTERSECTS-RECT-10000", "INTERSECTS-RECT-100000",
|
||||
"INTERSECTS-CIRCLE", "INTERSECTS-CIRCLE-1000", "INTERSECTS-CIRCLE-10000", "INTERSECTS-CIRCLE-100000":
|
||||
"INTERSECTS-BOUNDS", "INTERSECTS-BOUNDS-1000", "INTERSECTS-BOUNDS-10000", "INTERSECTS-BOUNDS-100000",
|
||||
"INTERSECTS-CIRCLE", "INTERSECTS-CIRCLE-1000", "INTERSECTS-CIRCLE-10000", "INTERSECTS-CIRCLE-100000",
|
||||
"INTERSECTS-AZ":
|
||||
if redis {
|
||||
break
|
||||
}
|
||||
|
@ -368,6 +400,51 @@ func main() {
|
|||
)
|
||||
}
|
||||
|
||||
switch strings.ToUpper(strings.TrimSpace(test)) {
|
||||
case "INTERSECTS", "INTERSECTS-AZ":
|
||||
var mu sync.Mutex
|
||||
var loaded bool
|
||||
redbench.Bench("INTERSECTS (intersects-az limit 5)", addr, opts, func(conn net.Conn) bool {
|
||||
func() {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
if loaded {
|
||||
return
|
||||
}
|
||||
loaded = true
|
||||
p := make([]byte, 0xFF)
|
||||
conn.Write([]byte("GET keys:bench:geo az point\r\n"))
|
||||
n, err := conn.Read(p)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if string(p[:n]) != "$-1\r\n" {
|
||||
return
|
||||
}
|
||||
args := []string{"SET", "key:bench:geo", "az", "object", az.JSON}
|
||||
out := redcon.AppendArray(nil, len(args))
|
||||
for _, arg := range args {
|
||||
out = redcon.AppendBulkString(out, arg)
|
||||
}
|
||||
conn.Write(out)
|
||||
n, err = conn.Read(p)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if string(p[:n]) != "+OK\r\n" {
|
||||
panic("expected OK")
|
||||
}
|
||||
}()
|
||||
return prepFn(conn)
|
||||
},
|
||||
func(buf []byte) []byte {
|
||||
args := []string{"INTERSECTS", "key:bench", "LIMIT", "5",
|
||||
"COUNT", "GET", "key:bench:geo", "az"}
|
||||
return redbench.AppendCommand(buf, args...)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
case "WITHIN",
|
||||
"WITHIN-RECT", "WITHIN-RECT-1000", "WITHIN-RECT-10000", "WITHIN-RECT-100000",
|
||||
"WITHIN-CIRCLE", "WITHIN-CIRCLE-1000", "WITHIN-CIRCLE-10000", "WITHIN-CIRCLE-100000":
|
||||
|
@ -544,9 +621,7 @@ func main() {
|
|||
)
|
||||
}
|
||||
case "EVAL":
|
||||
if redis {
|
||||
break
|
||||
}
|
||||
if !redis {
|
||||
var i int64
|
||||
getScript := "return tile38.call('GET', KEYS[1], ARGV[1], 'point')"
|
||||
get4Script :=
|
||||
|
@ -563,6 +638,7 @@ func main() {
|
|||
fmt.Println("GET FOUR SCRIPT: " + get4Script)
|
||||
fmt.Println("SET SCRIPT: " + setScript)
|
||||
}
|
||||
|
||||
redbench.Bench("EVAL (set point)", addr, opts, prepFn,
|
||||
func(buf []byte) []byte {
|
||||
i := atomic.AddInt64(&i, 1)
|
||||
|
@ -590,22 +666,19 @@ func main() {
|
|||
redbench.Bench("EVALRO (get point)", addr, opts, prepFn,
|
||||
func(buf []byte) []byte {
|
||||
i := atomic.AddInt64(&i, 1)
|
||||
args := []string{"EVALRO", getScript, "1", "key:bench", "id:" + strconv.FormatInt(i, 10)}
|
||||
return redbench.AppendCommand(buf, args...)
|
||||
return redbench.AppendCommand(buf, "EVALRO", getScript, "1", "key:bench", "id:"+strconv.FormatInt(i, 10))
|
||||
},
|
||||
)
|
||||
redbench.Bench("EVALRO (get 4 points)", addr, opts, prepFn,
|
||||
func(buf []byte) []byte {
|
||||
i := atomic.AddInt64(&i, 1)
|
||||
args := []string{
|
||||
"EVALRO", get4Script, "1",
|
||||
return redbench.AppendCommand(buf, "EVALRO", get4Script, "1",
|
||||
"key:bench",
|
||||
"id:" + strconv.FormatInt(i, 10),
|
||||
"id:" + strconv.FormatInt(i+1, 10),
|
||||
"id:" + strconv.FormatInt(i+2, 10),
|
||||
"id:" + strconv.FormatInt(i+3, 10),
|
||||
}
|
||||
return redbench.AppendCommand(buf, args...)
|
||||
"id:"+strconv.FormatInt(i, 10),
|
||||
"id:"+strconv.FormatInt(i+1, 10),
|
||||
"id:"+strconv.FormatInt(i+2, 10),
|
||||
"id:"+strconv.FormatInt(i+3, 10),
|
||||
)
|
||||
},
|
||||
)
|
||||
redbench.Bench("EVALNA (get point)", addr, opts, prepFn,
|
||||
|
@ -616,6 +689,7 @@ func main() {
|
|||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const earthRadius = 6371e3
|
||||
|
|
|
@ -80,6 +80,7 @@ Advanced Options:
|
|||
--http-transport yes/no : HTTP transport (default: yes)
|
||||
--protected-mode yes/no : protected mode (default: yes)
|
||||
--threads num : number of network threads (default: num cores)
|
||||
--evio yes/no : use the evio package (default: no)
|
||||
|
||||
Developer Options:
|
||||
--dev : enable developer mode
|
||||
|
@ -146,10 +147,10 @@ Developer Options:
|
|||
if i < len(os.Args) {
|
||||
switch strings.ToLower(os.Args[i]) {
|
||||
case "no":
|
||||
core.ProtectedMode = "no"
|
||||
core.ProtectedMode = false
|
||||
continue
|
||||
case "yes":
|
||||
core.ProtectedMode = "yes"
|
||||
core.ProtectedMode = true
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
@ -163,10 +164,10 @@ Developer Options:
|
|||
if i < len(os.Args) {
|
||||
switch strings.ToLower(os.Args[i]) {
|
||||
case "no":
|
||||
core.AppendOnly = "no"
|
||||
core.AppendOnly = false
|
||||
continue
|
||||
case "yes":
|
||||
core.AppendOnly = "yes"
|
||||
core.AppendOnly = true
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
@ -213,6 +214,20 @@ Developer Options:
|
|||
}
|
||||
fmt.Fprintf(os.Stderr, "http-transport must be 'yes' or 'no'\n")
|
||||
os.Exit(1)
|
||||
case "--evio", "-evio":
|
||||
i++
|
||||
if i < len(os.Args) {
|
||||
switch strings.ToLower(os.Args[i]) {
|
||||
case "no":
|
||||
core.Evio = false
|
||||
continue
|
||||
case "yes":
|
||||
core.Evio = true
|
||||
continue
|
||||
}
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "evio must be 'yes' or 'no'\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
nargs = append(nargs, os.Args[i])
|
||||
}
|
||||
|
|
|
@ -7,10 +7,10 @@ var DevMode = false
|
|||
var ShowDebugMessages = false
|
||||
|
||||
// ProtectedMode forces Tile38 to default in protected mode.
|
||||
var ProtectedMode = "yes"
|
||||
var ProtectedMode = true
|
||||
|
||||
// AppendOnly allows for disabling the appendonly file.
|
||||
var AppendOnly = "yes"
|
||||
var AppendOnly = true
|
||||
|
||||
// AppendFileName allows for custom appendonly file path
|
||||
var AppendFileName string
|
||||
|
@ -20,3 +20,6 @@ var QueueFileName string
|
|||
|
||||
// NumThreads is the number of network threads to use.
|
||||
var NumThreads int
|
||||
|
||||
// Evio set the networking to use the evio package.
|
||||
var Evio = false
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package core
|
||||
|
||||
// Build variables
|
||||
var (
|
||||
Version = "0.0.0" // Placeholder for the version
|
||||
BuildTime = "" // Placeholder for the build time
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"github.com/tidwall/boxtree/d2"
|
||||
"github.com/tidwall/btree"
|
||||
"github.com/tidwall/geojson"
|
||||
"github.com/tidwall/geojson/geo"
|
||||
"github.com/tidwall/geojson/geometry"
|
||||
"github.com/tidwall/tile38/internal/ds"
|
||||
)
|
||||
|
@ -673,6 +674,30 @@ func (c *Collection) Nearby(
|
|||
cursor Cursor,
|
||||
iter func(id string, obj geojson.Object, fields []float64) bool,
|
||||
) bool {
|
||||
// First look to see if there's at least one candidate in the circle's
|
||||
// outer rectangle. This is a fast-fail operation.
|
||||
if circle, ok := target.(*geojson.Circle); ok {
|
||||
meters := circle.Meters()
|
||||
if meters > 0 {
|
||||
center := circle.Center()
|
||||
minLat, minLon, maxLat, maxLon :=
|
||||
geo.RectFromCenter(center.Y, center.X, meters)
|
||||
var exists bool
|
||||
c.index.Search(
|
||||
[]float64{minLon, minLat},
|
||||
[]float64{maxLon, maxLat},
|
||||
func(_, _ []float64, itemv interface{}) bool {
|
||||
exists = true
|
||||
return false
|
||||
},
|
||||
)
|
||||
if !exists {
|
||||
// no candidates
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
// do the kNN operation
|
||||
alive := true
|
||||
center := target.Center()
|
||||
var count uint64
|
||||
|
|
|
@ -236,7 +236,7 @@ func Serve(host string, port int, dir string, http bool) error {
|
|||
if err := server.migrateAOF(); err != nil {
|
||||
return err
|
||||
}
|
||||
if core.AppendOnly == "yes" {
|
||||
if core.AppendOnly == true {
|
||||
f, err := os.OpenFile(core.AppendFileName, os.O_CREATE|os.O_RDWR, 0600)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -271,11 +271,14 @@ func Serve(host string, port int, dir string, http bool) error {
|
|||
}()
|
||||
|
||||
// Start the network server
|
||||
if core.Evio {
|
||||
return server.evioServe()
|
||||
}
|
||||
return server.netServe()
|
||||
}
|
||||
|
||||
func (server *Server) isProtected() bool {
|
||||
if core.ProtectedMode == "no" {
|
||||
if core.ProtectedMode == false {
|
||||
// --protected-mode no
|
||||
return false
|
||||
}
|
||||
|
@ -485,6 +488,192 @@ func (server *Server) evioServe() error {
|
|||
return evio.Serve(events, fmt.Sprintf("%s:%d", server.host, server.port))
|
||||
}
|
||||
|
||||
func (server *Server) netServe() error {
|
||||
ln, err := net.Listen("tcp", fmt.Sprintf("%s:%d", server.host, server.port))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer ln.Close()
|
||||
log.Infof("Ready to accept connections at %s", ln.Addr())
|
||||
var clientID int64
|
||||
for {
|
||||
conn, err := ln.Accept()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
go func(conn net.Conn) {
|
||||
// open connection
|
||||
// create the client
|
||||
client := new(Client)
|
||||
client.id = int(atomic.AddInt64(&clientID, 1))
|
||||
client.opened = time.Now()
|
||||
client.remoteAddr = conn.RemoteAddr().String()
|
||||
|
||||
// add client to server map
|
||||
server.connsmu.Lock()
|
||||
server.conns[client.id] = client
|
||||
server.connsmu.Unlock()
|
||||
server.statsTotalConns.add(1)
|
||||
|
||||
// set the client keep-alive, if needed
|
||||
if server.config.keepAlive() > 0 {
|
||||
if conn, ok := conn.(*net.TCPConn); ok {
|
||||
conn.SetKeepAlive(true)
|
||||
conn.SetKeepAlivePeriod(
|
||||
time.Duration(server.config.keepAlive()) * time.Second,
|
||||
)
|
||||
}
|
||||
}
|
||||
log.Debugf("Opened connection: %s", client.remoteAddr)
|
||||
|
||||
defer func() {
|
||||
// close connection
|
||||
// delete from server map
|
||||
server.connsmu.Lock()
|
||||
delete(server.conns, client.id)
|
||||
server.connsmu.Unlock()
|
||||
log.Debugf("Closed connection: %s", client.remoteAddr)
|
||||
conn.Close()
|
||||
}()
|
||||
|
||||
// check if the connection is protected
|
||||
if !strings.HasPrefix(client.remoteAddr, "127.0.0.1:") &&
|
||||
!strings.HasPrefix(client.remoteAddr, "[::1]:") {
|
||||
if server.isProtected() {
|
||||
// This is a protected server. Only loopback is allowed.
|
||||
conn.Write(deniedMessage)
|
||||
return // close connection
|
||||
}
|
||||
}
|
||||
packet := make([]byte, 0xFFFF)
|
||||
for {
|
||||
var close bool
|
||||
n, err := conn.Read(packet)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
in := packet[:n]
|
||||
|
||||
// read the payload packet from the client input stream.
|
||||
packet := client.in.Begin(in)
|
||||
|
||||
// load the pipeline reader
|
||||
pr := &client.pr
|
||||
rdbuf := bytes.NewBuffer(packet)
|
||||
pr.rd = rdbuf
|
||||
pr.wr = client
|
||||
|
||||
msgs, err := pr.ReadMessages()
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return // close connection
|
||||
}
|
||||
for _, msg := range msgs {
|
||||
// Just closing connection if we have deprecated HTTP or WS connection,
|
||||
// And --http-transport = false
|
||||
if !server.http && (msg.ConnType == WebSocket ||
|
||||
msg.ConnType == HTTP) {
|
||||
close = true // close connection
|
||||
break
|
||||
}
|
||||
if msg != nil && msg.Command() != "" {
|
||||
if client.outputType != Null {
|
||||
msg.OutputType = client.outputType
|
||||
}
|
||||
if msg.Command() == "quit" {
|
||||
if msg.OutputType == RESP {
|
||||
io.WriteString(client, "+OK\r\n")
|
||||
}
|
||||
close = true // close connection
|
||||
break
|
||||
}
|
||||
|
||||
// increment last used
|
||||
client.mu.Lock()
|
||||
client.last = time.Now()
|
||||
client.mu.Unlock()
|
||||
|
||||
// update total command count
|
||||
server.statsTotalCommands.add(1)
|
||||
|
||||
// handle the command
|
||||
err := server.handleInputCommand(client, msg)
|
||||
if err != nil {
|
||||
if err.Error() == goingLive {
|
||||
client.goLiveErr = err
|
||||
client.goLiveMsg = msg
|
||||
// detach
|
||||
var rwc io.ReadWriteCloser = conn
|
||||
client.conn = rwc
|
||||
if len(client.out) > 0 {
|
||||
client.conn.Write(client.out)
|
||||
client.out = nil
|
||||
}
|
||||
client.in = evio.InputStream{}
|
||||
client.pr.rd = rwc
|
||||
client.pr.wr = rwc
|
||||
log.Debugf("Detached connection: %s", client.remoteAddr)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
err := server.goLive(
|
||||
client.goLiveErr,
|
||||
&liveConn{conn.RemoteAddr(), rwc},
|
||||
&client.pr,
|
||||
client.goLiveMsg,
|
||||
client.goLiveMsg.ConnType == WebSocket,
|
||||
)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
}()
|
||||
wg.Wait()
|
||||
return // close connection
|
||||
}
|
||||
log.Error(err)
|
||||
return // close connection, NOW
|
||||
}
|
||||
|
||||
client.outputType = msg.OutputType
|
||||
} else {
|
||||
client.Write([]byte("HTTP/1.1 500 Bad Request\r\nConnection: close\r\n\r\n"))
|
||||
break
|
||||
}
|
||||
if msg.ConnType == HTTP || msg.ConnType == WebSocket {
|
||||
close = true // close connection
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
packet = packet[len(packet)-rdbuf.Len():]
|
||||
client.in.End(packet)
|
||||
|
||||
// write to client
|
||||
if len(client.out) > 0 {
|
||||
if atomic.LoadInt32(&server.aofdirty) != 0 {
|
||||
func() {
|
||||
// prewrite
|
||||
server.mu.Lock()
|
||||
defer server.mu.Unlock()
|
||||
server.flushAOF()
|
||||
}()
|
||||
atomic.StoreInt32(&server.aofdirty, 0)
|
||||
}
|
||||
conn.Write(client.out)
|
||||
client.out = nil
|
||||
|
||||
}
|
||||
if close {
|
||||
break
|
||||
}
|
||||
}
|
||||
}(conn)
|
||||
}
|
||||
}
|
||||
|
||||
type liveConn struct {
|
||||
remoteAddr net.Addr
|
||||
rwc io.ReadWriteCloser
|
||||
|
@ -672,6 +861,7 @@ func (server *Server) handleInputCommand(client *Client, msg *Message) error {
|
|||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Ping. Just send back the response. No need to put through the pipeline.
|
||||
if msg.Command() == "ping" || msg.Command() == "echo" {
|
||||
switch msg.OutputType {
|
||||
|
@ -689,6 +879,7 @@ func (server *Server) handleInputCommand(client *Client, msg *Message) error {
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
writeErr := func(errMsg string) error {
|
||||
switch msg.OutputType {
|
||||
case JSON:
|
||||
|
@ -732,6 +923,7 @@ func (server *Server) handleInputCommand(client *Client, msg *Message) error {
|
|||
return writeErr("invalid password")
|
||||
}
|
||||
}
|
||||
|
||||
// choose the locking strategy
|
||||
switch msg.Command() {
|
||||
default:
|
||||
|
@ -765,6 +957,7 @@ func (server *Server) handleInputCommand(client *Client, msg *Message) error {
|
|||
"chans", "search", "ttl", "bounds", "server", "info", "type", "jget",
|
||||
"evalro", "evalrosha":
|
||||
// read operations
|
||||
|
||||
server.mu.RLock()
|
||||
defer server.mu.RUnlock()
|
||||
if server.config.followHost() != "" && !server.fcuponce {
|
||||
|
@ -803,7 +996,6 @@ func (server *Server) handleInputCommand(client *Client, msg *Message) error {
|
|||
}
|
||||
|
||||
res, d, err := server.command(msg, client)
|
||||
|
||||
if res.Type() == resp.Error {
|
||||
return writeErr(res.String())
|
||||
}
|
||||
|
@ -822,7 +1014,6 @@ func (server *Server) handleInputCommand(client *Client, msg *Message) error {
|
|||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if !isRespValueEmptyString(res) {
|
||||
var resStr string
|
||||
resStr, err := serializeOutput(res)
|
||||
|
@ -1094,6 +1285,7 @@ const (
|
|||
|
||||
// Message is a resp message
|
||||
type Message struct {
|
||||
_command string
|
||||
Args []string
|
||||
ConnType Type
|
||||
OutputType Type
|
||||
|
@ -1102,7 +1294,10 @@ type Message struct {
|
|||
|
||||
// Command returns the first argument as a lowercase string
|
||||
func (msg *Message) Command() string {
|
||||
return strings.ToLower(msg.Args[0])
|
||||
if msg._command == "" {
|
||||
msg._command = strings.ToLower(msg.Args[0])
|
||||
}
|
||||
return msg._command
|
||||
}
|
||||
|
||||
// PipelineReader ...
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package geojson
|
||||
|
||||
import (
|
||||
"math"
|
||||
"strconv"
|
||||
|
||||
"github.com/tidwall/geojson/geo"
|
||||
|
@ -9,7 +10,7 @@ import (
|
|||
|
||||
// Circle ...
|
||||
type Circle struct {
|
||||
Object
|
||||
object Object
|
||||
center geometry.Point
|
||||
meters float64
|
||||
haversine float64
|
||||
|
@ -27,25 +28,8 @@ func NewCircle(center geometry.Point, meters float64, steps int) *Circle {
|
|||
g.center = center
|
||||
g.meters = meters
|
||||
g.steps = steps
|
||||
if meters <= 0 {
|
||||
g.Object = NewPoint(center)
|
||||
} else {
|
||||
if meters > 0 {
|
||||
meters = geo.NormalizeDistance(meters)
|
||||
var points []geometry.Point
|
||||
step := 360.0 / float64(steps)
|
||||
i := 0
|
||||
for deg := 360.0; deg > 0; deg -= step {
|
||||
lat, lon := geo.DestinationPoint(center.Y, center.X, meters, deg)
|
||||
points = append(points, geometry.Point{X: lon, Y: lat})
|
||||
i++
|
||||
}
|
||||
// TODO: account for the pole and antimerdian. In most cases only a
|
||||
// polygon is needed, but when the circle bounds passes the 90/180
|
||||
// lines, we need to create a multipolygon
|
||||
points = append(points, points[0])
|
||||
g.Object = NewPolygon(
|
||||
geometry.NewPoly(points, nil, geometry.DefaultIndexOptions),
|
||||
)
|
||||
g.haversine = geo.DistanceToHaversine(meters)
|
||||
}
|
||||
return g
|
||||
|
@ -128,7 +112,7 @@ func (g *Circle) Contains(obj Object) bool {
|
|||
return true
|
||||
default:
|
||||
// No simple cases, so using polygon approximation.
|
||||
return g.Object.Contains(other)
|
||||
return g.getObject().Contains(other)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -185,6 +169,81 @@ func (g *Circle) Intersects(obj Object) bool {
|
|||
return false
|
||||
default:
|
||||
// No simple cases, so using polygon approximation.
|
||||
return g.Object.Intersects(obj)
|
||||
return g.getObject().Intersects(obj)
|
||||
}
|
||||
}
|
||||
|
||||
// Empty ...
|
||||
func (g *Circle) Empty() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// ForEach ...
|
||||
func (g *Circle) ForEach(iter func(geom Object) bool) bool {
|
||||
return iter(g)
|
||||
}
|
||||
|
||||
// NumPoints ...
|
||||
func (g *Circle) NumPoints() int {
|
||||
// should this be g.steps?
|
||||
return 1
|
||||
}
|
||||
|
||||
// Distance ...
|
||||
func (g *Circle) Distance(other Object) float64 {
|
||||
return g.getObject().Distance(other)
|
||||
}
|
||||
|
||||
// Rect ...
|
||||
func (g *Circle) Rect() geometry.Rect {
|
||||
return g.getObject().Rect()
|
||||
}
|
||||
|
||||
// Spatial ...
|
||||
func (g *Circle) Spatial() Spatial {
|
||||
return g.getObject().Spatial()
|
||||
}
|
||||
|
||||
func (g *Circle) getObject() Object {
|
||||
if g.object != nil {
|
||||
return g.object
|
||||
}
|
||||
return makeCircleObject(g.center, g.meters, g.steps)
|
||||
}
|
||||
|
||||
func makeCircleObject(center geometry.Point, meters float64, steps int) Object {
|
||||
if meters <= 0 {
|
||||
return NewPoint(center)
|
||||
}
|
||||
meters = geo.NormalizeDistance(meters)
|
||||
points := make([]geometry.Point, 0, steps+1)
|
||||
|
||||
// calc the four corners
|
||||
maxY, _ := geo.DestinationPoint(center.Y, center.X, meters, 0)
|
||||
_, maxX := geo.DestinationPoint(center.Y, center.X, meters, 90)
|
||||
minY, _ := geo.DestinationPoint(center.Y, center.X, meters, 180)
|
||||
_, minX := geo.DestinationPoint(center.Y, center.X, meters, 270)
|
||||
|
||||
// TODO: detect of pole and antimeridian crossing and generate a
|
||||
// valid multigeometry
|
||||
|
||||
// use the half width of the lat and lon
|
||||
lons := (maxX - minX) / 2
|
||||
lats := (maxY - minY) / 2
|
||||
|
||||
// generate the
|
||||
for th := 0.0; th <= 360.0; th += 360.0 / float64(steps) {
|
||||
radians := (math.Pi / 180) * th
|
||||
x := center.X + lats*math.Cos(radians)
|
||||
y := center.Y + lons*math.Sin(radians)
|
||||
points = append(points, geometry.Point{X: x, Y: y})
|
||||
}
|
||||
// add last connecting point, make a total of steps+1
|
||||
points = append(points, points[0])
|
||||
|
||||
return NewPolygon(
|
||||
geometry.NewPoly(points, nil, &geometry.IndexOptions{
|
||||
Kind: geometry.None,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -45,7 +45,7 @@ func DistanceToHaversine(meters float64) float64 {
|
|||
return sin * sin
|
||||
}
|
||||
|
||||
// DistanceFromHaversine...
|
||||
// DistanceFromHaversine ...
|
||||
func DistanceFromHaversine(haversine float64) float64 {
|
||||
return earthRadius * 2 * math.Asin(math.Sqrt(haversine))
|
||||
}
|
||||
|
@ -88,3 +88,84 @@ func BearingTo(latA, lonA, latB, lonB float64) float64 {
|
|||
|
||||
return math.Mod(θ*degrees+360, 360)
|
||||
}
|
||||
|
||||
// RectFromCenter calculates the bounding box surrounding a circle.
|
||||
func RectFromCenter(lat, lon, meters float64) (
|
||||
minLat, minLon, maxLat, maxLon float64,
|
||||
) {
|
||||
|
||||
// see http://janmatuschek.de/LatitudeLongitudeBoundingCoordinates#Latitude
|
||||
lat *= radians
|
||||
lon *= radians
|
||||
|
||||
r := meters / earthRadius // angular radius
|
||||
|
||||
minLat = lat - r
|
||||
maxLat = lat + r
|
||||
|
||||
latT := math.Asin(math.Sin(lat) / math.Cos(r))
|
||||
lonΔ := math.Acos((math.Cos(r) - math.Sin(latT)*math.Sin(lat)) / (math.Cos(latT) * math.Cos(lat)))
|
||||
|
||||
minLon = lon - lonΔ
|
||||
maxLon = lon + lonΔ
|
||||
|
||||
// Adjust for north poll
|
||||
if maxLat > math.Pi/2 {
|
||||
minLon = -math.Pi
|
||||
maxLat = math.Pi / 2
|
||||
maxLon = math.Pi
|
||||
}
|
||||
|
||||
// Adjust for south poll
|
||||
if minLat < -math.Pi/2 {
|
||||
minLat = -math.Pi / 2
|
||||
minLon = -math.Pi
|
||||
maxLon = math.Pi
|
||||
}
|
||||
|
||||
// Adjust for wraparound. Remove this if the commented-out condition below this block is added.
|
||||
if minLon < -math.Pi || maxLon > math.Pi {
|
||||
minLon = -math.Pi
|
||||
maxLon = math.Pi
|
||||
}
|
||||
|
||||
/*
|
||||
// Consider splitting area into two bboxes, using the below checks, and erasing above block for performance. See http://janmatuschek.de/LatitudeLongitudeBoundingCoordinates#PolesAnd180thMeridian
|
||||
// Adjust for wraparound if minimum longitude is less than -180 degrees.
|
||||
if lonMin < -math.Pi {
|
||||
// box 1:
|
||||
latMin = latMin
|
||||
latMax = latMax
|
||||
lonMin += 2*math.Pi
|
||||
lonMax = math.Pi
|
||||
// box 2:
|
||||
latMin = latMin
|
||||
latMax = latMax
|
||||
lonMin = -math.Pi
|
||||
lonMax = lonMax
|
||||
}
|
||||
// Adjust for wraparound if maximum longitude is greater than 180 degrees.
|
||||
if lonMax > math.Pi {
|
||||
// box 1:
|
||||
latMin = latMin
|
||||
latMax = latMax
|
||||
lonMin = lonMin
|
||||
lonMax = -math.Pi
|
||||
// box 2:
|
||||
latMin = latMin
|
||||
latMax = latMax
|
||||
lonMin = -math.Pi
|
||||
lonMax -= 2*math.Pi
|
||||
}
|
||||
*/
|
||||
|
||||
minLon = math.Mod(minLon+3*math.Pi, 2*math.Pi) - math.Pi // normalise to -180..+180°
|
||||
maxLon = math.Mod(maxLon+3*math.Pi, 2*math.Pi) - math.Pi
|
||||
|
||||
minLat *= degrees
|
||||
minLon *= degrees
|
||||
maxLat *= degrees
|
||||
maxLon *= degrees
|
||||
return
|
||||
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package geojson
|
||||
|
||||
import "github.com/tidwall/geojson/geometry"
|
||||
import (
|
||||
"github.com/tidwall/geojson/geometry"
|
||||
)
|
||||
|
||||
// Rect ...
|
||||
type Rect struct {
|
||||
|
|
Loading…
Reference in New Issue