Add geometry contains + inside database functions

This commit is contained in:
Tobie Morgan Hitchcock 2021-05-13 01:28:13 +01:00
parent ef160763f3
commit 02bacf121c
6 changed files with 143 additions and 5 deletions

View file

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

View file

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

View file

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

View file

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