tidwall 6257ddba78 Faster point in polygon / GeoJSON updates
The big change is that the GeoJSON package has been completely
rewritten to fix a few of geometry calculation bugs, increase
performance, and to better follow the GeoJSON spec RFC 7946.

GeoJSON updates

- A LineString now requires at least two points.
- All json members, even foreign, now persist with the object.
- The bbox member persists too but is no longer used for geometry
  calculations. This is change in behavior. Previously Tile38 would
  treat the bbox as the object's physical rectangle.
- Corrections to geometry intersects and within calculations.

Faster spatial queries

- The performance of Point-in-polygon and object intersect operations
  are greatly improved for complex polygons and line strings. It went
  from O(n) to roughly O(log n).
- The same for all collection types with many children, including
  FeatureCollection, GeometryCollection, MultiPoint, MultiLineString,
  and MultiPolygon.

Codebase changes

- The pkg directory has been renamed to internal
- The GeoJSON internal package has been moved to a seperate repo at
  https://github.com/tidwall/geojson. It's now vendored.

Please look out for higher memory usage for datasets using complex
shapes. A complex shape is one that has 64 or more points. For these
shapes it's expected that there will be increase of least 54 bytes per
point.
2018-10-13 04:30:48 -07:00

163 lines
3.5 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
// Line is a open series of points
type Line struct {
baseSeries
}
// NewLine creates a new Line
func NewLine(points []Point, index int) *Line {
line := new(Line)
line.baseSeries = makeSeries(points, true, false, index)
return line
}
// Move ...
func (line *Line) Move(deltaX, deltaY float64) *Line {
if line == nil {
return nil
}
nline := new(Line)
nline.baseSeries = *line.baseSeries.Move(deltaX, deltaY).(*baseSeries)
return nline
}
// ContainsPoint ...
func (line *Line) ContainsPoint(point Point) bool {
if line == nil {
return false
}
contains := false
line.Search(Rect{point, point}, func(seg Segment, index int) bool {
if seg.Raycast(point).On {
contains = true
return false
}
return true
})
return contains
}
// IntersectsPoint ...
func (line *Line) IntersectsPoint(point Point) bool {
if line == nil {
return false
}
return line.ContainsPoint(point)
}
// ContainsRect ...
func (line *Line) ContainsRect(rect Rect) bool {
if line == nil {
return false
}
// Convert rect into a poly
return line.ContainsPoly(&Poly{Exterior: rect})
}
// IntersectsRect ...
func (line *Line) IntersectsRect(rect Rect) bool {
if line == nil {
return false
}
return rect.IntersectsLine(line)
}
// ContainsLine ...
func (line *Line) ContainsLine(other *Line) bool {
if line == nil || other == nil || line.Empty() || other.Empty() {
return false
}
// locate the first "other" segment that contains the first "line" segment.
lineNumSegments := line.NumSegments()
segIdx := -1
for j := 0; j < lineNumSegments; j++ {
if line.SegmentAt(j).ContainsSegment(other.SegmentAt(0)) {
segIdx = j
break
}
}
if segIdx == -1 {
return false
}
otherNumSegments := other.NumSegments()
for i := 1; i < otherNumSegments; i++ {
lineSeg := line.SegmentAt(segIdx)
otherSeg := other.SegmentAt(i)
if lineSeg.ContainsSegment(otherSeg) {
continue
}
if otherSeg.A == lineSeg.A {
// reverse it
if segIdx == 0 {
return false
}
segIdx--
i--
} else if otherSeg.A == lineSeg.B {
// forward it
if segIdx == lineNumSegments-1 {
return false
}
segIdx++
i--
}
}
return true
}
// IntersectsLine ...
func (line *Line) IntersectsLine(other *Line) bool {
if line == nil || other == nil || line.Empty() || other.Empty() {
return false
}
if !line.Rect().IntersectsRect(other.Rect()) {
return false
}
if line.NumPoints() > other.NumPoints() {
line, other = other, line
}
lineNumSegments := line.NumSegments()
for i := 0; i < lineNumSegments; i++ {
segA := line.SegmentAt(i)
var intersects bool
other.Search(segA.Rect(), func(segB Segment, _ int) bool {
if segA.IntersectsSegment(segB) {
intersects = true
return false
}
return true
})
if intersects {
return true
}
}
return false
}
// ContainsPoly ...
func (line *Line) ContainsPoly(poly *Poly) bool {
if line == nil || poly == nil || line.Empty() || poly.Empty() {
return false
}
rect := poly.Rect()
if rect.Min.X != rect.Max.X && rect.Min.Y != rect.Max.Y {
return false
}
// polygon can fit in a straight (vertial or horizontal) line
points := [2]Point{rect.Min, rect.Max}
var other Line
other.baseSeries.points = points[:]
other.baseSeries.rect = rect
return line.ContainsLine(&other)
}
// IntersectsPoly ...
func (line *Line) IntersectsPoly(poly *Poly) bool {
return poly.IntersectsLine(line)
}