Merge net and circle optz

This commit is contained in:
tidwall 2018-11-11 09:45:42 -07:00
commit 766e0c941e
11 changed files with 569 additions and 108 deletions

4
Gopkg.lock generated
View File

@ -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

View File

@ -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,76 +621,73 @@ func main() {
)
}
case "EVAL":
if redis {
break
}
var i int64
getScript := "return tile38.call('GET', KEYS[1], ARGV[1], 'point')"
get4Script :=
"local a = tile38.call('GET', KEYS[1], ARGV[1], 'point');" +
"local b = tile38.call('GET', KEYS[1], ARGV[2], 'point');" +
"local c = tile38.call('GET', KEYS[1], ARGV[3], 'point');" +
"local d = tile38.call('GET', KEYS[1], ARGV[4], 'point');" +
"return d"
if !redis {
var i int64
getScript := "return tile38.call('GET', KEYS[1], ARGV[1], 'point')"
get4Script :=
"local a = tile38.call('GET', KEYS[1], ARGV[1], 'point');" +
"local b = tile38.call('GET', KEYS[1], ARGV[2], 'point');" +
"local c = tile38.call('GET', KEYS[1], ARGV[3], 'point');" +
"local d = tile38.call('GET', KEYS[1], ARGV[4], 'point');" +
"return d"
setScript := "return tile38.call('SET', KEYS[1], ARGV[1], 'point', ARGV[2], ARGV[3])"
if !opts.Quiet {
fmt.Println("Scripts to run:")
fmt.Println("GET SCRIPT: " + getScript)
fmt.Println("GET FOUR SCRIPT: " + get4Script)
fmt.Println("SET SCRIPT: " + setScript)
setScript := "return tile38.call('SET', KEYS[1], ARGV[1], 'point', ARGV[2], ARGV[3])"
if !opts.Quiet {
fmt.Println("Scripts to run:")
fmt.Println("GET SCRIPT: " + getScript)
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)
lat, lon := randPoint()
return redbench.AppendCommand(buf, "EVAL", setScript, "1",
"key:bench",
"id:"+strconv.FormatInt(i, 10),
strconv.FormatFloat(lat, 'f', 5, 64),
strconv.FormatFloat(lon, 'f', 5, 64),
)
},
)
redbench.Bench("EVALNA (set point)", addr, opts, prepFn,
func(buf []byte) []byte {
i := atomic.AddInt64(&i, 1)
lat, lon := randPoint()
return redbench.AppendCommand(buf, "EVALNA", setScript, "1",
"key:bench",
"id:"+strconv.FormatInt(i, 10),
strconv.FormatFloat(lat, 'f', 5, 64),
strconv.FormatFloat(lon, 'f', 5, 64),
)
},
)
redbench.Bench("EVALRO (get point)", addr, opts, prepFn,
func(buf []byte) []byte {
i := atomic.AddInt64(&i, 1)
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)
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),
)
},
)
redbench.Bench("EVALNA (get point)", addr, opts, prepFn,
func(buf []byte) []byte {
i := atomic.AddInt64(&i, 1)
return redbench.AppendCommand(buf, "EVALNA", getScript, "1", "key:bench", "id:"+strconv.FormatInt(i, 10))
},
)
}
redbench.Bench("EVAL (set point)", addr, opts, prepFn,
func(buf []byte) []byte {
i := atomic.AddInt64(&i, 1)
lat, lon := randPoint()
return redbench.AppendCommand(buf, "EVAL", setScript, "1",
"key:bench",
"id:"+strconv.FormatInt(i, 10),
strconv.FormatFloat(lat, 'f', 5, 64),
strconv.FormatFloat(lon, 'f', 5, 64),
)
},
)
redbench.Bench("EVALNA (set point)", addr, opts, prepFn,
func(buf []byte) []byte {
i := atomic.AddInt64(&i, 1)
lat, lon := randPoint()
return redbench.AppendCommand(buf, "EVALNA", setScript, "1",
"key:bench",
"id:"+strconv.FormatInt(i, 10),
strconv.FormatFloat(lat, 'f', 5, 64),
strconv.FormatFloat(lon, 'f', 5, 64),
)
},
)
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...)
},
)
redbench.Bench("EVALRO (get 4 points)", addr, opts, prepFn,
func(buf []byte) []byte {
i := atomic.AddInt64(&i, 1)
args := []string{
"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...)
},
)
redbench.Bench("EVALNA (get point)", addr, opts, prepFn,
func(buf []byte) []byte {
i := atomic.AddInt64(&i, 1)
return redbench.AppendCommand(buf, "EVALNA", getScript, "1", "key:bench", "id:"+strconv.FormatInt(i, 10))
},
)
}
}
}

View File

@ -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])
}

View File

@ -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

View File

@ -1,5 +1,6 @@
package core
// Build variables
var (
Version = "0.0.0" // Placeholder for the version
BuildTime = "" // Placeholder for the build time

View File

@ -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

View File

@ -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
return server.evioServe()
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 ...

View File

@ -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,
}),
)
}

View File

@ -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
}

View File

@ -1,6 +1,8 @@
package geojson
import "github.com/tidwall/geojson/geometry"
import (
"github.com/tidwall/geojson/geometry"
)
// Rect ...
type Rect struct {