tile38/vendor/github.com/tidwall/geojson/geometry/series.go

289 lines
6.7 KiB
Go

// Copyright 2018 Joshua J Baker. All rights reserved.
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.
package geometry
import "github.com/tidwall/boxtree/d2"
// DefaultIndex are the minumum number of points required before it makes
// sense to index the segments.
// 64 seems to be the sweet spot
const DefaultIndex = 64
// Series is just a series of points with utilities for efficiently accessing
// segments from rectangle queries, making stuff like point-in-polygon lookups
// very quick.
type Series interface {
Rect() Rect
Empty() bool
Convex() bool
Clockwise() bool
NumPoints() int
NumSegments() int
PointAt(index int) Point
SegmentAt(index int) Segment
Search(rect Rect, iter func(seg Segment, index int) bool)
Index() interface{}
}
func seriesCopyPoints(series Series) []Point {
points := make([]Point, series.NumPoints())
for i := 0; i < len(points); i++ {
points[i] = series.PointAt(i)
}
return points
}
// baseSeries is a concrete type containing all that is needed to make a Series.
type baseSeries struct {
closed bool // points create a closed shape
clockwise bool // points move clockwise
convex bool // points create a convex shape
rect Rect // minumum bounding rectangle
points []Point // original points
tree *d2.BoxTree // segment tree.
}
// makeSeries returns a processed baseSeries.
func makeSeries(points []Point, copyPoints, closed bool, index int) baseSeries {
var series baseSeries
series.closed = closed
if copyPoints {
series.points = make([]Point, len(points))
copy(series.points, points)
} else {
series.points = points
}
if index != 0 && len(points) >= int(index) {
series.tree = new(d2.BoxTree)
}
series.convex, series.rect, series.clockwise =
processPoints(points, closed, series.tree)
return series
}
// Index ...
func (series *baseSeries) Index() interface{} {
if series.tree == nil {
return nil
}
return series.tree
}
// Clockwise ...
func (series *baseSeries) Clockwise() bool {
return series.clockwise
}
func (series *baseSeries) Move(deltaX, deltaY float64) Series {
points := make([]Point, len(series.points))
for i := 0; i < len(series.points); i++ {
points[i].X = series.points[i].X + deltaX
points[i].Y = series.points[i].Y + deltaY
}
nseries := makeSeries(points, false, series.closed, 0)
if series.tree != nil {
nseries.buildTree()
}
return &nseries
}
// Empty returns true if the series does not take up space.
func (series *baseSeries) Empty() bool {
if series == nil {
return true
}
return (series.closed && len(series.points) < 3) || len(series.points) < 2
}
// Rect returns the series rectangle
func (series *baseSeries) Rect() Rect {
return series.rect
}
// Convex returns true if the points create a convex loop or linestring
func (series *baseSeries) Convex() bool {
return series.convex
}
// Closed return true if the shape is closed
func (series *baseSeries) Closed() bool {
return series.closed
}
// NumPoints returns the number of points in the series
func (series *baseSeries) NumPoints() int {
return len(series.points)
}
// PointAt returns the point at index
func (series *baseSeries) PointAt(index int) Point {
return series.points[index]
}
// Search finds a searches for segments that intersect the provided rectangle
func (series *baseSeries) Search(rect Rect, iter func(seg Segment, idx int) bool) {
if series.tree == nil {
n := series.NumSegments()
for i := 0; i < n; i++ {
seg := series.SegmentAt(i)
if seg.Rect().IntersectsRect(rect) {
if !iter(seg, i) {
return
}
}
}
} else {
series.tree.Search(
[]float64{rect.Min.X, rect.Min.Y},
[]float64{rect.Max.X, rect.Max.Y},
func(_, _ []float64, value interface{}) bool {
index := value.(int)
var seg Segment
seg.A = series.points[index]
if series.closed && index == len(series.points)-1 {
seg.B = series.points[0]
} else {
seg.B = series.points[index+1]
}
if !iter(seg, index) {
return false
}
return true
},
)
}
}
// NumSegments ...
func (series *baseSeries) NumSegments() int {
if series.closed {
if len(series.points) < 3 {
return 0
}
if series.points[len(series.points)-1] == series.points[0] {
return len(series.points) - 1
}
return len(series.points)
}
if len(series.points) < 2 {
return 0
}
return len(series.points) - 1
}
// SegmentAt ...
func (series *baseSeries) SegmentAt(index int) Segment {
var seg Segment
seg.A = series.points[index]
if index == len(series.points)-1 {
seg.B = series.points[0]
} else {
seg.B = series.points[index+1]
}
return seg
}
func (series *baseSeries) buildTree() {
if series.tree == nil {
series.tree = new(d2.BoxTree)
processPoints(series.points, series.closed, series.tree)
}
}
// processPoints tests if the ring is convex, calculates the outer
// rectangle, and inserts segments into a boxtree in one pass.
func processPoints(points []Point, closed bool, tree *d2.BoxTree) (
convex bool, rect Rect, clockwise bool,
) {
if (closed && len(points) < 3) || len(points) < 2 {
return
}
var concave bool
var dir int
var a, b, c Point
var segCount int
var cwc float64
if closed {
segCount = len(points)
} else {
segCount = len(points) - 1
}
for i := 0; i < len(points); i++ {
// process the segments for tree insertion
if tree != nil && i < segCount {
var seg Segment
seg.A = points[i]
if closed && i == len(points)-1 {
if seg.A == points[0] {
break
}
seg.B = points[0]
} else {
seg.B = points[i+1]
}
rect := seg.Rect()
tree.Insert(
[]float64{rect.Min.X, rect.Min.Y},
[]float64{rect.Max.X, rect.Max.Y}, i)
}
// process the rectangle inflation
if i == 0 {
rect = Rect{points[i], points[i]}
} else {
if points[i].X < rect.Min.X {
rect.Min.X = points[i].X
} else if points[i].X > rect.Max.X {
rect.Max.X = points[i].X
}
if points[i].Y < rect.Min.Y {
rect.Min.Y = points[i].Y
} else if points[i].Y > rect.Max.Y {
rect.Max.Y = points[i].Y
}
}
// gather some point positions for concave and clockwise detection
a = points[i]
if i == len(points)-1 {
b = points[0]
c = points[1]
} else if i == len(points)-2 {
b = points[i+1]
c = points[0]
} else {
b = points[i+1]
c = points[i+2]
}
// process the clockwise detection
cwc += (b.X - a.X) * (b.Y + a.Y)
// process the convex calculation
if concave {
continue
}
zCrossProduct := (b.X-a.X)*(c.Y-b.Y) - (b.Y-a.Y)*(c.X-b.X)
if dir == 0 {
if zCrossProduct < 0 {
dir = -1
} else if zCrossProduct > 0 {
dir = 1
}
} else if zCrossProduct < 0 {
if dir == 1 {
concave = true
}
} else if zCrossProduct > 0 {
if dir == -1 {
concave = true
}
}
}
return !concave, rect, cwc > 0
}