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

327 lines
6.5 KiB
Go

package geojson
import (
"github.com/tidwall/boxtree/d2"
"github.com/tidwall/geojson/geometry"
)
type collection struct {
children []Object
extra *extra
tree *d2.BoxTree
prect geometry.Rect
pempty bool
}
func (g *collection) Indexed() bool {
return g.tree != nil
}
func (g *collection) Children() []Object {
return g.children
}
// ForEach ...
func (g *collection) ForEach(iter func(geom Object) bool) bool {
for _, child := range g.children {
if !child.ForEach(iter) {
return false
}
}
return true
}
// Base ...
func (g *collection) Base() []Object {
return g.children
}
func (g *collection) Search(rect geometry.Rect, iter func(child Object) bool) {
if g.tree != nil {
g.tree.Search(
[]float64{rect.Min.X, rect.Min.Y},
[]float64{rect.Max.X, rect.Max.Y},
func(_, _ []float64, value interface{}) bool {
return iter(value.(Object))
},
)
} else {
for _, child := range g.children {
if child.Empty() {
continue
}
if child.Rect().IntersectsRect(rect) {
if !iter(child) {
break
}
}
}
}
}
// Empty ...
func (g *collection) Empty() bool {
return g.pempty
}
// Rect ...
func (g *collection) Rect() geometry.Rect {
return g.prect
}
// Center ...
func (g *collection) Center() geometry.Point {
return g.Rect().Center()
}
// AppendJSON ...
func (g *collection) AppendJSON(dst []byte) []byte {
// this should never be called
return append(dst, "null"...)
}
// JSON ...
func (g *collection) JSON() string {
return string(g.AppendJSON(nil))
}
// String ...
func (g *collection) String() string {
return string(g.AppendJSON(nil))
}
// Within ...
func (g *collection) Within(obj Object) bool {
return obj.Contains(g)
}
// Contains ...
func (g *collection) Contains(obj Object) bool {
if g.Empty() {
return false
}
// all of obj must be contained by any number of the collection children
var objContained bool
obj.ForEach(func(geom Object) bool {
if geom.Empty() {
// ignore empties
return true
}
var geomContained bool
g.Search(geom.Rect(), func(child Object) bool {
if child.Contains(geom) {
// found a child object that contains geom, end inner loop
geomContained = true
return false
}
return true
})
if !geomContained {
// unmark and quit the loop
objContained = false
return false
}
// mark that at least one geom is contained
objContained = true
return true
})
return objContained
}
func (g *collection) Spatial() Spatial { return g }
func (g *collection) WithinRect(rect geometry.Rect) bool {
if g.Empty() {
return false
}
var withinCount int
g.Search(rect, func(child Object) bool {
if child.Spatial().WithinRect(rect) {
withinCount++
return true
}
return false
})
return withinCount == len(g.children)
}
func (g *collection) WithinPoint(point geometry.Point) bool {
if g.Empty() {
return false
}
var withinCount int
g.Search(point.Rect(), func(child Object) bool {
if child.Spatial().WithinPoint(point) {
withinCount++
return true
}
return false
})
return withinCount == len(g.children)
}
func (g *collection) WithinLine(line *geometry.Line) bool {
if g.Empty() {
return false
}
var withinCount int
g.Search(line.Rect(), func(child Object) bool {
if child.Spatial().WithinLine(line) {
withinCount++
return true
}
return false
})
return withinCount == len(g.children)
}
func (g *collection) WithinPoly(poly *geometry.Poly) bool {
if g.Empty() {
return false
}
var withinCount int
g.Search(poly.Rect(), func(child Object) bool {
if child.Spatial().WithinPoly(poly) {
withinCount++
return true
}
return false
})
return withinCount == len(g.children)
}
// Intersects ...
func (g *collection) Intersects(obj Object) bool {
// check if any of obj intersects with any of collection
var intersects bool
obj.ForEach(func(geom Object) bool {
if geom.Empty() {
// ignore the empties
return true
}
g.Search(geom.Rect(), func(child Object) bool {
if child.Intersects(geom) {
intersects = true
return false
}
return true
})
if intersects {
return false
}
return true
})
return intersects
}
func (g *collection) IntersectsPoint(point geometry.Point) bool {
var intersects bool
g.Search(point.Rect(), func(child Object) bool {
if child.Spatial().IntersectsPoint(point) {
intersects = true
return false
}
return true
})
return intersects
}
func (g *collection) IntersectsRect(rect geometry.Rect) bool {
var intersects bool
g.Search(rect, func(child Object) bool {
if child.Spatial().IntersectsRect(rect) {
intersects = true
return false
}
return true
})
return intersects
}
func (g *collection) IntersectsLine(line *geometry.Line) bool {
var intersects bool
g.Search(line.Rect(), func(child Object) bool {
if child.Spatial().IntersectsLine(line) {
intersects = true
return false
}
return true
})
return intersects
}
func (g *collection) IntersectsPoly(poly *geometry.Poly) bool {
var intersects bool
g.Search(poly.Rect(), func(child Object) bool {
if child.Spatial().IntersectsPoly(poly) {
intersects = true
return false
}
return true
})
return intersects
}
// NumPoints ...
func (g *collection) NumPoints() int {
var n int
for _, child := range g.children {
n += child.NumPoints()
}
return n
}
func (g *collection) parseInitRectIndex(opts *ParseOptions) {
g.pempty = true
var count int
for _, child := range g.children {
if child.Empty() {
continue
}
if g.pempty && !child.Empty() {
g.pempty = false
}
if count == 0 {
g.prect = child.Rect()
} else {
if len(g.children) == 1 {
g.prect = child.Rect()
} else {
g.prect = unionRects(g.prect, child.Rect())
}
}
count++
}
if count > 0 && opts.IndexChildren != 0 && count >= opts.IndexChildren {
g.tree = new(d2.BoxTree)
for _, child := range g.children {
if child.Empty() {
continue
}
rect := child.Rect()
g.tree.Insert(
[]float64{rect.Min.X, rect.Min.Y},
[]float64{rect.Max.X, rect.Max.Y},
child,
)
}
}
}
// Distance ...
func (g *collection) Distance(obj Object) float64 {
return obj.Spatial().DistancePoint(g.Center())
}
func (g *collection) DistancePoint(point geometry.Point) float64 {
return geoDistancePoints(g.Center(), point)
}
func (g *collection) DistanceRect(rect geometry.Rect) float64 {
return geoDistancePoints(g.Center(), rect.Center())
}
func (g *collection) DistanceLine(line *geometry.Line) float64 {
return geoDistancePoints(g.Center(), line.Rect().Center())
}
func (g *collection) DistancePoly(poly *geometry.Poly) float64 {
return geoDistancePoints(g.Center(), poly.Rect().Center())
}