mirror of https://github.com/tidwall/tile38.git
feat: add sector
This commit is contained in:
parent
d95935124a
commit
bc62edb692
|
@ -919,6 +919,31 @@
|
|||
"type": "geohash"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "SECTOR",
|
||||
"arguments": [
|
||||
{
|
||||
"name": "lat",
|
||||
"type": "double"
|
||||
},
|
||||
{
|
||||
"name": "lon",
|
||||
"type": "double"
|
||||
},
|
||||
{
|
||||
"name": "radius",
|
||||
"type": "double"
|
||||
},
|
||||
{
|
||||
"name": "startBearing",
|
||||
"type": "double"
|
||||
},
|
||||
{
|
||||
"name": "endBearing",
|
||||
"type": "double"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -1146,6 +1171,31 @@
|
|||
"type": "geohash"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "SECTOR",
|
||||
"arguments": [
|
||||
{
|
||||
"name": "lat",
|
||||
"type": "double"
|
||||
},
|
||||
{
|
||||
"name": "lon",
|
||||
"type": "double"
|
||||
},
|
||||
{
|
||||
"name": "radius",
|
||||
"type": "double"
|
||||
},
|
||||
{
|
||||
"name": "startBearing",
|
||||
"type": "double"
|
||||
},
|
||||
{
|
||||
"name": "endBearing",
|
||||
"type": "double"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -1373,7 +1423,6 @@
|
|||
"type": "string",
|
||||
"variadic": true
|
||||
}
|
||||
|
||||
],
|
||||
"group": "webhook"
|
||||
},
|
||||
|
@ -1458,7 +1507,6 @@
|
|||
"type": "string",
|
||||
"variadic": true
|
||||
}
|
||||
|
||||
],
|
||||
"group": "pubsub"
|
||||
},
|
||||
|
|
|
@ -1085,6 +1085,31 @@ var commandsJSON = `{
|
|||
"type": "geohash"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "SECTOR",
|
||||
"arguments": [
|
||||
{
|
||||
"name": "lat",
|
||||
"type": "double"
|
||||
},
|
||||
{
|
||||
"name": "lon",
|
||||
"type": "double"
|
||||
},
|
||||
{
|
||||
"name": "radius",
|
||||
"type": "double"
|
||||
},
|
||||
{
|
||||
"name": "startBearing",
|
||||
"type": "double"
|
||||
},
|
||||
{
|
||||
"name": "endBearing",
|
||||
"type": "double"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -1312,6 +1337,31 @@ var commandsJSON = `{
|
|||
"type": "geohash"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "SECTOR",
|
||||
"arguments": [
|
||||
{
|
||||
"name": "lat",
|
||||
"type": "double"
|
||||
},
|
||||
{
|
||||
"name": "lon",
|
||||
"type": "double"
|
||||
},
|
||||
{
|
||||
"name": "radius",
|
||||
"type": "double"
|
||||
},
|
||||
{
|
||||
"name": "startBearing",
|
||||
"type": "double"
|
||||
},
|
||||
{
|
||||
"name": "endBearing",
|
||||
"type": "double"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -1539,7 +1589,6 @@ var commandsJSON = `{
|
|||
"type": "string",
|
||||
"variadic": true
|
||||
}
|
||||
|
||||
],
|
||||
"group": "webhook"
|
||||
},
|
||||
|
@ -1624,7 +1673,6 @@ var commandsJSON = `{
|
|||
"type": "string",
|
||||
"variadic": true
|
||||
}
|
||||
|
||||
],
|
||||
"group": "pubsub"
|
||||
},
|
||||
|
|
1
go.mod
1
go.mod
|
@ -9,6 +9,7 @@ require (
|
|||
github.com/eclipse/paho.mqtt.golang v1.3.1
|
||||
github.com/golang/protobuf v1.4.3
|
||||
github.com/gomodule/redigo v1.8.3
|
||||
github.com/iwpnd/sectr v0.1.2
|
||||
github.com/mmcloughlin/geohash v0.10.0
|
||||
github.com/nats-io/nats-server/v2 v2.2.0 // indirect
|
||||
github.com/nats-io/nats.go v1.10.1-0.20210228004050-ed743748acac
|
||||
|
|
2
go.sum
2
go.sum
|
@ -214,6 +214,8 @@ github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmK
|
|||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
|
||||
github.com/iwpnd/sectr v0.1.2 h1:FauaPRn5C2tC42HTF7gM3FJZXvGXWc6jabBbIxzTMag=
|
||||
github.com/iwpnd/sectr v0.1.2/go.mod h1:Dm6YXDJCRx1NTfMX/1RMIkGfVp2ORjCY/cQGfbknz4c=
|
||||
github.com/jcmturner/gofork v1.0.0 h1:J7uCkflzTEhUZ64xqKnkDxq3kzc96ajM1Gli5ktUem8=
|
||||
github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||
|
|
|
@ -3,10 +3,12 @@ package server
|
|||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/iwpnd/sectr"
|
||||
"github.com/mmcloughlin/geohash"
|
||||
"github.com/tidwall/geojson"
|
||||
"github.com/tidwall/geojson/geometry"
|
||||
|
@ -214,6 +216,67 @@ func (server *Server) cmdSearchArgs(
|
|||
switch ltyp {
|
||||
case "point":
|
||||
fallthrough
|
||||
case "sector":
|
||||
if s.clip {
|
||||
err = errInvalidArgument("cannot clip with " + ltyp)
|
||||
return
|
||||
}
|
||||
var slat, slon, smeters, sb1, sb2 string
|
||||
if vs, slat, ok = tokenval(vs); !ok || slat == "" {
|
||||
err = errInvalidNumberOfArguments
|
||||
return
|
||||
}
|
||||
if vs, slon, ok = tokenval(vs); !ok || slon == "" {
|
||||
err = errInvalidNumberOfArguments
|
||||
return
|
||||
}
|
||||
if vs, smeters, ok = tokenval(vs); !ok || smeters == "" {
|
||||
err = errInvalidNumberOfArguments
|
||||
return
|
||||
}
|
||||
if vs, sb1, ok = tokenval(vs); !ok || sb1 == "" {
|
||||
err = errInvalidNumberOfArguments
|
||||
return
|
||||
}
|
||||
if vs, sb2, ok = tokenval(vs); !ok || sb2 == "" {
|
||||
err = errInvalidNumberOfArguments
|
||||
return
|
||||
}
|
||||
var lat, lon, meters, b1, b2 float64
|
||||
if lat, err = strconv.ParseFloat(slat, 64); err != nil {
|
||||
err = errInvalidArgument(slat)
|
||||
return
|
||||
}
|
||||
if lon, err = strconv.ParseFloat(slon, 64); err != nil {
|
||||
err = errInvalidArgument(slon)
|
||||
return
|
||||
}
|
||||
if meters, err = strconv.ParseFloat(smeters, 64); err != nil {
|
||||
err = errInvalidArgument(smeters)
|
||||
return
|
||||
}
|
||||
if b1, err = strconv.ParseFloat(sb1, 64); err != nil {
|
||||
err = errInvalidArgument(sb1)
|
||||
return
|
||||
}
|
||||
if b2, err = strconv.ParseFloat(sb2, 64); err != nil {
|
||||
err = errInvalidArgument(sb2)
|
||||
return
|
||||
}
|
||||
|
||||
if b1 == b2 {
|
||||
err = fmt.Errorf("equal bearings (%s == %s), use CIRCLE instead", sb1, sb2)
|
||||
return
|
||||
}
|
||||
|
||||
origin := sectr.Point{Lng: lon, Lat: lat}
|
||||
sector := sectr.NewSector(origin, meters, b1, b2)
|
||||
|
||||
s.obj, err = geojson.Parse(string(sector.JSON()), &server.geomParseOpts)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
case "circle":
|
||||
if s.clip {
|
||||
err = errInvalidArgument("cannot clip with " + ltyp)
|
||||
|
@ -379,7 +442,8 @@ func (server *Server) cmdSearchArgs(
|
|||
|
||||
var nearbyTypes = []string{"point"}
|
||||
var withinOrIntersectsTypes = []string{
|
||||
"geo", "bounds", "hash", "tile", "quadkey", "get", "object", "circle"}
|
||||
"geo", "bounds", "hash", "tile", "quadkey", "get", "object", "circle", "sector",
|
||||
}
|
||||
|
||||
func (server *Server) cmdNearby(msg *Message) (res resp.Value, err error) {
|
||||
start := time.Now()
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/iwpnd/sectr"
|
||||
"github.com/mmcloughlin/geohash"
|
||||
"github.com/tidwall/geojson"
|
||||
"github.com/tidwall/geojson/geometry"
|
||||
|
@ -47,6 +48,67 @@ func (s *Server) parseArea(ovs []string, doClip bool) (vs []string, o geojson.Ob
|
|||
return
|
||||
}
|
||||
o = geojson.NewPoint(geometry.Point{X: lon, Y: lat})
|
||||
case "sector":
|
||||
if doClip {
|
||||
err = errInvalidArgument("cannot clip with " + ltyp)
|
||||
return
|
||||
}
|
||||
var slat, slon, smeters, sb1, sb2 string
|
||||
if vs, slat, ok = tokenval(vs); !ok || slat == "" {
|
||||
err = errInvalidNumberOfArguments
|
||||
return
|
||||
}
|
||||
if vs, slon, ok = tokenval(vs); !ok || slon == "" {
|
||||
err = errInvalidNumberOfArguments
|
||||
return
|
||||
}
|
||||
if vs, smeters, ok = tokenval(vs); !ok || smeters == "" {
|
||||
err = errInvalidNumberOfArguments
|
||||
return
|
||||
}
|
||||
if vs, sb1, ok = tokenval(vs); !ok || sb1 == "" {
|
||||
err = errInvalidNumberOfArguments
|
||||
return
|
||||
}
|
||||
if vs, sb2, ok = tokenval(vs); !ok || sb2 == "" {
|
||||
err = errInvalidNumberOfArguments
|
||||
return
|
||||
}
|
||||
var lat, lon, meters, b1, b2 float64
|
||||
if lat, err = strconv.ParseFloat(slat, 64); err != nil {
|
||||
err = errInvalidArgument(slat)
|
||||
return
|
||||
}
|
||||
if lon, err = strconv.ParseFloat(slon, 64); err != nil {
|
||||
err = errInvalidArgument(slon)
|
||||
return
|
||||
}
|
||||
if meters, err = strconv.ParseFloat(smeters, 64); err != nil {
|
||||
err = errInvalidArgument(smeters)
|
||||
return
|
||||
}
|
||||
if b1, err = strconv.ParseFloat(sb1, 64); err != nil {
|
||||
err = errInvalidArgument(sb1)
|
||||
return
|
||||
}
|
||||
if b2, err = strconv.ParseFloat(sb2, 64); err != nil {
|
||||
err = errInvalidArgument(sb2)
|
||||
return
|
||||
}
|
||||
|
||||
if b1 == b2 {
|
||||
err = fmt.Errorf("equal bearings (%s == %s), use CIRCLE instead", sb1, sb2)
|
||||
return
|
||||
}
|
||||
|
||||
origin := sectr.Point{Lng: lon, Lat: lat}
|
||||
sector := sectr.NewSector(origin, meters, b1, b2)
|
||||
|
||||
o, err = geojson.Parse(string(sector.JSON()), &s.geomParseOpts)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
case "circle":
|
||||
if doClip {
|
||||
err = fmt.Errorf("invalid clip type '%s'", typ)
|
||||
|
|
|
@ -763,7 +763,7 @@ loop:
|
|||
ae = &areaExpression{op: OR, children: []*areaExpression{ae}}
|
||||
}
|
||||
vsout = nvs
|
||||
case "point", "circle", "object", "bounds", "hash", "quadkey", "tile", "get":
|
||||
case "point", "circle", "object", "bounds", "hash", "quadkey", "tile", "get", "sector":
|
||||
parsedVs, parsedObj, areaErr := s.parseArea(vsout, doClip)
|
||||
if areaErr != nil {
|
||||
err = areaErr
|
||||
|
|
|
@ -40,10 +40,12 @@ func testcmd_WITHIN_test(mc *mockServer) error {
|
|||
{"SET", "mykey", "poly8", "OBJECT", poly8}, {"OK"},
|
||||
|
||||
{"TEST", "GET", "mykey", "point1", "WITHIN", "OBJECT", poly}, {"1"},
|
||||
{"TEST", "GET", "mykey", "point1", "WITHIN", "SECTOR", "37.72999", "-122.44760", "1000", "0", "90"}, {"1"},
|
||||
{"TEST", "GET", "mykey", "line3", "WITHIN", "OBJECT", poly}, {"1"},
|
||||
{"TEST", "GET", "mykey", "poly4", "WITHIN", "OBJECT", poly}, {"1"},
|
||||
{"TEST", "GET", "mykey", "multipoly5", "WITHIN", "OBJECT", poly}, {"1"},
|
||||
{"TEST", "GET", "mykey", "poly8", "WITHIN", "OBJECT", poly}, {"1"},
|
||||
{"TEST", "GET", "mykey", "poly8", "WITHIN", "SECTOR", "37.72999", "-122.44760", "1000", "0", "90"}, {"1"},
|
||||
|
||||
{"TEST", "GET", "mykey", "point6", "WITHIN", "OBJECT", poly}, {"0"},
|
||||
{"TEST", "GET", "mykey", "point7", "WITHIN", "OBJECT", poly}, {"0"},
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
.ipynb_checkpoints/
|
||||
/tmp/
|
||||
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
.DS_Store
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
|
@ -0,0 +1,119 @@
|
|||
# Edit this file as you like.
|
||||
#
|
||||
# All these sections are optional. Each section with the exception of [general] represents
|
||||
# one rule and each key in it is an option for that specific rule.
|
||||
#
|
||||
# Rules and sections can be referenced by their full name or by id. For example
|
||||
# section "[body-max-line-length]" could also be written as "[B1]". Full section names are
|
||||
# used in here for clarity.
|
||||
#
|
||||
[general]
|
||||
# Ignore certain rules, this example uses both full name and id
|
||||
# ignore=title-trailing-punctuation, T3
|
||||
|
||||
# verbosity should be a value between 1 and 3, the commandline -v flags take precedence over this
|
||||
# verbosity = 2
|
||||
|
||||
# By default gitlint will ignore merge, revert, fixup and squash commits.
|
||||
# ignore-merge-commits=true
|
||||
# ignore-revert-commits=true
|
||||
# ignore-fixup-commits=true
|
||||
# ignore-squash-commits=true
|
||||
|
||||
# Ignore any data send to gitlint via stdin
|
||||
# ignore-stdin=true
|
||||
|
||||
# Fetch additional meta-data from the local repository when manually passing a
|
||||
# commit message to gitlint via stdin or --commit-msg. Disabled by default.
|
||||
# staged=true
|
||||
|
||||
# Enable debug mode (prints more output). Disabled by default.
|
||||
# debug=true
|
||||
|
||||
# Enable community contributed rules
|
||||
# See http://jorisroovers.github.io/gitlint/contrib_rules for details
|
||||
contrib=contrib-title-conventional-commits
|
||||
|
||||
# Set the extra-path where gitlint will search for user defined rules
|
||||
# See http://jorisroovers.github.io/gitlint/user_defined_rules for details
|
||||
# extra-path=examples/
|
||||
|
||||
# This is an example of how to configure the "title-max-length" rule and
|
||||
# set the line-length it enforces to 80
|
||||
# [title-max-length]
|
||||
# line-length=50
|
||||
|
||||
# Conversely, you can also enforce minimal length of a title with the
|
||||
# "title-min-length" rule:
|
||||
# [title-min-length]
|
||||
# min-length=5
|
||||
|
||||
# [title-must-not-contain-word]
|
||||
# Comma-separated list of words that should not occur in the title. Matching is case
|
||||
# insensitive. It's fine if the keyword occurs as part of a larger word (so "WIPING"
|
||||
# will not cause a violation, but "WIP: my title" will.
|
||||
# words=wip
|
||||
|
||||
# [title-match-regex]
|
||||
# python-style regex that the commit-msg title must match
|
||||
# Note that the regex can contradict with other rules if not used correctly
|
||||
# (e.g. title-must-not-contain-word).
|
||||
# regex=^US[0-9]*
|
||||
|
||||
# [body-max-line-length]
|
||||
# line-length=72
|
||||
|
||||
# [body-min-length]
|
||||
# min-length=5
|
||||
|
||||
#[body-is-missing]
|
||||
# Whether to ignore this rule on merge commits (which typically only have a title)
|
||||
# default = True
|
||||
#ignore-merge-commits=false
|
||||
|
||||
# [body-changed-file-mention]
|
||||
# List of files that need to be explicitly mentioned in the body when they are changed
|
||||
# This is useful for when developers often erroneously edit certain files or git submodules.
|
||||
# By specifying this rule, developers can only change the file when they explicitly reference
|
||||
# it in the commit message.
|
||||
# files=gitlint/rules.py,README.md
|
||||
|
||||
# [body-match-regex]
|
||||
# python-style regex that the commit-msg body must match.
|
||||
# E.g. body must end in My-Commit-Tag: foo
|
||||
# regex=My-Commit-Tag: foo$
|
||||
|
||||
# [author-valid-email]
|
||||
# python-style regex that the commit author email address must match.
|
||||
# For example, use the following regex if you only want to allow email addresses from foo.com
|
||||
# regex=[^@]+@foo.com
|
||||
|
||||
# [ignore-by-title]
|
||||
# Ignore certain rules for commits of which the title matches a regex
|
||||
# E.g. Match commit titles that start with "Release"
|
||||
# regex=^Release(.*)
|
||||
|
||||
# Ignore certain rules, you can reference them by their id or by their full name
|
||||
# Use 'all' to ignore all rules
|
||||
ignore=B6,CC1
|
||||
|
||||
# [ignore-by-body]
|
||||
# Ignore certain rules for commits of which the body has a line that matches a regex
|
||||
# E.g. Match bodies that have a line that that contain "release"
|
||||
# regex=(.*)release(.*)
|
||||
#
|
||||
# Ignore certain rules, you can reference them by their id or by their full name
|
||||
# Use 'all' to ignore all rules
|
||||
# ignore=T1,body-min-length
|
||||
|
||||
# [ignore-body-lines]
|
||||
# Ignore certain lines in a commit body that match a regex.
|
||||
# E.g. Ignore all lines that start with 'Co-Authored-By'
|
||||
# regex=^Co-Authored-By
|
||||
|
||||
# This is a contrib rule - a community contributed rule. These are disabled by default.
|
||||
# You need to explicitly enable them one-by-one by adding them to the "contrib" option
|
||||
# under [general] section above.
|
||||
# [contrib-title-conventional-commits]
|
||||
# Specify allowed commit types. For details see: https://www.conventionalcommits.org/
|
||||
#types = feat,fix,refactor,docs,ci,chore
|
|
@ -0,0 +1,13 @@
|
|||
repos:
|
||||
- repo: git://github.com/dnephin/pre-commit-golang
|
||||
rev: v0.4.0
|
||||
hooks:
|
||||
- id: go-fmt
|
||||
- id: go-vet
|
||||
- id: go-lint
|
||||
- id: go-imports
|
||||
- id: go-mod-tidy
|
||||
- repo: https://github.com/jorisroovers/gitlint
|
||||
rev: v0.15.1
|
||||
hooks:
|
||||
- id: gitlint
|
|
@ -0,0 +1,3 @@
|
|||
module github.com/iwpnd/sectr
|
||||
|
||||
go 1.16
|
|
@ -0,0 +1,71 @@
|
|||
# sectr 🍕
|
||||
|
||||
Build a circular sector (pizza piece 😅 ) spanning the angle between two given bearings, a radius and a center point.
|
||||
|
||||
## installation
|
||||
|
||||
```
|
||||
go get -u github.com/iwpnd/sectr
|
||||
```
|
||||
|
||||
## usage
|
||||
|
||||
<img src=".github/img/sectr_bg.png" alt="Logo" width="500" height="227">
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/iwpnd/sectr"
|
||||
)
|
||||
|
||||
func main() {
|
||||
p := sectr.Point{Lat: 52.25, Lng: 13.37}
|
||||
sector := sectr.NewSector(p, 100, 0, 90)
|
||||
|
||||
fmt.Printf("%s", sector.JSON())
|
||||
}
|
||||
|
||||
>> {
|
||||
"type": "Polygon",
|
||||
"coordinates": [
|
||||
[
|
||||
[13.37,52.25],
|
||||
[13.37,52.25089932],
|
||||
[13.37012803,52.2508959],
|
||||
[13.3702803,52.2508828],
|
||||
[13.37040491,52.25086448],
|
||||
[13.37055029,52.25083383],
|
||||
[13.37068965,52.25079405],
|
||||
[13.37080006,52.25075423],
|
||||
[13.37092446,52.2506989],
|
||||
[13.37103872,52.25063591],
|
||||
[13.3711253,52.25057807],
|
||||
[13.37121783,52.25050289],
|
||||
[13.37128479,52.25043599],
|
||||
[13.37135219,52.25035138],
|
||||
[13.37140478,52.25026293],
|
||||
[13.37143686,52.25018697],
|
||||
[13.37146091,52.250094],
|
||||
[13.37146896,52.24999999],
|
||||
[13.37,52.25]
|
||||
]
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
## Acknowledgement
|
||||
|
||||
[Chris Veness](https://github.com/chrisveness) for refreshing my university left-overs with this blog [moveable-type](https://www.movable-type.co.uk/scripts/latlong.html)
|
||||
|
||||
## Maintainer
|
||||
|
||||
Benjamin Ramser - [@iwpnd](https://github.com/iwpnd)
|
||||
|
||||
Project Link: [https://github.com/iwpnd/sectr](https://github.com/iwpnd/sectr)
|
|
@ -0,0 +1,160 @@
|
|||
package sectr
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"math"
|
||||
)
|
||||
|
||||
const earthRadius = 6371008.8 // earth radius
|
||||
|
||||
// Point ...
|
||||
type Point struct {
|
||||
Lng, Lat float64
|
||||
}
|
||||
|
||||
// Sector ...
|
||||
type Sector struct {
|
||||
coordinates [][][]float64
|
||||
origin Point
|
||||
radius float64
|
||||
bearing1 float64
|
||||
bearing2 float64
|
||||
}
|
||||
|
||||
// SectorGeometry ...
|
||||
type SectorGeometry struct {
|
||||
Type string `json:"type"`
|
||||
Coordinates [][][]float64 `json:"coordinates"`
|
||||
}
|
||||
|
||||
func radToDegree(rad float64) float64 {
|
||||
return rad * 180 / math.Pi
|
||||
}
|
||||
|
||||
func degreeToRad(degree float64) float64 {
|
||||
return degree * math.Pi / 180
|
||||
}
|
||||
|
||||
func distanceToRadians(distance float64) float64 {
|
||||
const r = earthRadius
|
||||
|
||||
return distance / r
|
||||
}
|
||||
|
||||
// terminal calculates the terminal position travelling a distance
|
||||
// from a given origin
|
||||
// see https://www.movable-type.co.uk/scripts/latlong.html
|
||||
func terminal(start Point, distance, bearing float64) Point {
|
||||
φ1 := degreeToRad(start.Lat)
|
||||
λ1 := degreeToRad(start.Lng)
|
||||
bearingRad := degreeToRad(bearing)
|
||||
distanceRad := distanceToRadians(distance)
|
||||
|
||||
φ2 := math.Asin(
|
||||
math.Sin(φ1)*
|
||||
math.Cos(distanceRad) +
|
||||
math.Cos(φ1)*
|
||||
math.Sin(distanceRad)*
|
||||
math.Cos(bearingRad))
|
||||
|
||||
λ2 := λ1 + math.Atan2(
|
||||
math.Sin(bearingRad)*
|
||||
math.Sin(distanceRad)*
|
||||
math.Cos(φ1),
|
||||
math.Cos(distanceRad)-
|
||||
math.Sin(φ1)*
|
||||
math.Sin(φ2))
|
||||
|
||||
// cap decimals at .00000001 degree ~= 1.11mm
|
||||
lng := math.Round(radToDegree(λ2)*100000000) / 100000000
|
||||
lat := math.Round(radToDegree(φ2)*100000000) / 100000000
|
||||
|
||||
return Point{Lng: lng, Lat: lat}
|
||||
}
|
||||
|
||||
func bearingToAngle(bearing float64) float64 {
|
||||
angle := math.Mod(bearing, 360)
|
||||
|
||||
if angle < 0 {
|
||||
angle = angle + 360
|
||||
}
|
||||
|
||||
return angle
|
||||
}
|
||||
|
||||
// NewSector creates a sector from a given origin point, a radius and two bearings
|
||||
func NewSector(origin Point, radius, bearing1, bearing2 float64) *Sector {
|
||||
|
||||
// to cap the maximum positions in a sector/circle to 64
|
||||
// the higher the smoother, yet the bigger the coordinate array
|
||||
const steps = 64
|
||||
|
||||
s := &Sector{
|
||||
origin: origin,
|
||||
bearing1: bearing1,
|
||||
bearing2: bearing2,
|
||||
radius: radius,
|
||||
}
|
||||
|
||||
angle1 := bearingToAngle(bearing1)
|
||||
angle2 := bearingToAngle(bearing2)
|
||||
|
||||
// if angle1 == angle2 return circle
|
||||
if angle1 == angle2 {
|
||||
for i := 1; i < steps; i++ {
|
||||
α := float64(i * -360 / steps)
|
||||
t := terminal(origin, radius, α)
|
||||
s.addPoint(t)
|
||||
}
|
||||
|
||||
s.coordinates[0] = append(s.coordinates[0], s.coordinates[0][0])
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
var endDegree float64
|
||||
startDegree := angle1
|
||||
|
||||
if angle1 < angle2 {
|
||||
endDegree = angle2
|
||||
} else {
|
||||
endDegree = angle2 + 360
|
||||
}
|
||||
|
||||
α := startDegree
|
||||
|
||||
s.addPoint(origin)
|
||||
|
||||
for i := 1; ; i++ {
|
||||
if α < endDegree {
|
||||
t := terminal(origin, radius, α)
|
||||
s.addPoint(t)
|
||||
α = startDegree + float64((i*360)/steps)
|
||||
}
|
||||
|
||||
if α >= endDegree {
|
||||
t := terminal(origin, radius, endDegree)
|
||||
s.addPoint(t)
|
||||
s.addPoint(origin)
|
||||
|
||||
return s
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Sector) addPoint(p Point) {
|
||||
if len(s.coordinates) == 0 {
|
||||
s.coordinates = append(s.coordinates, [][]float64{{p.Lng, p.Lat}})
|
||||
return
|
||||
}
|
||||
|
||||
s.coordinates[0] = append(s.coordinates[0], []float64{p.Lng, p.Lat})
|
||||
}
|
||||
|
||||
// JSON exports the Sector as json
|
||||
func (s Sector) JSON() []byte {
|
||||
f := SectorGeometry{Type: "Polygon", Coordinates: s.coordinates}
|
||||
j, _ := json.Marshal(f)
|
||||
|
||||
return j
|
||||
}
|
|
@ -103,6 +103,9 @@ github.com/googleapis/gax-go/v2
|
|||
github.com/gorilla/websocket
|
||||
# github.com/hashicorp/go-uuid v1.0.2
|
||||
github.com/hashicorp/go-uuid
|
||||
# github.com/iwpnd/sectr v0.1.2
|
||||
## explicit
|
||||
github.com/iwpnd/sectr
|
||||
# github.com/jcmturner/gofork v1.0.0
|
||||
github.com/jcmturner/gofork/encoding/asn1
|
||||
github.com/jcmturner/gofork/x/crypto/pbkdf2
|
||||
|
|
Loading…
Reference in New Issue