Added multiple indexing kinds

This commit is contained in:
tidwall 2018-10-21 19:08:56 -07:00
parent 8ee4c10862
commit 7cc4008442
28 changed files with 1000 additions and 230 deletions

4
Gopkg.lock generated
View File

@ -227,7 +227,7 @@
[[projects]] [[projects]]
branch = "master" branch = "master"
digest = "1:c27e7d5bfc68a4ad2166d1ae909917013a985ba4c8b5255b91bd253bf3ab5730" digest = "1:9fcbc64769ddda5230be91f192aa87aa8233f9776053108c1c89e1263f2c4a98"
name = "github.com/tidwall/geojson" name = "github.com/tidwall/geojson"
packages = [ packages = [
".", ".",
@ -235,7 +235,7 @@
"geometry", "geometry",
] ]
pruneopts = "" pruneopts = ""
revision = "5302514a34feb71743bf597938742b51831ba289" revision = "2d2f7893c3d92f36cef52047d457573ed44d6f04"
[[projects]] [[projects]]
digest = "1:3ddca2bd5496c6922a2a9e636530e178a43c2a534ea6634211acdc7d10222794" digest = "1:3ddca2bd5496c6922a2a9e636530e178a43c2a534ea6634211acdc7d10222794"

View File

@ -34,7 +34,7 @@ func clipLineString(
var children []*geometry.Line var children []*geometry.Line
for _, points := range newPoints { for _, points := range newPoints {
children = append(children, children = append(children,
geometry.NewLine(points, geometry.DefaultIndex)) geometry.NewLine(points, nil))
} }
if len(children) == 1 { if len(children) == 1 {
return geojson.NewLineString(children[0]) return geojson.NewLineString(children[0])

View File

@ -30,7 +30,7 @@ func clipPolygon(
holes = newPoints[1:] holes = newPoints[1:]
} }
newPoly := geojson.NewPolygon( newPoly := geojson.NewPolygon(
geometry.NewPoly(exterior, holes, geometry.DefaultIndex), geometry.NewPoly(exterior, holes, nil),
) )
if newPoly.Empty() { if newPoly.Empty() {
return geojson.NewMultiPolygon(nil) return geojson.NewMultiPolygon(nil)

View File

@ -11,7 +11,7 @@ func clipRect(rect *geojson.Rect, clipper geojson.Object) geojson.Object {
for i := 0; i < len(points); i++ { for i := 0; i < len(points); i++ {
points[i] = base.PointAt(i) points[i] = base.PointAt(i)
} }
poly := geometry.NewPoly(points, nil, geometry.DefaultIndex) poly := geometry.NewPoly(points, nil, nil)
gPoly := geojson.NewPolygon(poly) gPoly := geojson.NewPolygon(poly)
return Clip(gPoly, clipper) return Clip(gPoly, clipper)
} }

View File

@ -19,6 +19,7 @@ import (
"github.com/tidwall/buntdb" "github.com/tidwall/buntdb"
"github.com/tidwall/geojson" "github.com/tidwall/geojson"
"github.com/tidwall/geojson/geometry"
"github.com/tidwall/resp" "github.com/tidwall/resp"
"github.com/tidwall/tile38/core" "github.com/tidwall/tile38/core"
"github.com/tidwall/tile38/internal/collection" "github.com/tidwall/tile38/internal/collection"
@ -174,8 +175,33 @@ func ListenAndServeEx(host string, port int, dir string, ln *net.Listener, http
if err == nil { if err == nil {
c.geomParseOpts.IndexChildren = int(n) c.geomParseOpts.IndexChildren = int(n)
} }
log.Debugf("geom indexing: %d", c.geomParseOpts.IndexGeometry) indexKind := os.Getenv("T38IDXGEOMKIND")
log.Debugf("multi indexing: %d", c.geomParseOpts.IndexChildren) switch indexKind {
default:
log.Errorf("Unknown index kind: %s", indexKind)
case "":
case "None":
c.geomParseOpts.IndexGeometryKind = geometry.None
case "RTree":
c.geomParseOpts.IndexGeometryKind = geometry.RTree
case "RTreeCompressed":
c.geomParseOpts.IndexGeometryKind = geometry.RTreeCompressed
case "QuadTree":
c.geomParseOpts.IndexGeometryKind = geometry.QuadTree
case "QuadTreeCompressed":
c.geomParseOpts.IndexGeometryKind = geometry.QuadTreeCompressed
}
if c.geomParseOpts.IndexGeometryKind == geometry.None {
log.Debugf("Geom indexing: %s",
c.geomParseOpts.IndexGeometryKind,
)
} else {
log.Debugf("Geom indexing: %s (%d points)",
c.geomParseOpts.IndexGeometryKind,
c.geomParseOpts.IndexGeometry,
)
}
log.Debugf("Multi indexing: RTree (%d points)", c.geomParseOpts.IndexChildren)
// load the queue before the aof // load the queue before the aof
qdb, err := buntdb.Open(core.QueueFileName) qdb, err := buntdb.Open(core.QueueFileName)

View File

@ -131,7 +131,7 @@ func fenceMatch(
[]geometry.Point{ []geometry.Point{
details.oldObj.Center(), details.oldObj.Center(),
details.obj.Center(), details.obj.Center(),
}, geometry.DefaultIndex)) }, nil))
temp := false temp := false
if fence.cmd == "within" { if fence.cmd == "within" {
// because we are testing if the line croses the area we need to use // because we are testing if the line croses the area we need to use

View File

@ -37,12 +37,12 @@ func NewCircle(center geometry.Point, meters float64, steps int) *Circle {
points = append(points, geometry.Point{X: lon, Y: lat}) points = append(points, geometry.Point{X: lon, Y: lat})
i++ i++
} }
// TODO: account for the pole and antimerdian. In most cases only a polygon // TODO: account for the pole and antimerdian. In most cases only a
// is needed, but when the circle bounds passes the 90/180 lines, we need // polygon is needed, but when the circle bounds passes the 90/180
// to create a multipolygon // lines, we need to create a multipolygon
points = append(points, points[0]) points = append(points, points[0])
g.Object = NewPolygon( g.Object = NewPolygon(
geometry.NewPoly(points, nil, geometry.DefaultIndex), geometry.NewPoly(points, nil, geometry.DefaultIndexOptions),
) )
} }
return g return g

View File

@ -30,7 +30,7 @@ func P(x, y float64) Point {
return Point{x, y} return Point{x, y}
} }
func L(points ...Point) *Line { func L(points ...Point) *Line {
return NewLine(points, DefaultIndex) return NewLine(points, DefaultIndexOptions)
} }
var ( var (

File diff suppressed because one or more lines are too long

View File

@ -10,9 +10,9 @@ type Line struct {
} }
// NewLine creates a new Line // NewLine creates a new Line
func NewLine(points []Point, index int) *Line { func NewLine(points []Point, opts *IndexOptions) *Line {
line := new(Line) line := new(Line)
line.baseSeries = makeSeries(points, true, false, index) line.baseSeries = makeSeries(points, true, false, opts)
return line return line
} }

View File

@ -7,7 +7,7 @@ package geometry
import "testing" import "testing"
func TestLineNewLine(t *testing.T) { func TestLineNewLine(t *testing.T) {
line := NewLine(u1, DefaultIndex) line := NewLine(u1, DefaultIndexOptions)
expect(t, !line.Empty()) expect(t, !line.Empty())
} }
@ -43,12 +43,12 @@ func TestLineMove(t *testing.T) {
} }
func TestLineContainsPoint(t *testing.T) { func TestLineContainsPoint(t *testing.T) {
line := NewLine(u1, DefaultIndex) line := NewLine(u1, DefaultIndexOptions)
expect(t, line.ContainsPoint(P(0, 0))) expect(t, line.ContainsPoint(P(0, 0)))
expect(t, line.ContainsPoint(P(10, 10))) expect(t, line.ContainsPoint(P(10, 10)))
expect(t, line.ContainsPoint(P(0, 5))) expect(t, line.ContainsPoint(P(0, 5)))
expect(t, !line.ContainsPoint(P(5, 5))) expect(t, !line.ContainsPoint(P(5, 5)))
line = NewLine(v1, DefaultIndex) line = NewLine(v1, DefaultIndexOptions)
expect(t, line.ContainsPoint(P(0, 10))) expect(t, line.ContainsPoint(P(0, 10)))
expect(t, !line.ContainsPoint(P(0, 0))) expect(t, !line.ContainsPoint(P(0, 0)))
expect(t, line.ContainsPoint(P(5, 0))) expect(t, line.ContainsPoint(P(5, 0)))
@ -59,7 +59,7 @@ func TestLineContainsPoint(t *testing.T) {
} }
func TestLineIntersectsPoint(t *testing.T) { func TestLineIntersectsPoint(t *testing.T) {
line := NewLine(v1, DefaultIndex) line := NewLine(v1, DefaultIndexOptions)
expect(t, line.IntersectsPoint(P(0, 10))) expect(t, line.IntersectsPoint(P(0, 10)))
expect(t, !line.IntersectsPoint(P(0, 0))) expect(t, !line.IntersectsPoint(P(0, 0)))
expect(t, line.IntersectsPoint(P(5, 0))) expect(t, line.IntersectsPoint(P(5, 0)))
@ -70,10 +70,10 @@ func TestLineIntersectsPoint(t *testing.T) {
} }
func TestLineContainsRect(t *testing.T) { func TestLineContainsRect(t *testing.T) {
line := NewLine(v1, DefaultIndex) line := NewLine(v1, DefaultIndexOptions)
expect(t, !line.ContainsRect(R(0, 0, 10, 10))) expect(t, !line.ContainsRect(R(0, 0, 10, 10)))
expect(t, line.ContainsRect(R(0, 10, 0, 10))) expect(t, line.ContainsRect(R(0, 10, 0, 10)))
line = NewLine(u1, DefaultIndex) line = NewLine(u1, DefaultIndexOptions)
expect(t, line.ContainsRect(R(0, 0, 0, 10))) expect(t, line.ContainsRect(R(0, 0, 0, 10)))
line = nil line = nil
expect(t, !line.ContainsRect(Rect{})) expect(t, !line.ContainsRect(Rect{}))
@ -81,7 +81,7 @@ func TestLineContainsRect(t *testing.T) {
} }
func TestLineIntersectsRect(t *testing.T) { func TestLineIntersectsRect(t *testing.T) {
line := NewLine(v1, DefaultIndex) line := NewLine(v1, DefaultIndexOptions)
expect(t, line.IntersectsRect(R(0, 0, 10, 10))) expect(t, line.IntersectsRect(R(0, 0, 10, 10)))
expect(t, line.IntersectsRect(R(0, 0, 2.5, 5))) expect(t, line.IntersectsRect(R(0, 0, 2.5, 5)))
expect(t, !line.IntersectsRect(R(0, 0, 2.4, 5))) expect(t, !line.IntersectsRect(R(0, 0, 2.4, 5)))
@ -130,15 +130,15 @@ func TestLineIntersectsLine(t *testing.T) {
lns := [][]Point{u1, u2, u3, u4, v1, v2, v3, v4} lns := [][]Point{u1, u2, u3, u4, v1, v2, v3, v4}
for i := 0; i < len(lns); i++ { for i := 0; i < len(lns); i++ {
for j := 0; j < len(lns); j++ { for j := 0; j < len(lns); j++ {
expect(t, NewLine(lns[i], DefaultIndex).IntersectsLine( expect(t, NewLine(lns[i], DefaultIndexOptions).IntersectsLine(
NewLine(lns[j], DefaultIndex), NewLine(lns[j], DefaultIndexOptions),
)) ))
} }
} }
line := NewLine(u1, DefaultIndex) line := NewLine(u1, DefaultIndexOptions)
expect(t, !line.IntersectsLine(NewLine(nil, DefaultIndex))) expect(t, !line.IntersectsLine(NewLine(nil, DefaultIndexOptions)))
expect(t, !NewLine(nil, DefaultIndex).IntersectsLine(NewLine(nil, DefaultIndex))) expect(t, !NewLine(nil, DefaultIndexOptions).IntersectsLine(NewLine(nil, DefaultIndexOptions)))
expect(t, !NewLine(nil, DefaultIndex).IntersectsLine(line)) expect(t, !NewLine(nil, DefaultIndexOptions).IntersectsLine(line))
expect(t, line.IntersectsLine(line.Move(5, 0))) expect(t, line.IntersectsLine(line.Move(5, 0)))
expect(t, line.IntersectsLine(line.Move(10, 0))) expect(t, line.IntersectsLine(line.Move(10, 0)))
expect(t, !line.IntersectsLine(line.Move(11, 0))) expect(t, !line.IntersectsLine(line.Move(11, 0)))
@ -147,27 +147,27 @@ func TestLineIntersectsLine(t *testing.T) {
} }
func TestLineContainsPoly(t *testing.T) { func TestLineContainsPoly(t *testing.T) {
line := NewLine(u1, DefaultIndex) line := NewLine(u1, DefaultIndexOptions)
poly := NewPoly(octagon, nil, DefaultIndex) poly := NewPoly(octagon, nil, DefaultIndexOptions)
expect(t, !line.ContainsPoly(poly)) expect(t, !line.ContainsPoly(poly))
expect(t, line.ContainsPoly(NewPoly( expect(t, line.ContainsPoly(NewPoly(
[]Point{P(0, 10), P(0, 0), P(0, 10)}, []Point{P(0, 10), P(0, 0), P(0, 10)},
nil, DefaultIndex, nil, DefaultIndexOptions,
))) )))
expect(t, line.ContainsPoly(NewPoly( expect(t, line.ContainsPoly(NewPoly(
[]Point{P(0, 0), P(10, 0), P(0, 0)}, []Point{P(0, 0), P(10, 0), P(0, 0)},
nil, DefaultIndex, nil, DefaultIndexOptions,
))) )))
expect(t, !L().ContainsPoly(NewPoly( expect(t, !L().ContainsPoly(NewPoly(
[]Point{P(0, 0), P(10, 0), P(0, 0)}, []Point{P(0, 0), P(10, 0), P(0, 0)},
nil, DefaultIndex, nil, DefaultIndexOptions,
))) )))
expect(t, !line.ContainsPoly(NewPoly(nil, nil, DefaultIndex))) expect(t, !line.ContainsPoly(NewPoly(nil, nil, DefaultIndexOptions)))
} }
func TestLineIntersectsPoly(t *testing.T) { func TestLineIntersectsPoly(t *testing.T) {
line := NewLine(u1, DefaultIndex) line := NewLine(u1, DefaultIndexOptions)
poly := NewPoly(octagon, nil, DefaultIndex) poly := NewPoly(octagon, nil, DefaultIndexOptions)
expect(t, line.IntersectsPoly(poly)) expect(t, line.IntersectsPoly(poly))
expect(t, line.IntersectsPoly(poly.Move(5, 0))) expect(t, line.IntersectsPoly(poly.Move(5, 0)))
expect(t, line.IntersectsPoly(poly.Move(10, 0))) expect(t, line.IntersectsPoly(poly.Move(10, 0)))

View File

@ -17,10 +17,10 @@ func testBig(
) { ) {
N, T := 100000, 4 N, T := 100000, 4
simple := newRing(points, DefaultIndex) simple := newRing(points, DefaultIndexOptions)
simple.(*baseSeries).tree = nil simple.(*baseSeries).clearIndex()
tree := newRing(points, DefaultIndex) tree := newRing(points, DefaultIndexOptions)
tree.(*baseSeries).buildTree() tree.(*baseSeries).buildIndex()
pointOn := points[len(points)/2] pointOn := points[len(points)/2]
// ioutil.WriteFile(label+".svg", []byte(tools.SVG(tree.(*baseSeries).tree)), 0666) // ioutil.WriteFile(label+".svg", []byte(tools.SVG(tree.(*baseSeries).tree)), 0666)

View File

@ -74,17 +74,17 @@ func TestPointIntersectsLine(t *testing.T) {
} }
func TestPointContainsPoly(t *testing.T) { func TestPointContainsPoly(t *testing.T) {
expect(t, !P(5, 5).ContainsPoly(NewPoly(nil, nil, DefaultIndex))) expect(t, !P(5, 5).ContainsPoly(NewPoly(nil, nil, DefaultIndexOptions)))
expect(t, !P(5, 5).ContainsPoly(NewPoly([]Point{P(0, 0), P(10, 0)}, nil, DefaultIndex))) expect(t, !P(5, 5).ContainsPoly(NewPoly([]Point{P(0, 0), P(10, 0)}, nil, DefaultIndexOptions)))
expect(t, !P(5, 5).ContainsPoly(&Poly{Exterior: R(0, 0, 10, 10)})) expect(t, !P(5, 5).ContainsPoly(&Poly{Exterior: R(0, 0, 10, 10)}))
expect(t, P(5, 5).ContainsPoly(&Poly{Exterior: R(5, 5, 5, 5)})) expect(t, P(5, 5).ContainsPoly(&Poly{Exterior: R(5, 5, 5, 5)}))
} }
func TestPointIntersectsPoly(t *testing.T) { func TestPointIntersectsPoly(t *testing.T) {
octa := NewPoly(octagon, nil, DefaultIndex) octa := NewPoly(octagon, nil, DefaultIndexOptions)
concave1 := NewPoly(concave1, nil, DefaultIndex) concave1 := NewPoly(concave1, nil, DefaultIndexOptions)
expect(t, !P(5, 5).IntersectsPoly(NewPoly(nil, nil, DefaultIndex))) expect(t, !P(5, 5).IntersectsPoly(NewPoly(nil, nil, DefaultIndexOptions)))
expect(t, !P(5, 5).IntersectsPoly(NewPoly([]Point{P(0, 0), P(10, 0)}, nil, DefaultIndex))) expect(t, !P(5, 5).IntersectsPoly(NewPoly([]Point{P(0, 0), P(10, 0)}, nil, DefaultIndexOptions)))
expect(t, P(5, 5).IntersectsPoly(octa)) expect(t, P(5, 5).IntersectsPoly(octa))
expect(t, P(0, 5).IntersectsPoly(octa)) expect(t, P(0, 5).IntersectsPoly(octa))
expect(t, !P(1, 1).IntersectsPoly(octa)) expect(t, !P(1, 1).IntersectsPoly(octa))

View File

@ -11,13 +11,13 @@ type Poly struct {
} }
// NewPoly ... // NewPoly ...
func NewPoly(exterior []Point, holes [][]Point, index int) *Poly { func NewPoly(exterior []Point, holes [][]Point, opts *IndexOptions) *Poly {
poly := new(Poly) poly := new(Poly)
poly.Exterior = newRing(exterior, index) poly.Exterior = newRing(exterior, opts)
if len(holes) > 0 { if len(holes) > 0 {
poly.Holes = make([]Ring, len(holes)) poly.Holes = make([]Ring, len(holes))
for i := range holes { for i := range holes {
poly.Holes[i] = newRing(holes[i], index) poly.Holes[i] = newRing(holes[i], opts)
} }
} }
return poly return poly
@ -60,7 +60,7 @@ func (poly *Poly) Move(deltaX, deltaY float64) *Poly {
npoly.Exterior = Ring(series.Move(deltaX, deltaY)) npoly.Exterior = Ring(series.Move(deltaX, deltaY))
} else { } else {
nseries := makeSeries( nseries := makeSeries(
seriesCopyPoints(poly.Exterior), false, true, DefaultIndex) seriesCopyPoints(poly.Exterior), false, true, DefaultIndexOptions)
npoly.Exterior = Ring(nseries.Move(deltaX, deltaY)) npoly.Exterior = Ring(nseries.Move(deltaX, deltaY))
} }
if len(poly.Holes) > 0 { if len(poly.Holes) > 0 {
@ -70,7 +70,7 @@ func (poly *Poly) Move(deltaX, deltaY float64) *Poly {
npoly.Holes[i] = Ring(series.Move(deltaX, deltaY)) npoly.Holes[i] = Ring(series.Move(deltaX, deltaY))
} else { } else {
nseries := makeSeries( nseries := makeSeries(
seriesCopyPoints(hole), false, true, DefaultIndex) seriesCopyPoints(hole), false, true, DefaultIndexOptions)
npoly.Holes[i] = Ring(nseries.Move(deltaX, deltaY)) npoly.Holes[i] = Ring(nseries.Move(deltaX, deltaY))
} }
} }

View File

@ -9,19 +9,19 @@ import (
) )
func newPolyIndexed(exterior []Point, holes [][]Point) *Poly { func newPolyIndexed(exterior []Point, holes [][]Point) *Poly {
poly := NewPoly(exterior, holes, DefaultIndex) poly := NewPoly(exterior, holes, DefaultIndexOptions)
poly.Exterior.(*baseSeries).buildTree() poly.Exterior.(*baseSeries).buildIndex()
for _, hole := range poly.Holes { for _, hole := range poly.Holes {
hole.(*baseSeries).buildTree() hole.(*baseSeries).buildIndex()
} }
return poly return poly
} }
func newPolySimple(exterior []Point, holes [][]Point) *Poly { func newPolySimple(exterior []Point, holes [][]Point) *Poly {
poly := NewPoly(exterior, holes, DefaultIndex) poly := NewPoly(exterior, holes, DefaultIndexOptions)
poly.Exterior.(*baseSeries).tree = nil poly.Exterior.(*baseSeries).clearIndex()
for _, hole := range poly.Holes { for _, hole := range poly.Holes {
hole.(*baseSeries).tree = nil hole.(*baseSeries).clearIndex()
} }
return poly return poly
} }
@ -169,22 +169,22 @@ func TestPolyIntersectsLine(t *testing.T) {
func TestPolyContainsPoly(t *testing.T) { func TestPolyContainsPoly(t *testing.T) {
holes1 := [][]Point{[]Point{{4, 4}, {6, 4}, {6, 6}, {4, 6}, {4, 4}}} holes1 := [][]Point{[]Point{{4, 4}, {6, 4}, {6, 6}, {4, 6}, {4, 4}}}
holes2 := [][]Point{[]Point{{5, 4}, {7, 4}, {7, 6}, {5, 6}, {5, 4}}} holes2 := [][]Point{[]Point{{5, 4}, {7, 4}, {7, 6}, {5, 6}, {5, 4}}}
poly1 := NewPoly(octagon, holes1, DefaultIndex) poly1 := NewPoly(octagon, holes1, DefaultIndexOptions)
poly2 := NewPoly(octagon, holes2, DefaultIndex) poly2 := NewPoly(octagon, holes2, DefaultIndexOptions)
expect(t, !poly1.ContainsPoly(NewPoly(holes2[0], nil, DefaultIndex))) expect(t, !poly1.ContainsPoly(NewPoly(holes2[0], nil, DefaultIndexOptions)))
expect(t, !poly1.ContainsPoly(poly2)) expect(t, !poly1.ContainsPoly(poly2))
dualPolyTest(t, octagon, holes1, func(t *testing.T, poly *Poly) { dualPolyTest(t, octagon, holes1, func(t *testing.T, poly *Poly) {
expect(t, poly.ContainsPoly(poly1)) expect(t, poly.ContainsPoly(poly1))
expect(t, !poly.ContainsPoly(poly1.Move(1, 0))) expect(t, !poly.ContainsPoly(poly1.Move(1, 0)))
expect(t, poly.ContainsPoly(NewPoly(holes1[0], nil, DefaultIndex))) expect(t, poly.ContainsPoly(NewPoly(holes1[0], nil, DefaultIndexOptions)))
expect(t, !poly.ContainsPoly(NewPoly(holes2[0], nil, DefaultIndex))) expect(t, !poly.ContainsPoly(NewPoly(holes2[0], nil, DefaultIndexOptions)))
}) })
} }
func TestPolyClockwise(t *testing.T) { func TestPolyClockwise(t *testing.T) {
expect(t, !NewPoly(bowtie, nil, DefaultIndex).Clockwise()) expect(t, !NewPoly(bowtie, nil, DefaultIndexOptions).Clockwise())
var poly *Poly var poly *Poly
expect(t, !poly.Clockwise()) expect(t, !poly.Clockwise())
} }
@ -212,28 +212,28 @@ func Test369(t *testing.T) {
{-122.44109272956847, 37.7326855231885}, {-122.44109272956847, 37.7326855231885},
{-122.44109272956847, 37.731870943026074}, {-122.44109272956847, 37.731870943026074},
}, },
}, DefaultIndex) }, DefaultIndexOptions)
a := NewPoly([]Point{ a := NewPoly([]Point{
{-122.4408378, 37.7341129}, {-122.4408378, 37.7341129},
{-122.4408378, 37.733}, {-122.4408378, 37.733},
{-122.44, 37.733}, {-122.44, 37.733},
{-122.44, 37.7343129}, {-122.44, 37.7343129},
{-122.4408378, 37.7341129}, {-122.4408378, 37.7341129},
}, nil, DefaultIndex) }, nil, DefaultIndexOptions)
b := NewPoly([]Point{ b := NewPoly([]Point{
{-122.44091033935547, 37.731981251280985}, {-122.44091033935547, 37.731981251280985},
{-122.43994474411011, 37.731981251280985}, {-122.43994474411011, 37.731981251280985},
{-122.43994474411011, 37.73254976045042}, {-122.43994474411011, 37.73254976045042},
{-122.44091033935547, 37.73254976045042}, {-122.44091033935547, 37.73254976045042},
{-122.44091033935547, 37.731981251280985}, {-122.44091033935547, 37.731981251280985},
}, nil, DefaultIndex) }, nil, DefaultIndexOptions)
c := NewPoly([]Point{ c := NewPoly([]Point{
{-122.4408378, 37.7341129}, {-122.4408378, 37.7341129},
{-122.4408378, 37.733}, {-122.4408378, 37.733},
{-122.44, 37.733}, {-122.44, 37.733},
{-122.44, 37.7341129}, {-122.44, 37.7341129},
{-122.4408378, 37.7341129}, {-122.4408378, 37.7341129},
}, nil, DefaultIndex) }, nil, DefaultIndexOptions)
expect(t, polyHoles.IntersectsPoly(a)) expect(t, polyHoles.IntersectsPoly(a))
expect(t, !polyHoles.IntersectsPoly(b)) expect(t, !polyHoles.IntersectsPoly(b))
expect(t, !polyHoles.IntersectsPoly(c)) expect(t, !polyHoles.IntersectsPoly(c))

255
vendor/github.com/tidwall/geojson/geometry/quadtree.go generated vendored Normal file
View File

@ -0,0 +1,255 @@
package geometry
import "encoding/binary"
const qMaxItems = 16
const qMaxDepth = 16
type qNode struct {
split bool
items []uint32
quads [4]*qNode
}
func (n *qNode) insert(series *baseSeries, bounds, rect Rect, item, depth int) {
if depth == qMaxDepth {
// limit depth and insert now
n.items = append(n.items, uint32(item))
} else if n.split {
// qnode is split so try to insert into a quad
q := n.chooseQuad(bounds, rect)
if q == -1 {
// insert into overflow
n.items = append(n.items, uint32(item))
} else {
// insert into quad
qbounds := quadBounds(bounds, q)
if n.quads[q] == nil {
n.quads[q] = new(qNode)
}
n.quads[q].insert(series, qbounds, rect, item, depth+1)
}
} else if len(n.items) == qMaxItems {
// split qnode, keep current items in place
var nitems []uint32
for i := 0; i < len(n.items); i++ {
iitem := n.items[i]
irect := series.SegmentAt(int(iitem)).Rect()
q := n.chooseQuad(bounds, irect)
if q == -1 {
nitems = append(nitems, iitem)
} else {
qbounds := quadBounds(bounds, q)
if n.quads[q] == nil {
n.quads[q] = new(qNode)
}
n.quads[q].insert(series, qbounds, irect, int(iitem), depth+1)
}
}
n.items = nitems
n.split = true
n.insert(series, bounds, rect, item, depth)
} else {
n.items = append(n.items, uint32(item))
}
}
func (n *qNode) chooseQuad(bounds, rect Rect) int {
mid := Point{
X: (bounds.Min.X + bounds.Max.X) / 2,
Y: (bounds.Min.Y + bounds.Max.Y) / 2,
}
if rect.Max.X < mid.X {
if rect.Max.Y < mid.Y {
return 2
}
if rect.Min.Y < mid.Y {
return -1
}
return 0
}
if rect.Min.X < mid.X {
return -1
}
if rect.Max.Y < mid.Y {
return 3
}
if rect.Min.Y < mid.Y {
return -1
}
return 1
}
func quadBounds(bounds Rect, q int) (qbounds Rect) {
switch q {
case 0:
qbounds.Min.X = bounds.Min.X
qbounds.Min.Y = (bounds.Min.Y + bounds.Max.Y) / 2
qbounds.Max.X = (bounds.Min.X + bounds.Max.X) / 2
qbounds.Max.Y = bounds.Max.Y
case 1:
qbounds.Min.X = (bounds.Min.X + bounds.Max.X) / 2
qbounds.Min.Y = (bounds.Min.Y + bounds.Max.Y) / 2
qbounds.Max.X = bounds.Max.X
qbounds.Max.Y = bounds.Max.Y
case 2:
qbounds.Min.X = bounds.Min.X
qbounds.Min.Y = bounds.Min.Y
qbounds.Max.X = (bounds.Min.X + bounds.Max.X) / 2
qbounds.Max.Y = (bounds.Min.Y + bounds.Max.Y) / 2
case 3:
qbounds.Min.X = (bounds.Min.X + bounds.Max.X) / 2
qbounds.Min.Y = bounds.Min.Y
qbounds.Max.X = bounds.Max.X
qbounds.Max.Y = (bounds.Min.Y + bounds.Max.Y) / 2
}
return
}
func (n *qNode) search(
series *baseSeries,
bounds, rect Rect,
iter func(seg Segment, item int) bool,
) bool {
for _, item := range n.items {
seg := series.SegmentAt(int(item))
irect := seg.Rect()
if irect.IntersectsRect(rect) {
if !iter(seg, int(item)) {
return false
}
}
}
if n.split {
for q := 0; q < 4; q++ {
if n.quads[q] != nil {
qbounds := quadBounds(bounds, q)
if qbounds.IntersectsRect(rect) {
if !n.quads[q].search(series, qbounds, rect, iter) {
return false
}
}
}
}
}
return true
}
func numBytes(n uint32) byte {
if n <= 0xFF {
return 1
}
if n <= 0xFFFF {
return 2
}
return 4
}
func appendNum(dst []byte, num uint32, ibytes byte) []byte {
switch ibytes {
case 1:
dst = append(dst, byte(num))
case 2:
dst = append(dst, 0, 0)
binary.LittleEndian.PutUint16(dst[len(dst)-2:], uint16(num))
default:
dst = append(dst, 0, 0, 0, 0)
binary.LittleEndian.PutUint32(dst[len(dst)-4:], uint32(num))
}
return dst
}
func readNum(data []byte, ibytes byte) uint32 {
switch ibytes {
case 1:
return uint32(data[0])
case 2:
return uint32(binary.LittleEndian.Uint16(data))
default:
return binary.LittleEndian.Uint32(data)
}
}
func (n *qNode) compress(dst []byte, bounds Rect) []byte {
ibytes := numBytes(uint32(len(n.items)))
for i := 0; i < len(n.items); i++ {
ibytes2 := numBytes(n.items[i])
if ibytes2 > ibytes {
ibytes = ibytes2
}
}
dst = append(dst, ibytes)
dst = appendNum(dst, uint32(len(n.items)), ibytes)
for i := 0; i < len(n.items); i++ {
dst = appendNum(dst, n.items[i], ibytes)
}
if !n.split {
dst = append(dst, 0)
return dst
}
// store the quads
dst = append(dst, 1)
// first make the address space
var mark [4]int
for q := 0; q < 4; q++ {
if n.quads[q] == nil {
// no quad, no address
dst = append(dst, 0)
} else {
// yes quad, plus addres
dst = append(dst, 1)
mark[q] = len(dst)
dst = append(dst, 0, 0, 0, 0)
}
}
// next add each quad
for q := 0; q < 4; q++ {
if n.quads[q] != nil {
binary.LittleEndian.PutUint32(dst[mark[q]:], uint32(len(dst)))
dst = n.quads[q].compress(dst, quadBounds(bounds, q))
}
}
return dst
}
func qCompressSearch(
data []byte,
addr int,
series *baseSeries,
bounds, rect Rect,
iter func(seg Segment, item int) bool,
) bool {
ibytes := data[addr]
addr++
nItems := int(readNum(data[addr:], ibytes))
addr += int(ibytes)
for i := 0; i < nItems; i++ {
item := int(readNum(data[addr:], ibytes))
addr += int(ibytes)
seg := series.SegmentAt(int(item))
irect := seg.Rect()
if irect.IntersectsRect(rect) {
if !iter(seg, int(item)) {
return false
}
}
}
split := data[addr] == 1
addr++
if split {
for q := 0; q < 4; q++ {
use := data[addr] == 1
addr++
if !use {
continue
}
naddr := int(binary.LittleEndian.Uint32(data[addr:]))
addr += 4
qbounds := quadBounds(bounds, q)
if qbounds.IntersectsRect(rect) {
if !qCompressSearch(data, naddr, series, qbounds, rect, iter) {
return false
}
}
}
}
return true
}

View File

@ -150,22 +150,22 @@ func TestRectIntersectsLine(t *testing.T) {
} }
func TestRectContainsPoly(t *testing.T) { func TestRectContainsPoly(t *testing.T) {
oct := NewPoly(octagon, nil, DefaultIndex) oct := NewPoly(octagon, nil, DefaultIndexOptions)
expect(t, R(0, 0, 10, 10).ContainsPoly(oct)) expect(t, R(0, 0, 10, 10).ContainsPoly(oct))
expect(t, !R(0, 0, 10, 10).ContainsPoly(oct.Move(1, 0))) expect(t, !R(0, 0, 10, 10).ContainsPoly(oct.Move(1, 0)))
expect(t, !R(0, 0, 10, 10).ContainsPoly(oct.Move(1, 1))) expect(t, !R(0, 0, 10, 10).ContainsPoly(oct.Move(1, 1)))
expect(t, !R(0, 0, 10, 10).ContainsPoly(NewPoly(nil, nil, DefaultIndex))) expect(t, !R(0, 0, 10, 10).ContainsPoly(NewPoly(nil, nil, DefaultIndexOptions)))
} }
func TestRectIntersectsPoly(t *testing.T) { func TestRectIntersectsPoly(t *testing.T) {
oct := NewPoly(octagon, nil, DefaultIndex) oct := NewPoly(octagon, nil, DefaultIndexOptions)
expect(t, R(0, 0, 10, 10).IntersectsPoly(oct)) expect(t, R(0, 0, 10, 10).IntersectsPoly(oct))
expect(t, R(0, 0, 10, 10).IntersectsPoly(oct.Move(1, 0))) expect(t, R(0, 0, 10, 10).IntersectsPoly(oct.Move(1, 0)))
expect(t, R(0, 0, 10, 10).IntersectsPoly(oct.Move(0, 1))) expect(t, R(0, 0, 10, 10).IntersectsPoly(oct.Move(0, 1)))
expect(t, !R(0, 0, 10, 10).IntersectsPoly(oct.Move(10, 10))) expect(t, !R(0, 0, 10, 10).IntersectsPoly(oct.Move(10, 10)))
expect(t, !R(0, 0, 10, 10).IntersectsPoly(oct.Move(11, 10))) expect(t, !R(0, 0, 10, 10).IntersectsPoly(oct.Move(11, 10)))
expect(t, !R(0, 0, 10, 10).IntersectsPoly(oct.Move(-11, 0))) expect(t, !R(0, 0, 10, 10).IntersectsPoly(oct.Move(-11, 0)))
expect(t, !R(0, 0, 10, 10).IntersectsPoly(NewPoly(nil, nil, DefaultIndex))) expect(t, !R(0, 0, 10, 10).IntersectsPoly(NewPoly(nil, nil, DefaultIndexOptions)))
} }
func TestRectClockwise(t *testing.T) { func TestRectClockwise(t *testing.T) {

View File

@ -13,8 +13,8 @@ const complexRingMinPoints = 16
// Ring ... // Ring ...
type Ring = Series type Ring = Series
func newRing(points []Point, index int) Ring { func newRing(points []Point, opts *IndexOptions) Ring {
series := makeSeries(points, true, true, index) series := makeSeries(points, true, true, opts)
return &series return &series
} }

View File

@ -10,16 +10,16 @@ import (
) )
func newRingXSimple(points []Point) Ring { func newRingXSimple(points []Point) Ring {
ring := newRing(points, DefaultIndex) ring := newRing(points, DefaultIndexOptions)
if ring.(*baseSeries).tree != nil { if ring.(*baseSeries).Index() != nil {
ring.(*baseSeries).tree = nil ring.(*baseSeries).clearIndex()
} }
return ring return ring
} }
func newRingXIndexed(points []Point) Ring { func newRingXIndexed(points []Point) Ring {
ring := newRing(points, DefaultIndex) ring := newRing(points, DefaultIndexOptions)
if ring.(*baseSeries).tree == nil { if ring.(*baseSeries).Index() == nil {
ring.(*baseSeries).buildTree() ring.(*baseSeries).buildIndex()
} }
return ring return ring
} }
@ -66,7 +66,7 @@ func TestRingXContainsPoint(t *testing.T) {
P(3, 4), P(1, 4), P(3, 4), P(1, 4),
P(0, 3), P(0, 0), P(0, 3), P(0, 0),
} }
ring := newRing(shape, DefaultIndex) ring := newRing(shape, DefaultIndexOptions)
expect(t, !ringContainsPoint(ring, P(0, 3.5), true).hit) expect(t, !ringContainsPoint(ring, P(0, 3.5), true).hit)
expect(t, !ringContainsPoint(ring, P(0.4, 3.5), true).hit) expect(t, !ringContainsPoint(ring, P(0.4, 3.5), true).hit)
expect(t, ringContainsPoint(ring, P(0.5, 3.5), true).hit) expect(t, ringContainsPoint(ring, P(0.5, 3.5), true).hit)
@ -97,7 +97,7 @@ func TestRingXIntersectsSegment(t *testing.T) {
t.Run("Cases", func(t *testing.T) { t.Run("Cases", func(t *testing.T) {
ring := newRing([]Point{ ring := newRing([]Point{
P(0, 0), P(4, 0), P(4, 4), P(0, 4), P(0, 0), P(0, 0), P(4, 0), P(4, 4), P(0, 4), P(0, 0),
}, DefaultIndex) }, DefaultIndexOptions)
t.Run("1", func(t *testing.T) { t.Run("1", func(t *testing.T) {
expect(t, ringIntersectsSegment(ring, S(2, 2, 4, 4), true)) expect(t, ringIntersectsSegment(ring, S(2, 2, 4, 4), true))
expect(t, ringIntersectsSegment(ring, S(2, 2, 4, 4), false)) expect(t, ringIntersectsSegment(ring, S(2, 2, 4, 4), false))
@ -145,42 +145,42 @@ func TestRingXIntersectsSegment(t *testing.T) {
t.Run("12", func(t *testing.T) { t.Run("12", func(t *testing.T) {
ring := newRing([]Point{ ring := newRing([]Point{
{0, 0}, {2, 0}, {4, 0}, {4, 4}, {0, 4}, {0, 0}, {0, 0}, {2, 0}, {4, 0}, {4, 4}, {0, 4}, {0, 0},
}, DefaultIndex) }, DefaultIndexOptions)
expect(t, ringIntersectsSegment(ring, S(1, 0, 3, 0), true)) expect(t, ringIntersectsSegment(ring, S(1, 0, 3, 0), true))
expect(t, !ringIntersectsSegment(ring, S(1, 0, 3, 0), false)) expect(t, !ringIntersectsSegment(ring, S(1, 0, 3, 0), false))
}) })
t.Run("13", func(t *testing.T) { t.Run("13", func(t *testing.T) {
ring := newRing([]Point{ ring := newRing([]Point{
{0, 0}, {4, 0}, {4, 4}, {2, 4}, {0, 4}, {0, 0}, {0, 0}, {4, 0}, {4, 4}, {2, 4}, {0, 4}, {0, 0},
}, DefaultIndex) }, DefaultIndexOptions)
expect(t, ringIntersectsSegment(ring, S(0, 4, 4, 4), true)) expect(t, ringIntersectsSegment(ring, S(0, 4, 4, 4), true))
expect(t, !ringIntersectsSegment(ring, S(0, 4, 4, 4), false)) expect(t, !ringIntersectsSegment(ring, S(0, 4, 4, 4), false))
}) })
t.Run("14", func(t *testing.T) { t.Run("14", func(t *testing.T) {
ring := newRing([]Point{ ring := newRing([]Point{
{0, 0}, {4, 0}, {4, 4}, {2, 4}, {0, 4}, {0, 0}, {0, 0}, {4, 0}, {4, 4}, {2, 4}, {0, 4}, {0, 0},
}, DefaultIndex) }, DefaultIndexOptions)
expect(t, ringIntersectsSegment(ring, S(-1, -2, 0, 0), true)) expect(t, ringIntersectsSegment(ring, S(-1, -2, 0, 0), true))
expect(t, !ringIntersectsSegment(ring, S(-1, -2, 0, 0), false)) expect(t, !ringIntersectsSegment(ring, S(-1, -2, 0, 0), false))
}) })
t.Run("15", func(t *testing.T) { t.Run("15", func(t *testing.T) {
ring := newRing([]Point{ ring := newRing([]Point{
{0, 0}, {4, 0}, {4, 4}, {2, 4}, {0, 4}, {0, 0}, {0, 0}, {4, 0}, {4, 4}, {2, 4}, {0, 4}, {0, 0},
}, DefaultIndex) }, DefaultIndexOptions)
expect(t, ringIntersectsSegment(ring, S(0, 4, 5, 4), true)) expect(t, ringIntersectsSegment(ring, S(0, 4, 5, 4), true))
expect(t, !ringIntersectsSegment(ring, S(0, 4, 5, 4), false)) expect(t, !ringIntersectsSegment(ring, S(0, 4, 5, 4), false))
}) })
t.Run("16", func(t *testing.T) { t.Run("16", func(t *testing.T) {
ring := newRing([]Point{ ring := newRing([]Point{
{0, 0}, {4, 0}, {4, 4}, {2, 4}, {0, 4}, {0, 0}, {0, 0}, {4, 0}, {4, 4}, {2, 4}, {0, 4}, {0, 0},
}, DefaultIndex) }, DefaultIndexOptions)
expect(t, ringIntersectsSegment(ring, S(1, 4, 5, 4), true)) expect(t, ringIntersectsSegment(ring, S(1, 4, 5, 4), true))
expect(t, !ringIntersectsSegment(ring, S(1, 4, 5, 4), false)) expect(t, !ringIntersectsSegment(ring, S(1, 4, 5, 4), false))
}) })
t.Run("17", func(t *testing.T) { t.Run("17", func(t *testing.T) {
ring := newRing([]Point{ ring := newRing([]Point{
{0, 0}, {4, 0}, {4, 4}, {2, 4}, {0, 4}, {0, 0}, {0, 0}, {4, 0}, {4, 4}, {2, 4}, {0, 4}, {0, 0},
}, DefaultIndex) }, DefaultIndexOptions)
expect(t, ringIntersectsSegment(ring, S(1, 4, 4, 4), true)) expect(t, ringIntersectsSegment(ring, S(1, 4, 4, 4), true))
expect(t, !ringIntersectsSegment(ring, S(1, 4, 4, 4), false)) expect(t, !ringIntersectsSegment(ring, S(1, 4, 4, 4), false))
}) })
@ -815,7 +815,7 @@ func TestRingXContainsSegment(t *testing.T) {
ring := newRing([]Point{ ring := newRing([]Point{
P(0, 0), P(4, 0), P(4, 4), P(3, 4), P(0, 0), P(4, 0), P(4, 4), P(3, 4),
P(2, 3), P(1, 4), P(0, 4), P(0, 0), P(2, 3), P(1, 4), P(0, 4), P(0, 0),
}, DefaultIndex) }, DefaultIndexOptions)
t.Run("1", func(t *testing.T) { t.Run("1", func(t *testing.T) {
expect(t, !ringContainsSegment(ring, S(1.5, 3.5, 2.5, 3.5), true)) expect(t, !ringContainsSegment(ring, S(1.5, 3.5, 2.5, 3.5), true))
expect(t, !ringContainsSegment(ring, S(1.5, 3.5, 2.5, 3.5), false)) expect(t, !ringContainsSegment(ring, S(1.5, 3.5, 2.5, 3.5), false))
@ -840,7 +840,7 @@ func TestRingXContainsSegment(t *testing.T) {
ring := newRing([]Point{ ring := newRing([]Point{
P(0, 0), P(4, 0), P(4, 4), P(3, 4), P(0, 0), P(4, 0), P(4, 4), P(3, 4),
P(2, 5), P(1, 4), P(0, 4), P(0, 0), P(2, 5), P(1, 4), P(0, 4), P(0, 0),
}, DefaultIndex) }, DefaultIndexOptions)
expect(t, ringContainsSegment(ring, S(1.5, 4.5, 2.5, 4.5), true)) expect(t, ringContainsSegment(ring, S(1.5, 4.5, 2.5, 4.5), true))
expect(t, !ringContainsSegment(ring, S(1.5, 4.5, 2.5, 4.5), false)) expect(t, !ringContainsSegment(ring, S(1.5, 4.5, 2.5, 4.5), false))
}) })
@ -849,7 +849,7 @@ func TestRingXContainsSegment(t *testing.T) {
P(0, 0), P(4, 0), P(4, 4), P(3, 4), P(0, 0), P(4, 0), P(4, 4), P(3, 4),
P(2.5, 3), P(2, 4), P(1.5, 3), P(2.5, 3), P(2, 4), P(1.5, 3),
P(1, 4), P(0, 4), P(0, 0), P(1, 4), P(0, 4), P(0, 0),
}, DefaultIndex) }, DefaultIndexOptions)
expect(t, !ringContainsSegment(ring, S(1.25, 3.5, 2.75, 3.5), true)) expect(t, !ringContainsSegment(ring, S(1.25, 3.5, 2.75, 3.5), true))
expect(t, !ringContainsSegment(ring, S(1.25, 3.5, 2.75, 3.5), false)) expect(t, !ringContainsSegment(ring, S(1.25, 3.5, 2.75, 3.5), false))
}) })
@ -858,7 +858,7 @@ func TestRingXContainsSegment(t *testing.T) {
P(0, 0), P(4, 0), P(4, 4), P(3, 4), P(0, 0), P(4, 0), P(4, 4), P(3, 4),
P(2.5, 5), P(2, 4), P(1.5, 5), P(2.5, 5), P(2, 4), P(1.5, 5),
P(1, 4), P(0, 4), P(0, 0), P(1, 4), P(0, 4), P(0, 0),
}, DefaultIndex) }, DefaultIndexOptions)
expect(t, !ringContainsSegment(ring, S(1.25, 4.5, 2.75, 4.5), true)) expect(t, !ringContainsSegment(ring, S(1.25, 4.5, 2.75, 4.5), true))
expect(t, !ringContainsSegment(ring, S(1.25, 4.5, 2.75, 4.5), false)) expect(t, !ringContainsSegment(ring, S(1.25, 4.5, 2.75, 4.5), false))
}) })
@ -882,14 +882,14 @@ func TestRingXContainsSegment(t *testing.T) {
ring := newRing([]Point{ ring := newRing([]Point{
P(0, 0), P(2, 0), P(4, 0), P(4, 4), P(3, 4), P(0, 0), P(2, 0), P(4, 0), P(4, 4), P(3, 4),
P(2, 3), P(1, 4), P(0, 4), P(0, 0), P(2, 3), P(1, 4), P(0, 4), P(0, 0),
}, DefaultIndex) }, DefaultIndexOptions)
expect(t, ringContainsSegment(ring, S(1, 0, 3, 0), true)) expect(t, ringContainsSegment(ring, S(1, 0, 3, 0), true))
expect(t, !ringContainsSegment(ring, S(1, 0, 3, 0), false)) expect(t, !ringContainsSegment(ring, S(1, 0, 3, 0), false))
}) })
t.Run("14", func(t *testing.T) { t.Run("14", func(t *testing.T) {
ring := newRing([]Point{ ring := newRing([]Point{
P(0, 0), P(4, 0), P(2, 2), P(0, 4), P(0, 0), P(0, 0), P(4, 0), P(2, 2), P(0, 4), P(0, 0),
}, DefaultIndex) }, DefaultIndexOptions)
expect(t, ringContainsSegment(ring, S(1, 3, 3, 1), true)) expect(t, ringContainsSegment(ring, S(1, 3, 3, 1), true))
expect(t, ringContainsSegment(ring, S(3, 1, 1, 3), true)) expect(t, ringContainsSegment(ring, S(3, 1, 1, 3), true))
expect(t, !ringContainsSegment(ring, S(1, 3, 3, 1), false)) expect(t, !ringContainsSegment(ring, S(1, 3, 3, 1), false))
@ -908,24 +908,24 @@ func TestRingXContainsSegment(t *testing.T) {
func TestRingXContainsRing(t *testing.T) { func TestRingXContainsRing(t *testing.T) {
t.Run("Empty", func(t *testing.T) { t.Run("Empty", func(t *testing.T) {
expect(t, !ringContainsRing(newRing(nil, DefaultIndex), R(0, 0, 1, 1), true)) expect(t, !ringContainsRing(newRing(nil, DefaultIndexOptions), R(0, 0, 1, 1), true))
expect(t, !ringContainsRing(R(0, 0, 1, 1), newRing(nil, DefaultIndex), true)) expect(t, !ringContainsRing(R(0, 0, 1, 1), newRing(nil, DefaultIndexOptions), true))
}) })
t.Run("Exact", func(t *testing.T) { t.Run("Exact", func(t *testing.T) {
expect(t, ringContainsRing(R(0, 0, 1, 1), R(0, 0, 1, 1), true)) expect(t, ringContainsRing(R(0, 0, 1, 1), R(0, 0, 1, 1), true))
expect(t, !ringContainsRing(R(0, 0, 1, 1), R(0, 0, 1, 1), false)) expect(t, !ringContainsRing(R(0, 0, 1, 1), R(0, 0, 1, 1), false))
expect(t, ringContainsRing(newRing(concave1, DefaultIndex), newRing(concave1, DefaultIndex), true)) expect(t, ringContainsRing(newRing(concave1, DefaultIndexOptions), newRing(concave1, DefaultIndexOptions), true))
expect(t, !ringContainsRing(newRing(concave1, DefaultIndex), newRing(concave1, DefaultIndex), false)) expect(t, !ringContainsRing(newRing(concave1, DefaultIndexOptions), newRing(concave1, DefaultIndexOptions), false))
expect(t, ringContainsRing(newRing(octagon, DefaultIndex), newRing(octagon, DefaultIndex), true)) expect(t, ringContainsRing(newRing(octagon, DefaultIndexOptions), newRing(octagon, DefaultIndexOptions), true))
expect(t, !ringContainsRing(newRing(octagon, DefaultIndex), newRing(octagon, DefaultIndex), false)) expect(t, !ringContainsRing(newRing(octagon, DefaultIndexOptions), newRing(octagon, DefaultIndexOptions), false))
}) })
t.Run("Cases", func(t *testing.T) { t.Run("Cases", func(t *testing.T) {
// concave // concave
ring := newRing([]Point{ ring := newRing([]Point{
P(0, 0), P(4, 0), P(4, 4), P(3, 4), P(0, 0), P(4, 0), P(4, 4), P(3, 4),
P(2, 3), P(1, 4), P(0, 4), P(0, 0), P(2, 3), P(1, 4), P(0, 4), P(0, 0),
}, DefaultIndex) }, DefaultIndexOptions)
t.Run("1", func(t *testing.T) { t.Run("1", func(t *testing.T) {
expect(t, ringContainsRing(ring, R(1, 1, 3, 2), true)) expect(t, ringContainsRing(ring, R(1, 1, 3, 2), true))
expect(t, ringContainsRing(ring, R(1, 1, 3, 2), false)) expect(t, ringContainsRing(ring, R(1, 1, 3, 2), false))
@ -951,7 +951,7 @@ func TestRingXContainsRing(t *testing.T) {
P(0, 0), P(4, 0), P(4, 4), P(0, 0), P(4, 0), P(4, 4),
P(3, 4), P(2, 5), P(1, 4), P(3, 4), P(2, 5), P(1, 4),
P(0, 4), P(0, 0), P(0, 4), P(0, 0),
}, DefaultIndex) }, DefaultIndexOptions)
t.Run("6", func(t *testing.T) { t.Run("6", func(t *testing.T) {
expect(t, ringContainsRing(ring, R(1, 2, 3, 3), true)) expect(t, ringContainsRing(ring, R(1, 2, 3, 3), true))
expect(t, ringContainsRing(ring, R(1, 2, 3, 3), false)) expect(t, ringContainsRing(ring, R(1, 2, 3, 3), false))
@ -968,7 +968,7 @@ func TestRingXContainsRing(t *testing.T) {
ring = newRing([]Point{ ring = newRing([]Point{
P(0, 0), P(2, 0), P(4, 0), P(4, 4), P(3, 4), P(0, 0), P(2, 0), P(4, 0), P(4, 4), P(3, 4),
P(2, 5), P(1, 4), P(0, 4), P(0, 0), P(2, 5), P(1, 4), P(0, 4), P(0, 0),
}, DefaultIndex) }, DefaultIndexOptions)
expect(t, ringContainsRing(ring, R(1, 0, 3, 1), true)) expect(t, ringContainsRing(ring, R(1, 0, 3, 1), true))
expect(t, !ringContainsRing(ring, R(1, 0, 3, 1), false)) expect(t, !ringContainsRing(ring, R(1, 0, 3, 1), false))
}) })
@ -976,13 +976,13 @@ func TestRingXContainsRing(t *testing.T) {
ring = newRing([]Point{ ring = newRing([]Point{
P(0, 0), P(4, 0), P(4, 3), P(2, 4), P(0, 0), P(4, 0), P(4, 3), P(2, 4),
P(0, 3), P(0, 0), P(0, 3), P(0, 0),
}, DefaultIndex) }, DefaultIndexOptions)
expect(t, ringContainsRing(ring, R(1, 1, 3, 2), true)) expect(t, ringContainsRing(ring, R(1, 1, 3, 2), true))
expect(t, ringContainsRing(ring, R(1, 1, 3, 2), false)) expect(t, ringContainsRing(ring, R(1, 1, 3, 2), false))
}) })
ring = newRing([]Point{ ring = newRing([]Point{
P(0, 0), P(4, 0), P(4, 3), P(3, 4), P(1, 4), P(0, 3), P(0, 0), P(0, 0), P(4, 0), P(4, 3), P(3, 4), P(1, 4), P(0, 3), P(0, 0),
}, DefaultIndex) }, DefaultIndexOptions)
t.Run("11", func(t *testing.T) { t.Run("11", func(t *testing.T) {
expect(t, ringContainsRing(ring, R(1, 1, 3, 2), true)) expect(t, ringContainsRing(ring, R(1, 1, 3, 2), true))
expect(t, ringContainsRing(ring, R(1, 1, 3, 2), false)) expect(t, ringContainsRing(ring, R(1, 1, 3, 2), false))
@ -1024,10 +1024,10 @@ func TestRingXContainsRing(t *testing.T) {
t.Run("Identical", func(t *testing.T) { t.Run("Identical", func(t *testing.T) {
shapes := []Ring{ shapes := []Ring{
R(0, 0, 10, 10), R(0, 0, 10, 10),
newRing(octagon, DefaultIndex), newRing(octagon, DefaultIndexOptions),
newRing(concave1, DefaultIndex), newRing(concave2, DefaultIndex), newRing(concave1, DefaultIndexOptions), newRing(concave2, DefaultIndexOptions),
newRing(concave3, DefaultIndex), newRing(concave4, DefaultIndex), newRing(concave3, DefaultIndexOptions), newRing(concave4, DefaultIndexOptions),
newRing(ri, DefaultIndex), newRing(ri, DefaultIndexOptions),
} }
for i, shape := range shapes { for i, shape := range shapes {
t.Run(fmt.Sprintf("Shape%d", i), func(t *testing.T) { t.Run(fmt.Sprintf("Shape%d", i), func(t *testing.T) {
@ -1038,7 +1038,7 @@ func TestRingXContainsRing(t *testing.T) {
t.Run("Big", func(t *testing.T) { t.Run("Big", func(t *testing.T) {
// use rhode island // use rhode island
ring := newRing(ri, DefaultIndex) ring := newRing(ri, DefaultIndexOptions)
expect(t, ringContainsRing(ring.Rect(), ring, true)) expect(t, ringContainsRing(ring.Rect(), ring, true))
expect(t, ringContainsRing(ring, ring, true)) expect(t, ringContainsRing(ring, ring, true))
@ -1055,15 +1055,15 @@ func TestRingXIntersectsRing(t *testing.T) {
return t1 return t1
} }
t.Run("Empty", func(t *testing.T) { t.Run("Empty", func(t *testing.T) {
expect(t, !intersectsBothWays(newRing(nil, DefaultIndex), R(0, 0, 1, 1), true)) expect(t, !intersectsBothWays(newRing(nil, DefaultIndexOptions), R(0, 0, 1, 1), true))
expect(t, !intersectsBothWays(R(0, 0, 1, 1), newRing(nil, DefaultIndex), true)) expect(t, !intersectsBothWays(R(0, 0, 1, 1), newRing(nil, DefaultIndexOptions), true))
}) })
t.Run("Cases", func(t *testing.T) { t.Run("Cases", func(t *testing.T) {
// concave // concave
ring := newRing([]Point{ ring := newRing([]Point{
P(0, 0), P(4, 0), P(4, 4), P(3, 4), P(0, 0), P(4, 0), P(4, 4), P(3, 4),
P(2, 3), P(1, 4), P(0, 4), P(0, 0), P(2, 3), P(1, 4), P(0, 4), P(0, 0),
}, DefaultIndex) }, DefaultIndexOptions)
t.Run("1", func(t *testing.T) { t.Run("1", func(t *testing.T) {
expect(t, intersectsBothWays(ring, R(1, 1, 3, 2), true)) expect(t, intersectsBothWays(ring, R(1, 1, 3, 2), true))
expect(t, intersectsBothWays(ring, R(1, 1, 3, 2), false)) expect(t, intersectsBothWays(ring, R(1, 1, 3, 2), false))
@ -1089,7 +1089,7 @@ func TestRingXIntersectsRing(t *testing.T) {
P(0, 0), P(4, 0), P(4, 4), P(0, 0), P(4, 0), P(4, 4),
P(3, 4), P(2, 5), P(1, 4), P(3, 4), P(2, 5), P(1, 4),
P(0, 4), P(0, 0), P(0, 4), P(0, 0),
}, DefaultIndex) }, DefaultIndexOptions)
t.Run("6", func(t *testing.T) { t.Run("6", func(t *testing.T) {
expect(t, intersectsBothWays(ring, R(1, 2, 3, 3), true)) expect(t, intersectsBothWays(ring, R(1, 2, 3, 3), true))
expect(t, intersectsBothWays(ring, R(1, 2, 3, 3), false)) expect(t, intersectsBothWays(ring, R(1, 2, 3, 3), false))
@ -1106,7 +1106,7 @@ func TestRingXIntersectsRing(t *testing.T) {
ring = newRing([]Point{ ring = newRing([]Point{
P(0, 0), P(2, 0), P(4, 0), P(4, 4), P(3, 4), P(0, 0), P(2, 0), P(4, 0), P(4, 4), P(3, 4),
P(2, 5), P(1, 4), P(0, 4), P(0, 0), P(2, 5), P(1, 4), P(0, 4), P(0, 0),
}, DefaultIndex) }, DefaultIndexOptions)
expect(t, intersectsBothWays(ring, R(1, 0, 3, 1), true)) expect(t, intersectsBothWays(ring, R(1, 0, 3, 1), true))
expect(t, intersectsBothWays(ring, R(1, 0, 3, 1), false)) expect(t, intersectsBothWays(ring, R(1, 0, 3, 1), false))
}) })
@ -1114,13 +1114,13 @@ func TestRingXIntersectsRing(t *testing.T) {
ring = newRing([]Point{ ring = newRing([]Point{
P(0, 0), P(4, 0), P(4, 3), P(2, 4), P(0, 0), P(4, 0), P(4, 3), P(2, 4),
P(0, 3), P(0, 0), P(0, 3), P(0, 0),
}, DefaultIndex) }, DefaultIndexOptions)
expect(t, intersectsBothWays(ring, R(1, 1, 3, 2), true)) expect(t, intersectsBothWays(ring, R(1, 1, 3, 2), true))
expect(t, intersectsBothWays(ring, R(1, 1, 3, 2), false)) expect(t, intersectsBothWays(ring, R(1, 1, 3, 2), false))
}) })
ring = newRing([]Point{ ring = newRing([]Point{
P(0, 0), P(4, 0), P(4, 3), P(3, 4), P(1, 4), P(0, 3), P(0, 0), P(0, 0), P(4, 0), P(4, 3), P(3, 4), P(1, 4), P(0, 3), P(0, 0),
}, DefaultIndex) }, DefaultIndexOptions)
t.Run("11", func(t *testing.T) { t.Run("11", func(t *testing.T) {
expect(t, intersectsBothWays(ring, R(1, 1, 3, 2), true)) expect(t, intersectsBothWays(ring, R(1, 1, 3, 2), true))
expect(t, intersectsBothWays(ring, R(1, 1, 3, 2), false)) expect(t, intersectsBothWays(ring, R(1, 1, 3, 2), false))
@ -1167,12 +1167,12 @@ func TestRingXContainsLine(t *testing.T) {
P(0, 0), P(4, 0), P(4, 3), P(0, 0), P(4, 0), P(4, 3),
P(3, 4), P(1, 4), P(3, 4), P(1, 4),
P(0, 3), P(0, 0), P(0, 3), P(0, 0),
}, DefaultIndex) }, DefaultIndexOptions)
makeLine := func(start Point) *Line { makeLine := func(start Point) *Line {
return NewLine([]Point{ return NewLine([]Point{
start, start.Move(0, 1), start.Move(1, 1), start, start.Move(0, 1), start.Move(1, 1),
start.Move(1, 2), start.Move(2, 2), start.Move(1, 2), start.Move(2, 2),
}, DefaultIndex) }, DefaultIndexOptions)
} }
t.Run("1", func(t *testing.T) { t.Run("1", func(t *testing.T) {
expect(t, ringContainsLine(ring, makeLine(P(1, 1)), true)) expect(t, ringContainsLine(ring, makeLine(P(1, 1)), true))
@ -1285,12 +1285,12 @@ func TestRingXIntersectsLine(t *testing.T) {
P(0, 0), P(4, 0), P(4, 3), P(0, 0), P(4, 0), P(4, 3),
P(3, 4), P(1, 4), P(3, 4), P(1, 4),
P(0, 3), P(0, 0), P(0, 3), P(0, 0),
}, DefaultIndex) }, DefaultIndexOptions)
makeLine := func(start Point) *Line { makeLine := func(start Point) *Line {
return NewLine([]Point{ return NewLine([]Point{
start, start.Move(0, 1), start.Move(1, 1), start, start.Move(0, 1), start.Move(1, 1),
start.Move(1, 2), start.Move(2, 2), start.Move(1, 2), start.Move(2, 2),
}, DefaultIndex) }, DefaultIndexOptions)
} }
t.Run("1", func(t *testing.T) { t.Run("1", func(t *testing.T) {
expect(t, ringIntersectsLine(ring, makeLine(P(1, 1)), true)) expect(t, ringIntersectsLine(ring, makeLine(P(1, 1)), true))

384
vendor/github.com/tidwall/geojson/geometry/rtree.go generated vendored Normal file
View File

@ -0,0 +1,384 @@
package geometry
import (
"encoding/binary"
"math"
)
const rDims = 2
const rMaxEntries = 16
type rRect struct {
data interface{}
min, max [rDims]float64
}
type rNode struct {
count int
rects [rMaxEntries + 1]rRect
}
// rTree ...
type rTree struct {
height int
root rRect
count int
reinsert []rRect
}
func (r *rRect) expand(b *rRect) {
for i := 0; i < rDims; i++ {
if b.min[i] < r.min[i] {
r.min[i] = b.min[i]
}
if b.max[i] > r.max[i] {
r.max[i] = b.max[i]
}
}
}
// Insert inserts an item into the RTree
func (tr *rTree) Insert(min, max []float64, value interface{}) {
var item rRect
fit(min, max, value, &item)
tr.insert(&item)
}
func (tr *rTree) insert(item *rRect) {
if tr.root.data == nil {
fit(item.min[:], item.max[:], new(rNode), &tr.root)
}
grown := tr.root.insert(item, tr.height)
if grown {
tr.root.expand(item)
}
if tr.root.data.(*rNode).count == rMaxEntries+1 {
newRoot := new(rNode)
tr.root.splitLargestAxisEdgeSnap(&newRoot.rects[1])
newRoot.rects[0] = tr.root
newRoot.count = 2
tr.root.data = newRoot
tr.root.recalc()
tr.height++
}
tr.count++
}
func (r *rRect) chooseLeastEnlargement(b *rRect) int {
j, jenlargement, jarea := -1, 0.0, 0.0
n := r.data.(*rNode)
for i := 0; i < n.count; i++ {
// force inline
area := n.rects[i].max[0] - n.rects[i].min[0]
for j := 1; j < rDims; j++ {
area *= n.rects[i].max[j] - n.rects[i].min[j]
}
var enlargement float64
// force inline
enlargedArea := 1.0
for j := 0; j < len(n.rects[i].min); j++ {
if b.max[j] > n.rects[i].max[j] {
if b.min[j] < n.rects[i].min[j] {
enlargedArea *= b.max[j] - b.min[j]
} else {
enlargedArea *= b.max[j] - n.rects[i].min[j]
}
} else {
if b.min[j] < n.rects[i].min[j] {
enlargedArea *= n.rects[i].max[j] - b.min[j]
} else {
enlargedArea *= n.rects[i].max[j] - n.rects[i].min[j]
}
}
}
enlargement = enlargedArea - area
if j == -1 || enlargement < jenlargement {
j, jenlargement, jarea = i, enlargement, area
} else if enlargement == jenlargement {
if area < jarea {
j, jenlargement, jarea = i, enlargement, area
}
}
}
return j
}
func (r *rRect) recalc() {
n := r.data.(*rNode)
r.min = n.rects[0].min
r.max = n.rects[0].max
for i := 1; i < n.count; i++ {
r.expand(&n.rects[i])
}
}
// contains return struct when b is fully contained inside of n
func (r *rRect) contains(b *rRect) bool {
for i := 0; i < rDims; i++ {
if b.min[i] < r.min[i] || b.max[i] > r.max[i] {
return false
}
}
return true
}
func (r *rRect) largestAxis() (axis int, size float64) {
j, jsz := 0, 0.0
for i := 0; i < rDims; i++ {
sz := r.max[i] - r.min[i]
if i == 0 || sz > jsz {
j, jsz = i, sz
}
}
return j, jsz
}
func (r *rRect) splitLargestAxisEdgeSnap(right *rRect) {
axis, _ := r.largestAxis()
left := r
leftNode := left.data.(*rNode)
rightNode := new(rNode)
right.data = rightNode
var equals []rRect
for i := 0; i < leftNode.count; i++ {
minDist := leftNode.rects[i].min[axis] - left.min[axis]
maxDist := left.max[axis] - leftNode.rects[i].max[axis]
if minDist < maxDist {
// stay left
} else {
if minDist > maxDist {
// move to right
rightNode.rects[rightNode.count] = leftNode.rects[i]
rightNode.count++
} else {
// move to equals, at the end of the left array
equals = append(equals, leftNode.rects[i])
}
leftNode.rects[i] = leftNode.rects[leftNode.count-1]
leftNode.rects[leftNode.count-1].data = nil
leftNode.count--
i--
}
}
for _, b := range equals {
if leftNode.count < rightNode.count {
leftNode.rects[leftNode.count] = b
leftNode.count++
} else {
rightNode.rects[rightNode.count] = b
rightNode.count++
}
}
left.recalc()
right.recalc()
}
func (r *rRect) insert(item *rRect, height int) (grown bool) {
n := r.data.(*rNode)
if height == 0 {
n.rects[n.count] = *item
n.count++
grown = !r.contains(item)
return grown
}
// choose subtree
index := r.chooseLeastEnlargement(item)
child := &n.rects[index]
grown = child.insert(item, height-1)
if grown {
child.expand(item)
grown = !r.contains(item)
}
if child.data.(*rNode).count == rMaxEntries+1 {
child.splitLargestAxisEdgeSnap(&n.rects[n.count])
n.count++
}
return grown
}
// fit an external item into a rect type
func fit(min, max []float64, value interface{}, target *rRect) {
if max == nil {
max = min
}
if len(min) != len(max) {
panic("min/max dimension mismatch")
}
if len(min) != rDims {
panic("invalid number of dimensions")
}
for i := 0; i < rDims; i++ {
target.min[i] = min[i]
target.max[i] = max[i]
}
target.data = value
}
func (r *rRect) intersects(b *rRect) bool {
for i := 0; i < rDims; i++ {
if b.min[i] > r.max[i] || b.max[i] < r.min[i] {
return false
}
}
return true
}
func (r *rRect) search(
target *rRect, height int,
iter func(min, max []float64, value interface{}) bool,
) bool {
n := r.data.(*rNode)
if height == 0 {
for i := 0; i < n.count; i++ {
if target.intersects(&n.rects[i]) {
if !iter(n.rects[i].min[:], n.rects[i].max[:],
n.rects[i].data) {
return false
}
}
}
} else {
for i := 0; i < n.count; i++ {
if target.intersects(&n.rects[i]) {
if !n.rects[i].search(target, height-1, iter) {
return false
}
}
}
}
return true
}
func (tr *rTree) search(
target *rRect,
iter func(min, max []float64, value interface{}) bool,
) {
if tr.root.data == nil {
return
}
if target.intersects(&tr.root) {
tr.root.search(target, tr.height, iter)
}
}
// Search ...
func (tr *rTree) Search(
min, max []float64,
iter func(min, max []float64, value interface{}) bool,
) {
var target rRect
fit(min, max, nil, &target)
tr.search(&target, iter)
}
func appendFloat(dst []byte, num float64) []byte {
var buf [8]byte
binary.LittleEndian.PutUint64(buf[:], math.Float64bits(num))
return append(dst, buf[:]...)
}
func (r *rRect) compress(dst []byte, height int) []byte {
n := r.data.(*rNode)
dst = appendFloat(dst, r.min[0])
dst = appendFloat(dst, r.min[1])
dst = appendFloat(dst, r.max[0])
dst = appendFloat(dst, r.max[1])
dst = append(dst, byte(n.count))
if height == 0 {
var ibytes byte = 1
for i := 0; i < n.count; i++ {
ibytes2 := numBytes(uint32(n.rects[i].data.(int)))
if ibytes2 > ibytes {
ibytes = ibytes2
}
}
dst = append(dst, ibytes)
for i := 0; i < n.count; i++ {
dst = appendNum(dst, uint32(n.rects[i].data.(int)), ibytes)
}
return dst
}
mark := make([]int, n.count)
for i := 0; i < n.count; i++ {
mark[i] = len(dst)
dst = append(dst, 0, 0, 0, 0)
}
for i := 0; i < n.count; i++ {
binary.LittleEndian.PutUint32(dst[mark[i]:], uint32(len(dst)))
dst = n.rects[i].compress(dst, height-1)
}
return dst
}
func (tr *rTree) compress(dst []byte) []byte {
if tr.root.data == nil {
return dst
}
dst = append(dst, byte(tr.height))
return tr.root.compress(dst, tr.height)
}
func rCompressSearch(
data []byte,
addr int,
series *baseSeries,
rect Rect,
iter func(seg Segment, item int) bool,
) bool {
if int(addr) == len(data) {
return true
}
height := int(data[addr])
addr++
return rnCompressSearch(data, addr, series, rect, height, iter)
}
func rnCompressSearch(
data []byte,
addr int,
series *baseSeries,
rect Rect,
height int,
iter func(seg Segment, item int) bool,
) bool {
var nrect Rect
nrect.Min.X = math.Float64frombits(binary.LittleEndian.Uint64(data[addr:]))
addr += 8
nrect.Min.Y = math.Float64frombits(binary.LittleEndian.Uint64(data[addr:]))
addr += 8
nrect.Max.X = math.Float64frombits(binary.LittleEndian.Uint64(data[addr:]))
addr += 8
nrect.Max.Y = math.Float64frombits(binary.LittleEndian.Uint64(data[addr:]))
addr += 8
if !rect.IntersectsRect(nrect) {
return true
}
count := int(data[addr])
addr++
if height == 0 {
ibytes := data[addr]
addr++
for i := 0; i < count; i++ {
item := int(readNum(data[addr:], ibytes))
addr += int(ibytes)
seg := series.SegmentAt(int(item))
irect := seg.Rect()
if irect.IntersectsRect(rect) {
if !iter(seg, int(item)) {
return false
}
}
}
return true
}
for i := 0; i < count; i++ {
naddr := int(binary.LittleEndian.Uint32(data[addr:]))
addr += 4
if !rnCompressSearch(data, naddr, series, rect, height-1, iter) {
return false
}
}
return true
}

View File

@ -4,12 +4,53 @@
package geometry package geometry
import "github.com/tidwall/boxtree/d2" import (
"encoding/binary"
"reflect"
"unsafe"
)
// DefaultIndex are the minumum number of points required before it makes // IndexKind is the kind of index to use in the options.
// sense to index the segments. type IndexKind byte
// 64 seems to be the sweet spot
const DefaultIndex = 64 // IndexKind types
const (
None IndexKind = iota
RTree
RTreeCompressed
QuadTree
QuadTreeCompressed
)
func (kind IndexKind) String() string {
switch kind {
default:
return "Unknown"
case None:
return "None"
case RTree:
return "RTree"
case RTreeCompressed:
return "RTreeCompressed"
case QuadTree:
return "QuadTree"
case QuadTreeCompressed:
return "QuadTreeCompressed"
}
}
// IndexOptions are segment indexing options
type IndexOptions struct {
Kind IndexKind
MinPoints int
}
// DefaultIndexOptions ...
var DefaultIndexOptions = &IndexOptions{
Kind: QuadTree,
MinPoints: 64,
}
// Series is just a series of points with utilities for efficiently accessing // Series is just a series of points with utilities for efficiently accessing
// segments from rectangle queries, making stuff like point-in-polygon lookups // segments from rectangle queries, making stuff like point-in-polygon lookups
@ -40,13 +81,19 @@ type baseSeries struct {
closed bool // points create a closed shape closed bool // points create a closed shape
clockwise bool // points move clockwise clockwise bool // points move clockwise
convex bool // points create a convex shape convex bool // points create a convex shape
indexKind IndexKind // index kind
index interface{} // actual index
rect Rect // minumum bounding rectangle rect Rect // minumum bounding rectangle
points []Point // original points points []Point // original points
tree *d2.BoxTree // segment tree.
} }
// makeSeries returns a processed baseSeries. // makeSeries returns a processed baseSeries.
func makeSeries(points []Point, copyPoints, closed bool, index int) baseSeries { func makeSeries(
points []Point, copyPoints, closed bool, opts *IndexOptions,
) baseSeries {
if opts == nil {
opts = DefaultIndexOptions
}
var series baseSeries var series baseSeries
series.closed = closed series.closed = closed
if copyPoints { if copyPoints {
@ -55,20 +102,17 @@ func makeSeries(points []Point, copyPoints, closed bool, index int) baseSeries {
} else { } else {
series.points = points series.points = points
} }
if index != 0 && len(points) >= int(index) { series.convex, series.rect, series.clockwise = processPoints(points, closed)
series.tree = new(d2.BoxTree) if opts.MinPoints != 0 && len(points) >= opts.MinPoints {
series.indexKind = opts.Kind
series.buildIndex()
} }
series.convex, series.rect, series.clockwise =
processPoints(points, closed, series.tree)
return series return series
} }
// Index ... // Index ...
func (series *baseSeries) Index() interface{} { func (series *baseSeries) Index() interface{} {
if series.tree == nil { return series.index
return nil
}
return series.tree
} }
// Clockwise ... // Clockwise ...
@ -82,9 +126,10 @@ func (series *baseSeries) Move(deltaX, deltaY float64) Series {
points[i].X = series.points[i].X + deltaX points[i].X = series.points[i].X + deltaX
points[i].Y = series.points[i].Y + deltaY points[i].Y = series.points[i].Y + deltaY
} }
nseries := makeSeries(points, false, series.closed, 0) nseries := makeSeries(points, false, series.closed, nil)
if series.tree != nil { nseries.indexKind = series.indexKind
nseries.buildTree() if series.Index() != nil {
nseries.buildIndex()
} }
return &nseries return &nseries
} }
@ -123,8 +168,11 @@ func (series *baseSeries) PointAt(index int) Point {
} }
// Search finds a searches for segments that intersect the provided rectangle // Search finds a searches for segments that intersect the provided rectangle
func (series *baseSeries) Search(rect Rect, iter func(seg Segment, idx int) bool) { func (series *baseSeries) Search(
if series.tree == nil { rect Rect, iter func(seg Segment, idx int) bool,
) {
switch v := series.index.(type) {
default:
n := series.NumSegments() n := series.NumSegments()
for i := 0; i < n; i++ { for i := 0; i < n; i++ {
seg := series.SegmentAt(i) seg := series.SegmentAt(i)
@ -134,8 +182,8 @@ func (series *baseSeries) Search(rect Rect, iter func(seg Segment, idx int) bool
} }
} }
} }
} else { case *rTree:
series.tree.Search( v.Search(
[]float64{rect.Min.X, rect.Min.Y}, []float64{rect.Min.X, rect.Min.Y},
[]float64{rect.Max.X, rect.Max.Y}, []float64{rect.Max.X, rect.Max.Y},
func(_, _ []float64, value interface{}) bool { func(_, _ []float64, value interface{}) bool {
@ -153,6 +201,24 @@ func (series *baseSeries) Search(rect Rect, iter func(seg Segment, idx int) bool
return true return true
}, },
) )
case *qNode:
v.search(series, series.rect, rect, iter)
case *byte:
// convert the byte pointer back to a valid slice
data := *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{
Data: uintptr(unsafe.Pointer(v)),
Len: 0xFFFFFFFF,
Cap: 0xFFFFFFFF,
}))
n := binary.LittleEndian.Uint32(data[1:])
data = data[:n:n]
switch data[0] {
case 1:
rCompressSearch(data, 5, series, rect, iter)
case 2:
qCompressSearch(data, 5, series, series.rect, rect, iter)
}
} }
} }
@ -185,16 +251,9 @@ func (series *baseSeries) SegmentAt(index int) Segment {
return seg 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 // processPoints tests if the ring is convex, calculates the outer
// rectangle, and inserts segments into a boxtree in one pass. // rectangle, and inserts segments into a boxtree in one pass.
func processPoints(points []Point, closed bool, tree *d2.BoxTree) ( func processPoints(points []Point, closed bool) (
convex bool, rect Rect, clockwise bool, convex bool, rect Rect, clockwise bool,
) { ) {
if (closed && len(points) < 3) || len(points) < 2 { if (closed && len(points) < 3) || len(points) < 2 {
@ -203,33 +262,9 @@ func processPoints(points []Point, closed bool, tree *d2.BoxTree) (
var concave bool var concave bool
var dir int var dir int
var a, b, c Point var a, b, c Point
var segCount int
var cwc float64 var cwc float64
if closed {
segCount = len(points)
} else {
segCount = len(points) - 1
}
for i := 0; i < len(points); i++ { 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 // process the rectangle inflation
if i == 0 { if i == 0 {
rect = Rect{points[i], points[i]} rect = Rect{points[i], points[i]}
@ -286,3 +321,54 @@ func processPoints(points []Point, closed bool, tree *d2.BoxTree) (
} }
return !concave, rect, cwc > 0 return !concave, rect, cwc > 0
} }
func (series *baseSeries) clearIndex() {
series.index = nil
}
func (series *baseSeries) setCompressed(data []byte) {
binary.LittleEndian.PutUint32(data[1:], uint32(len(data)))
smaller := make([]byte, len(data))
copy(smaller, data)
// use the byte point instead of a double reference to the byte slice
series.index = &smaller[0]
}
func (series *baseSeries) buildIndex() {
if series.index != nil {
// already built
return
}
switch series.indexKind {
case RTree, RTreeCompressed:
tr := new(rTree)
n := series.NumSegments()
for i := 0; i < n; i++ {
rect := series.SegmentAt(i).Rect()
tr.Insert(
[]float64{rect.Min.X, rect.Min.Y},
[]float64{rect.Max.X, rect.Max.Y}, i)
}
if series.indexKind == RTreeCompressed {
series.setCompressed(
tr.compress([]byte{1, 0, 0, 0, 0}),
)
} else {
series.index = tr
}
case QuadTree, QuadTreeCompressed:
root := new(qNode)
n := series.NumSegments()
for i := 0; i < n; i++ {
seg := series.SegmentAt(i)
root.insert(series, series.rect, seg.Rect(), i, 0)
}
if series.indexKind == QuadTreeCompressed {
series.setCompressed(
root.compress([]byte{2, 0, 0, 0, 0}, series.rect),
)
} else {
series.index = root
}
}
}

View File

@ -30,16 +30,16 @@ func seriesForEachPoint(ring Ring, iter func(point Point) bool) {
func TestSeriesEmpty(t *testing.T) { func TestSeriesEmpty(t *testing.T) {
var series *baseSeries var series *baseSeries
expect(t, series.Empty()) expect(t, series.Empty())
series2 := makeSeries(nil, false, false, 0) series2 := makeSeries(nil, false, false, &IndexOptions{Kind: None})
expect(t, series2.Empty()) expect(t, series2.Empty())
} }
func TestSeriesIndex(t *testing.T) { func TestSeriesIndex(t *testing.T) {
series := makeSeries(nil, false, false, 0) series := makeSeries(nil, false, false, &IndexOptions{Kind: None})
expect(t, series.Index() == nil) expect(t, series.Index() == nil)
series = makeSeries([]Point{ series = makeSeries([]Point{
P(0, 0), P(10, 0), P(10, 10), P(0, 10), P(0, 0), P(0, 0), P(10, 0), P(10, 10), P(0, 10), P(0, 0),
}, true, true, 1) }, true, true, &IndexOptions{Kind: RTree, MinPoints: 1})
expect(t, series.Index() != nil) expect(t, series.Index() != nil)
} }
@ -48,27 +48,27 @@ func TestSeriesClockwise(t *testing.T) {
var series baseSeries var series baseSeries
series = makeSeries([]Point{ series = makeSeries([]Point{
P(0, 0), P(10, 0), P(10, 10), P(0, 10), P(0, 0), P(0, 0), P(10, 0), P(10, 10), P(0, 10), P(0, 0),
}, true, true, DefaultIndex) }, true, true, DefaultIndexOptions)
expect(t, !series.Clockwise()) expect(t, !series.Clockwise())
series = makeSeries([]Point{ series = makeSeries([]Point{
P(0, 0), P(10, 0), P(10, 10), P(0, 10), P(0, 0), P(10, 0), P(10, 10), P(0, 10),
}, true, true, DefaultIndex) }, true, true, DefaultIndexOptions)
expect(t, !series.Clockwise()) expect(t, !series.Clockwise())
series = makeSeries([]Point{ series = makeSeries([]Point{
P(0, 0), P(10, 0), P(10, 10), P(0, 0), P(10, 0), P(10, 10),
}, true, true, DefaultIndex) }, true, true, DefaultIndexOptions)
expect(t, !series.Clockwise()) expect(t, !series.Clockwise())
series = makeSeries([]Point{ series = makeSeries([]Point{
P(0, 0), P(0, 10), P(10, 10), P(10, 0), P(0, 0), P(0, 0), P(0, 10), P(10, 10), P(10, 0), P(0, 0),
}, true, true, DefaultIndex) }, true, true, DefaultIndexOptions)
expect(t, series.Clockwise()) expect(t, series.Clockwise())
series = makeSeries([]Point{ series = makeSeries([]Point{
P(0, 0), P(0, 10), P(10, 10), P(10, 0), P(0, 0), P(0, 10), P(10, 10), P(10, 0),
}, true, true, DefaultIndex) }, true, true, DefaultIndexOptions)
expect(t, series.Clockwise()) expect(t, series.Clockwise())
series = makeSeries([]Point{ series = makeSeries([]Point{
P(0, 0), P(0, 10), P(10, 10), P(0, 0), P(0, 10), P(10, 10),
}, true, true, DefaultIndex) }, true, true, DefaultIndexOptions)
expect(t, series.Clockwise()) expect(t, series.Clockwise())
} }
@ -76,19 +76,19 @@ func TestSeriesConvex(t *testing.T) {
t.Run("1", func(t *testing.T) { t.Run("1", func(t *testing.T) {
series := makeSeries([]Point{ series := makeSeries([]Point{
P(0, 0), P(4, 0), P(4, 4), P(0, 4), P(0, 0), P(0, 0), P(4, 0), P(4, 4), P(0, 4), P(0, 0),
}, true, true, DefaultIndex) }, true, true, DefaultIndexOptions)
expect(t, series.Convex()) expect(t, series.Convex())
}) })
t.Run("2", func(t *testing.T) { t.Run("2", func(t *testing.T) {
series := makeSeries([]Point{ series := makeSeries([]Point{
P(0, 0), P(4, 0), P(4, 4), P(3, 4), P(1, 4), P(0, 4), P(0, 0), P(0, 0), P(4, 0), P(4, 4), P(3, 4), P(1, 4), P(0, 4), P(0, 0),
}, true, true, DefaultIndex) }, true, true, DefaultIndexOptions)
expect(t, series.Convex()) expect(t, series.Convex())
}) })
t.Run("3", func(t *testing.T) { t.Run("3", func(t *testing.T) {
series := makeSeries([]Point{ series := makeSeries([]Point{
P(0, 0), P(4, 0), P(4, 4), P(2, 5), P(0, 4), P(0, 0), P(0, 0), P(4, 0), P(4, 4), P(2, 5), P(0, 4), P(0, 0),
}, true, true, DefaultIndex) }, true, true, DefaultIndexOptions)
expect(t, series.Convex()) expect(t, series.Convex())
}) })
t.Run("4", func(t *testing.T) { t.Run("4", func(t *testing.T) {
@ -96,7 +96,7 @@ func TestSeriesConvex(t *testing.T) {
P(0, 0), P(4, 0), P(4, 4), P(0, 0), P(4, 0), P(4, 4),
P(3, 4), P(2, 5), P(1, 4), P(3, 4), P(2, 5), P(1, 4),
P(0, 4), P(0, 0), P(0, 4), P(0, 0),
}, true, true, DefaultIndex) }, true, true, DefaultIndexOptions)
expect(t, !series.Convex()) expect(t, !series.Convex())
}) })
t.Run("5", func(t *testing.T) { t.Run("5", func(t *testing.T) {
@ -104,28 +104,28 @@ func TestSeriesConvex(t *testing.T) {
P(0, 0), P(4, 0), P(4, 4), P(0, 0), P(4, 0), P(4, 4),
P(3, 4), P(2, 3), P(1, 4), P(3, 4), P(2, 3), P(1, 4),
P(0, 4), P(0, 0), P(0, 4), P(0, 0),
}, true, true, DefaultIndex) }, true, true, DefaultIndexOptions)
expect(t, !series.Convex()) expect(t, !series.Convex())
}) })
t.Run("5", func(t *testing.T) { t.Run("5", func(t *testing.T) {
series := makeSeries([]Point{ series := makeSeries([]Point{
P(0, 0), P(4, 0), P(4, 4), P(0, 4), P(0, 0), P(4, 0), P(4, 4), P(0, 4),
P(-1, 2), P(0, 0), P(-1, 2), P(0, 0),
}, true, true, DefaultIndex) }, true, true, DefaultIndexOptions)
expect(t, series.Convex()) expect(t, series.Convex())
}) })
t.Run("6", func(t *testing.T) { t.Run("6", func(t *testing.T) {
series := makeSeries([]Point{ series := makeSeries([]Point{
P(0, 0), P(4, 0), P(4, 4), P(0, 4), P(0, 3), P(0, 0), P(4, 0), P(4, 4), P(0, 4), P(0, 3),
P(-1, 2), P(0, 0), P(-1, 2), P(0, 0),
}, true, true, DefaultIndex) }, true, true, DefaultIndexOptions)
expect(t, !series.Convex()) expect(t, !series.Convex())
}) })
t.Run("6", func(t *testing.T) { t.Run("6", func(t *testing.T) {
series := makeSeries([]Point{ series := makeSeries([]Point{
P(0, 0), P(4, 0), P(4, 4), P(0, 4), P(0, 0), P(4, 0), P(4, 4), P(0, 4),
P(-1, 2), P(0, 1), P(0, 0), P(-1, 2), P(0, 1), P(0, 0),
}, true, true, DefaultIndex) }, true, true, DefaultIndexOptions)
expect(t, !series.Convex()) expect(t, !series.Convex())
}) })
t.Run("7", func(t *testing.T) { t.Run("7", func(t *testing.T) {
@ -133,14 +133,14 @@ func TestSeriesConvex(t *testing.T) {
P(0, 0), P(4, 0), P(4, 4), P(0, 0), P(4, 0), P(4, 4),
P(3, 3), P(2, 5), P(1, 3), P(0, 4), P(3, 3), P(2, 5), P(1, 3), P(0, 4),
P(0, 0), P(0, 0),
}, true, true, DefaultIndex) }, true, true, DefaultIndexOptions)
expect(t, !series.Convex()) expect(t, !series.Convex())
}) })
t.Run("8", func(t *testing.T) { t.Run("8", func(t *testing.T) {
series := makeSeries([]Point{ series := makeSeries([]Point{
P(0, 0), P(0, 4), P(1, 3), P(4, 4), P(2, 5), P(3, 3), P(4, 0), P(0, 0), P(0, 4), P(1, 3), P(4, 4), P(2, 5), P(3, 3), P(4, 0),
P(0, 0), P(0, 0),
}, true, true, DefaultIndex) }, true, true, DefaultIndexOptions)
expect(t, !series.Convex()) expect(t, !series.Convex())
}) })
} }
@ -151,9 +151,9 @@ func testScanSeries(
) []Segment { ) []Segment {
t.Helper() t.Helper()
if index { if index {
series.buildTree() series.buildIndex()
} else { } else {
series.tree = nil series.clearIndex()
} }
var segs1 []Segment var segs1 []Segment
seriesForEachSegment(series, func(seg Segment) bool { seriesForEachSegment(series, func(seg Segment) bool {
@ -178,51 +178,51 @@ func testScanSeries(
} }
func TestSeriesBasic(t *testing.T) { func TestSeriesBasic(t *testing.T) {
series := makeSeries(octagon, true, true, DefaultIndex) series := makeSeries(octagon, true, true, DefaultIndexOptions)
expect(t, reflect.DeepEqual(seriesCopyPoints(&series), octagon)) expect(t, reflect.DeepEqual(seriesCopyPoints(&series), octagon))
expect(t, series.Convex()) expect(t, series.Convex())
expect(t, series.Rect() == R(0, 0, 10, 10)) expect(t, series.Rect() == R(0, 0, 10, 10))
expect(t, series.Closed()) expect(t, series.Closed())
series = makeSeries(octagon, false, true, DefaultIndex) series = makeSeries(octagon, false, true, DefaultIndexOptions)
expect(t, reflect.DeepEqual(seriesCopyPoints(&series), octagon)) expect(t, reflect.DeepEqual(seriesCopyPoints(&series), octagon))
series = makeSeries(ri, true, true, DefaultIndex) series = makeSeries(ri, true, true, DefaultIndexOptions)
testScanSeries(t, &series, true, len(ri)-1, false) testScanSeries(t, &series, true, len(ri)-1, false)
testScanSeries(t, &series, false, len(ri)-1, false) testScanSeries(t, &series, false, len(ri)-1, false)
// small lines // small lines
series = makeSeries([]Point{}, true, false, DefaultIndex) series = makeSeries([]Point{}, true, false, DefaultIndexOptions)
testScanSeries(t, &series, true, 0, true) testScanSeries(t, &series, true, 0, true)
testScanSeries(t, &series, false, 0, true) testScanSeries(t, &series, false, 0, true)
series = makeSeries([]Point{P(5, 5)}, true, false, DefaultIndex) series = makeSeries([]Point{P(5, 5)}, true, false, DefaultIndexOptions)
testScanSeries(t, &series, true, 0, true) testScanSeries(t, &series, true, 0, true)
testScanSeries(t, &series, false, 0, true) testScanSeries(t, &series, false, 0, true)
series = makeSeries([]Point{P(5, 5), P(10, 10)}, true, false, DefaultIndex) series = makeSeries([]Point{P(5, 5), P(10, 10)}, true, false, DefaultIndexOptions)
testScanSeries(t, &series, true, 1, false) testScanSeries(t, &series, true, 1, false)
testScanSeries(t, &series, false, 1, false) testScanSeries(t, &series, false, 1, false)
// small rings // small rings
series = makeSeries([]Point{}, true, true, DefaultIndex) series = makeSeries([]Point{}, true, true, DefaultIndexOptions)
testScanSeries(t, &series, true, 0, true) testScanSeries(t, &series, true, 0, true)
testScanSeries(t, &series, false, 0, true) testScanSeries(t, &series, false, 0, true)
series = makeSeries([]Point{P(5, 5)}, true, true, DefaultIndex) series = makeSeries([]Point{P(5, 5)}, true, true, DefaultIndexOptions)
testScanSeries(t, &series, true, 0, true) testScanSeries(t, &series, true, 0, true)
testScanSeries(t, &series, false, 0, true) testScanSeries(t, &series, false, 0, true)
series = makeSeries([]Point{P(5, 5), P(10, 10)}, true, true, DefaultIndex) series = makeSeries([]Point{P(5, 5), P(10, 10)}, true, true, DefaultIndexOptions)
testScanSeries(t, &series, true, 0, true) testScanSeries(t, &series, true, 0, true)
testScanSeries(t, &series, false, 0, true) testScanSeries(t, &series, false, 0, true)
series = makeSeries([]Point{P(5, 5), P(10, 10), P(10, 5)}, true, true, DefaultIndex) series = makeSeries([]Point{P(5, 5), P(10, 10), P(10, 5)}, true, true, DefaultIndexOptions)
testScanSeries(t, &series, true, 3, false) testScanSeries(t, &series, true, 3, false)
testScanSeries(t, &series, false, 3, false) testScanSeries(t, &series, false, 3, false)
} }
func TestSeriesSearch(t *testing.T) { func TestSeriesSearch(t *testing.T) {
series := makeSeries(octagon, true, true, DefaultIndex) series := makeSeries(octagon, true, true, DefaultIndexOptions)
var segs []Segment var segs []Segment
series.Search(R(0, 0, 0, 0), func(seg Segment, _ int) bool { series.Search(R(0, 0, 0, 0), func(seg Segment, _ int) bool {
segs = append(segs, seg) segs = append(segs, seg)
@ -284,7 +284,7 @@ func TestSeriesSearch(t *testing.T) {
func TestSeriesBig(t *testing.T) { func TestSeriesBig(t *testing.T) {
t.Run("Closed", func(t *testing.T) { t.Run("Closed", func(t *testing.T) {
// clip off the last point to force an auto closure // clip off the last point to force an auto closure
series := makeSeries(ri[:len(ri)-1], true, true, DefaultIndex) series := makeSeries(ri[:len(ri)-1], true, true, DefaultIndexOptions)
var seg2sA []Segment var seg2sA []Segment
series.Search(series.Rect(), func(seg Segment, idx int) bool { series.Search(series.Rect(), func(seg Segment, idx int) bool {
seg2sA = append(seg2sA, seg) seg2sA = append(seg2sA, seg)
@ -299,7 +299,7 @@ func TestSeriesBig(t *testing.T) {
expect(t, checkSegsDups(seg2sA, seg2sB)) expect(t, checkSegsDups(seg2sA, seg2sB))
// use all points // use all points
series2 := makeSeries(ri, true, true, DefaultIndex) series2 := makeSeries(ri, true, true, DefaultIndexOptions)
var seg2sC []Segment var seg2sC []Segment
seriesForEachSegment(&series2, func(seg Segment) bool { seriesForEachSegment(&series2, func(seg Segment) bool {
seg2sC = append(seg2sC, seg) seg2sC = append(seg2sC, seg)
@ -318,7 +318,7 @@ func TestSeriesBig(t *testing.T) {
expect(t, first == seg2sA[0]) expect(t, first == seg2sA[0])
}) })
t.Run("Opened", func(t *testing.T) { t.Run("Opened", func(t *testing.T) {
series := makeSeries(az, true, false, DefaultIndex) series := makeSeries(az, true, false, DefaultIndexOptions)
var seg2sA []Segment var seg2sA []Segment
series.Search(series.Rect(), func(seg Segment, idx int) bool { series.Search(series.Rect(), func(seg Segment, idx int) bool {
seg2sA = append(seg2sA, seg) seg2sA = append(seg2sA, seg)
@ -339,7 +339,7 @@ func TestSeriesReverse(t *testing.T) {
for i := len(shape) - 1; i >= 0; i-- { for i := len(shape) - 1; i >= 0; i-- {
rev = append(rev, shape[i]) rev = append(rev, shape[i])
} }
series := makeSeries(rev, true, true, DefaultIndex) series := makeSeries(rev, true, true, DefaultIndexOptions)
var seg2sA []Segment var seg2sA []Segment
series.Search(series.Rect(), func(seg Segment, idx int) bool { series.Search(series.Rect(), func(seg Segment, idx int) bool {
seg2sA = append(seg2sA, seg) seg2sA = append(seg2sA, seg)
@ -375,14 +375,14 @@ func checkSegsDups(a, b []Segment) bool {
func TestSeriesMove(t *testing.T) { func TestSeriesMove(t *testing.T) {
shapes := [][]Point{ri, octagon} shapes := [][]Point{ri, octagon}
for _, shape := range shapes { for _, shape := range shapes {
series := makeSeries(shape, true, true, DefaultIndex) series := makeSeries(shape, true, true, DefaultIndexOptions)
series.tree = nil series.clearIndex()
series2 := series.Move(60, 70) series2 := series.Move(60, 70)
expect(t, series2.NumPoints() == len(shape)) expect(t, series2.NumPoints() == len(shape))
for i := 0; i < len(shape); i++ { for i := 0; i < len(shape); i++ {
expect(t, series2.PointAt(i) == shape[i].Move(60, 70)) expect(t, series2.PointAt(i) == shape[i].Move(60, 70))
} }
series.buildTree() series.buildIndex()
series2 = series.Move(60, 70) series2 = series.Move(60, 70)
expect(t, series2.NumPoints() == len(shape)) expect(t, series2.NumPoints() == len(shape))
for i := 0; i < len(shape); i++ { for i := 0; i < len(shape); i++ {

View File

@ -138,7 +138,8 @@ func parseJSONLineString(keys *parseKeys, opts *ParseOptions) (Object, error) {
// https://tools.ietf.org/html/rfc7946#section-3.1.4 // https://tools.ietf.org/html/rfc7946#section-3.1.4
return nil, errCoordinatesInvalid return nil, errCoordinatesInvalid
} }
line := geometry.NewLine(points, opts.IndexGeometry) gopts := toGeometryOpts(opts)
line := geometry.NewLine(points, &gopts)
g.base = *line g.base = *line
g.extra = ex g.extra = ex
if err := parseBBoxAndExtras(&g.extra, keys, opts); err != nil { if err := parseBBoxAndExtras(&g.extra, keys, opts); err != nil {

View File

@ -69,7 +69,8 @@ func parseJSONMultiLineString(
err = errCoordinatesInvalid err = errCoordinatesInvalid
return false return false
} }
line := geometry.NewLine(coords, opts.IndexGeometry) gopts := toGeometryOpts(opts)
line := geometry.NewLine(coords, &gopts)
g.children = append(g.children, &LineString{base: *line, extra: ex}) g.children = append(g.children, &LineString{base: *line, extra: ex})
return true return true
}) })

View File

@ -79,7 +79,8 @@ func parseJSONMultiPolygon(
if len(coords) > 1 { if len(coords) > 1 {
holes = coords[1:] holes = coords[1:]
} }
poly := geometry.NewPoly(exterior, holes, opts.IndexGeometry) gopts := toGeometryOpts(opts)
poly := geometry.NewPoly(exterior, holes, &gopts)
g.children = append(g.children, &Polygon{base: *poly, extra: ex}) g.children = append(g.children, &Polygon{base: *poly, extra: ex})
return true return true
}) })

View File

@ -84,12 +84,16 @@ type ParseOptions struct {
// disable indexing. // disable indexing.
// The default is 64. // The default is 64.
IndexGeometry int IndexGeometry int
// IndexGeometryKind is the kind of index implementation.
// Default is QuadTreeCompressed
IndexGeometryKind geometry.IndexKind
} }
// DefaultParseOptions ... // DefaultParseOptions ...
var DefaultParseOptions = &ParseOptions{ var DefaultParseOptions = &ParseOptions{
IndexChildren: geometry.DefaultIndex, IndexChildren: 64,
IndexGeometry: geometry.DefaultIndex, IndexGeometry: 64,
IndexGeometryKind: geometry.QuadTreeCompressed,
} }
// Parse a GeoJSON object // Parse a GeoJSON object
@ -124,6 +128,17 @@ func Parse(data string, opts *ParseOptions) (Object, error) {
} }
} }
func toGeometryOpts(opts *ParseOptions) geometry.IndexOptions {
var gopts geometry.IndexOptions
if opts == nil {
gopts = *geometry.DefaultIndexOptions
} else {
gopts.Kind = opts.IndexGeometryKind
gopts.MinPoints = opts.IndexGeometry
}
return gopts
}
type parseKeys struct { type parseKeys struct {
rCoordinates gjson.Result rCoordinates gjson.Result
rGeometries gjson.Result rGeometries gjson.Result

View File

@ -36,11 +36,11 @@ func RO(minX, minY, maxX, maxY float64) *Rect {
} }
func LO(points []geometry.Point) *LineString { func LO(points []geometry.Point) *LineString {
return NewLineString(geometry.NewLine(points, geometry.DefaultIndex)) return NewLineString(geometry.NewLine(points, nil))
} }
func PPO(exterior []geometry.Point, holes [][]geometry.Point) *Polygon { func PPO(exterior []geometry.Point, holes [][]geometry.Point) *Polygon {
return NewPolygon(geometry.NewPoly(exterior, holes, geometry.DefaultIndex)) return NewPolygon(geometry.NewPoly(exterior, holes, nil))
} }
func expectJSON(t testing.TB, data string, expect interface{}) Object { func expectJSON(t testing.TB, data string, expect interface{}) Object {

View File

@ -156,7 +156,8 @@ func parseJSONPolygon(keys *parseKeys, opts *ParseOptions) (Object, error) {
if len(coords) > 1 { if len(coords) > 1 {
holes = coords[1:] holes = coords[1:]
} }
poly := geometry.NewPoly(exterior, holes, opts.IndexGeometry) gopts := toGeometryOpts(opts)
poly := geometry.NewPoly(exterior, holes, &gopts)
g.base = *poly g.base = *poly
g.extra = ex g.extra = ex
if err := parseBBoxAndExtras(&g.extra, keys, opts); err != nil { if err := parseBBoxAndExtras(&g.extra, keys, opts); err != nil {