174 lines
3.4 KiB
Go
174 lines
3.4 KiB
Go
package planar
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
|
|
"github.com/paulmach/orb"
|
|
)
|
|
|
|
// DistanceFromSegment returns the point's distance from the segment [a, b].
|
|
func DistanceFromSegment(a, b, point orb.Point) float64 {
|
|
return math.Sqrt(DistanceFromSegmentSquared(a, b, point))
|
|
}
|
|
|
|
// DistanceFromSegmentSquared returns point's squared distance from the segement [a, b].
|
|
func DistanceFromSegmentSquared(a, b, point orb.Point) float64 {
|
|
x := a[0]
|
|
y := a[1]
|
|
dx := b[0] - x
|
|
dy := b[1] - y
|
|
|
|
if dx != 0 || dy != 0 {
|
|
t := ((point[0]-x)*dx + (point[1]-y)*dy) / (dx*dx + dy*dy)
|
|
|
|
if t > 1 {
|
|
x = b[0]
|
|
y = b[1]
|
|
} else if t > 0 {
|
|
x += dx * t
|
|
y += dy * t
|
|
}
|
|
}
|
|
|
|
dx = point[0] - x
|
|
dy = point[1] - y
|
|
|
|
return dx*dx + dy*dy
|
|
}
|
|
|
|
// DistanceFrom returns the distance from the boundary of the geometry in
|
|
// the units of the geometry.
|
|
func DistanceFrom(g orb.Geometry, p orb.Point) float64 {
|
|
d, _ := DistanceFromWithIndex(g, p)
|
|
return d
|
|
}
|
|
|
|
// DistanceFromWithIndex returns the minimum euclidean distance
|
|
// from the boundary of the geometry plus the index of the sub-geometry
|
|
// that was the match.
|
|
func DistanceFromWithIndex(g orb.Geometry, p orb.Point) (float64, int) {
|
|
if g == nil {
|
|
return math.Inf(1), -1
|
|
}
|
|
|
|
switch g := g.(type) {
|
|
case orb.Point:
|
|
return Distance(g, p), 0
|
|
case orb.MultiPoint:
|
|
return multiPointDistanceFrom(g, p)
|
|
case orb.LineString:
|
|
return lineStringDistanceFrom(g, p)
|
|
case orb.MultiLineString:
|
|
dist := math.Inf(1)
|
|
index := -1
|
|
for i, ls := range g {
|
|
if d, _ := lineStringDistanceFrom(ls, p); d < dist {
|
|
dist = d
|
|
index = i
|
|
}
|
|
}
|
|
|
|
return dist, index
|
|
case orb.Ring:
|
|
return lineStringDistanceFrom(orb.LineString(g), p)
|
|
case orb.Polygon:
|
|
return polygonDistanceFrom(g, p)
|
|
case orb.MultiPolygon:
|
|
dist := math.Inf(1)
|
|
index := -1
|
|
for i, poly := range g {
|
|
if d, _ := polygonDistanceFrom(poly, p); d < dist {
|
|
dist = d
|
|
index = i
|
|
}
|
|
}
|
|
|
|
return dist, index
|
|
case orb.Collection:
|
|
dist := math.Inf(1)
|
|
index := -1
|
|
for i, ge := range g {
|
|
if d, _ := DistanceFromWithIndex(ge, p); d < dist {
|
|
dist = d
|
|
index = i
|
|
}
|
|
}
|
|
|
|
return dist, index
|
|
case orb.Bound:
|
|
return DistanceFromWithIndex(g.ToRing(), p)
|
|
}
|
|
|
|
panic(fmt.Sprintf("geometry type not supported: %T", g))
|
|
}
|
|
|
|
func multiPointDistanceFrom(mp orb.MultiPoint, p orb.Point) (float64, int) {
|
|
dist := math.Inf(1)
|
|
index := -1
|
|
|
|
for i := range mp {
|
|
if d := DistanceSquared(mp[i], p); d < dist {
|
|
dist = d
|
|
index = i
|
|
}
|
|
}
|
|
|
|
return math.Sqrt(dist), index
|
|
}
|
|
|
|
func lineStringDistanceFrom(ls orb.LineString, p orb.Point) (float64, int) {
|
|
dist := math.Inf(1)
|
|
index := -1
|
|
|
|
for i := 0; i < len(ls)-1; i++ {
|
|
if d := segmentDistanceFromSquared(ls[i], ls[i+1], p); d < dist {
|
|
dist = d
|
|
index = i
|
|
}
|
|
}
|
|
|
|
return math.Sqrt(dist), index
|
|
}
|
|
|
|
func polygonDistanceFrom(p orb.Polygon, point orb.Point) (float64, int) {
|
|
if len(p) == 0 {
|
|
return math.Inf(1), -1
|
|
}
|
|
|
|
dist, index := lineStringDistanceFrom(orb.LineString(p[0]), point)
|
|
for i := 1; i < len(p); i++ {
|
|
d, i := lineStringDistanceFrom(orb.LineString(p[i]), point)
|
|
if d < dist {
|
|
dist = d
|
|
index = i
|
|
}
|
|
}
|
|
|
|
return dist, index
|
|
}
|
|
|
|
func segmentDistanceFromSquared(p1, p2, point orb.Point) float64 {
|
|
x := p1[0]
|
|
y := p1[1]
|
|
dx := p2[0] - x
|
|
dy := p2[1] - y
|
|
|
|
if dx != 0 || dy != 0 {
|
|
t := ((point[0]-x)*dx + (point[1]-y)*dy) / (dx*dx + dy*dy)
|
|
|
|
if t > 1 {
|
|
x = p2[0]
|
|
y = p2[1]
|
|
} else if t > 0 {
|
|
x += dx * t
|
|
y += dy * t
|
|
}
|
|
}
|
|
|
|
dx = point[0] - x
|
|
dy = point[1] - y
|
|
|
|
return dx*dx + dy*dy
|
|
}
|