tile38/vendor/github.com/tidwall/geojson/circle.go

184 lines
4.4 KiB
Go

package geojson
import (
"strconv"
"github.com/tidwall/geojson/geo"
"github.com/tidwall/geojson/geometry"
)
// Circle ...
type Circle struct {
Object
center geometry.Point
meters float64
haversine float64
steps int
km bool
extra *extra
}
// NewCircle returns an circle object
func NewCircle(center geometry.Point, meters float64, steps int) *Circle {
if steps < 3 {
steps = 3
}
g := new(Circle)
g.center = center
g.meters = meters
g.steps = steps
if meters <= 0 {
g.Object = NewPoint(center)
} else {
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
}
// AppendJSON ...
func (g *Circle) AppendJSON(dst []byte) []byte {
dst = append(dst, `{"type":"Feature","geometry":`...)
dst = append(dst, `{"type":"Point","coordinates":[`...)
dst = strconv.AppendFloat(dst, g.center.X, 'f', -1, 64)
dst = append(dst, ',')
dst = strconv.AppendFloat(dst, g.center.Y, 'f', -1, 64)
dst = append(dst, `]},"properties":{"type":"Circle","radius":`...)
dst = strconv.AppendFloat(dst, g.meters, 'f', -1, 64)
dst = append(dst, `,"radius_units":"m"}}`...)
return dst
}
// JSON ...
func (g *Circle) JSON() string {
return string(g.AppendJSON(nil))
}
// String ...
func (g *Circle) String() string {
return string(g.AppendJSON(nil))
}
// Meters returns the circle's radius
func (g *Circle) Meters() float64 {
return g.meters
}
// Center returns the circle's center point
func (g *Circle) Center() geometry.Point {
return g.center
}
// Within returns true if circle is contained inside object
func (g *Circle) Within(obj Object) bool {
return obj.Contains(g)
}
func (g *Circle) contains(p geometry.Point, allowOnEdge bool) bool {
h := geo.Haversine(p.Y, p.X, g.center.Y, g.center.X)
if allowOnEdge {
return h <= g.haversine
}
return h < g.haversine
}
// Contains returns true if the circle contains other object
func (g *Circle) Contains(obj Object) bool {
switch other := obj.(type) {
case *Point:
return g.contains(other.Center(), false)
case *Circle:
return other.Distance(g) < (other.meters + g.meters)
case *LineString:
for i := 0; i < other.base.NumPoints(); i++ {
if geoDistancePoints(other.base.PointAt(i), g.center) > g.meters {
return false
}
}
return true
case Collection:
for _, p := range other.Children() {
if !g.Contains(p) {
return false
}
}
return true
default:
// No simple cases, so using polygon approximation.
return g.Object.Contains(other)
}
}
func (g *Circle) intersectsSegment(seg geometry.Segment) bool {
start, end := seg.A, seg.B
// These are faster checks. If they succeed there's no need do complicate things.
if g.contains(start, true) {
return true
}
if g.contains(end, true) {
return true
}
// Distance between start and end
l := geo.DistanceTo(start.Y, start.X, end.Y, end.X)
// Unit direction vector
dx := (end.X - start.X) / l
dy := (end.Y - start.Y) / l
// Point of the line closest to the center
t := dx*(g.center.X-start.X) + dy*(g.center.Y-start.Y)
px := t*dx + start.X
py := t*dy + start.Y
if px < start.X || px > end.X || py < start.Y || py > end.Y {
// closest point is outside the segment
return false
}
// Distance from the closest point to the center
return g.contains(geometry.Point{X: px, Y: py}, true)
}
// Intersects returns true the circle intersects other object
func (g *Circle) Intersects(obj Object) bool {
switch other := obj.(type) {
case *Point:
return g.contains(other.Center(), true)
case *Circle:
return other.Distance(g) <= (other.meters + g.meters)
case *LineString:
for i := 0; i < other.base.NumSegments(); i++ {
if g.intersectsSegment(other.base.SegmentAt(i)) {
return true
}
}
return false
case Collection:
for _, p := range other.Children() {
if g.Intersects(p) {
return true
}
}
return false
default:
// No simple cases, so using polygon approximation.
return g.Object.Intersects(obj)
}
}