Add geometry contains + inside database functions
This commit is contained in:
parent
ef160763f3
commit
02bacf121c
6 changed files with 143 additions and 5 deletions
|
@ -97,6 +97,7 @@ var funcs = map[string]map[int]interface{}{
|
||||||
"geo.point": {1: nil, 2: nil},
|
"geo.point": {1: nil, 2: nil},
|
||||||
"geo.circle": {2: nil},
|
"geo.circle": {2: nil},
|
||||||
"geo.polygon": {-1: nil},
|
"geo.polygon": {-1: nil},
|
||||||
|
"geo.contains": {2: nil},
|
||||||
"geo.distance": {2: nil},
|
"geo.distance": {2: nil},
|
||||||
"geo.inside": {2: nil},
|
"geo.inside": {2: nil},
|
||||||
"geo.intersects": {2: nil},
|
"geo.intersects": {2: nil},
|
||||||
|
|
|
@ -70,6 +70,8 @@ func Run(ctx context.Context, name string, args ...interface{}) (interface{}, er
|
||||||
return geoCircle(ctx, args...)
|
return geoCircle(ctx, args...)
|
||||||
case "geo.polygon":
|
case "geo.polygon":
|
||||||
return geoPolygon(ctx, args...)
|
return geoPolygon(ctx, args...)
|
||||||
|
case "geo.contains":
|
||||||
|
return geoContains(ctx, args...)
|
||||||
case "geo.distance":
|
case "geo.distance":
|
||||||
return geoDistance(ctx, args...)
|
return geoDistance(ctx, args...)
|
||||||
case "geo.inside":
|
case "geo.inside":
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
package fncs
|
package fncs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/abcum/surreal/sql"
|
"github.com/abcum/surreal/sql"
|
||||||
|
@ -57,8 +58,22 @@ func geoCircle(ctx context.Context, args ...interface{}) (interface{}, error) {
|
||||||
|
|
||||||
func geoPolygon(ctx context.Context, args ...interface{}) (interface{}, error) {
|
func geoPolygon(ctx context.Context, args ...interface{}) (interface{}, error) {
|
||||||
switch len(args) {
|
switch len(args) {
|
||||||
case 0, 1, 2:
|
case 0, 2:
|
||||||
// Not enough arguments, so just ignore
|
// Not enough arguments, so just ignore
|
||||||
|
case 1:
|
||||||
|
var pnts []*sql.Point
|
||||||
|
if a, ok := ensureSlice(args[0]); ok {
|
||||||
|
for _, a := range a {
|
||||||
|
if p, _ := ensurePoint(a); p != nil {
|
||||||
|
pnts = append(pnts, p)
|
||||||
|
} else if p := ensureFloats(a); len(p) == 2 {
|
||||||
|
pnts = append(pnts, sql.NewPoint(p[0], p[1]))
|
||||||
|
} else {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sql.NewPolygon(pnts...), nil
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
var pnts []*sql.Point
|
var pnts []*sql.Point
|
||||||
for _, a := range args {
|
for _, a := range args {
|
||||||
|
@ -75,6 +90,15 @@ func geoPolygon(ctx context.Context, args ...interface{}) (interface{}, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func geoContains(ctx context.Context, args ...interface{}) (interface{}, error) {
|
||||||
|
if a, ok := ensurePolygon(args[0]); ok {
|
||||||
|
if b, ok := ensurePoint(args[1]); ok {
|
||||||
|
return geof.Contains(a, b), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
func geoDistance(ctx context.Context, args ...interface{}) (interface{}, error) {
|
func geoDistance(ctx context.Context, args ...interface{}) (interface{}, error) {
|
||||||
if pnt, ok := ensurePoint(args[0]); ok {
|
if pnt, ok := ensurePoint(args[0]); ok {
|
||||||
if frm, ok := ensurePoint(args[1]); ok {
|
if frm, ok := ensurePoint(args[1]); ok {
|
||||||
|
@ -85,8 +109,8 @@ func geoDistance(ctx context.Context, args ...interface{}) (interface{}, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func geoInside(ctx context.Context, args ...interface{}) (interface{}, error) {
|
func geoInside(ctx context.Context, args ...interface{}) (interface{}, error) {
|
||||||
if a, ok := ensureGeometry(args[0]); ok {
|
if a, ok := ensurePoint(args[0]); ok {
|
||||||
if b, ok := ensureGeometry(args[1]); ok {
|
if b, ok := ensurePolygon(args[1]); ok {
|
||||||
return geof.Inside(a, b), nil
|
return geof.Inside(a, b), nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
31
util/geof/contains.go
Normal file
31
util/geof/contains.go
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
// Copyright © 2016 Abcum Ltd
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package geof
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/abcum/surreal/sql"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Contains(a *sql.Polygon, b *sql.Point) bool {
|
||||||
|
beg := len(a.PS) - 1
|
||||||
|
end := 0
|
||||||
|
contains := raycast(b, a.PS[beg], a.PS[end])
|
||||||
|
for i := 1; i < len(a.PS); i++ {
|
||||||
|
if raycast(b, a.PS[i-1], a.PS[i]) {
|
||||||
|
contains = !contains
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return contains
|
||||||
|
}
|
|
@ -14,6 +14,18 @@
|
||||||
|
|
||||||
package geof
|
package geof
|
||||||
|
|
||||||
func Inside(a, b interface{}) bool {
|
import (
|
||||||
return false
|
"github.com/abcum/surreal/sql"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Inside(a *sql.Point, b *sql.Polygon) bool {
|
||||||
|
beg := len(b.PS) - 1
|
||||||
|
end := 0
|
||||||
|
contains := raycast(a, b.PS[beg], b.PS[end])
|
||||||
|
for i := 1; i < len(b.PS); i++ {
|
||||||
|
if raycast(a, b.PS[i-1], b.PS[i]) {
|
||||||
|
contains = !contains
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return contains
|
||||||
}
|
}
|
||||||
|
|
68
util/geof/raycast.go
Normal file
68
util/geof/raycast.go
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
// Copyright © 2016 Abcum Ltd
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package geof
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
|
||||||
|
"github.com/abcum/surreal/sql"
|
||||||
|
)
|
||||||
|
|
||||||
|
func raycast(point, beg, end *sql.Point) bool {
|
||||||
|
|
||||||
|
// Always ensure that the the first point
|
||||||
|
// has a Y coordinate that is less than
|
||||||
|
// the second point. Switch if not.
|
||||||
|
|
||||||
|
if beg.LO > end.LO {
|
||||||
|
beg, end = end, beg
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move the point's Y coordinate outside of
|
||||||
|
// the bounds of the testing region so that
|
||||||
|
// we can start drawing a ray
|
||||||
|
for point.LO == beg.LO || point.LO == end.LO {
|
||||||
|
lng := math.Nextafter(point.LO, math.Inf(1))
|
||||||
|
point = sql.NewPoint(point.LA, lng)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we are outside of the polygon, indicate so.
|
||||||
|
if point.LO < beg.LO || point.LO > end.LO {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if beg.LA > end.LA {
|
||||||
|
if point.LA > beg.LA {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if point.LA < end.LA {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
if point.LA > end.LA {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if point.LA < beg.LA {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
raySlope := (point.LO - beg.LO) / (point.LA - beg.LA)
|
||||||
|
diagSlope := (end.LO - beg.LO) / (end.LA - beg.LA)
|
||||||
|
|
||||||
|
return raySlope >= diagSlope
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in a new issue