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

View File

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

View File

@ -30,7 +30,7 @@ func clipPolygon(
holes = newPoints[1:]
}
newPoly := geojson.NewPolygon(
geometry.NewPoly(exterior, holes, geometry.DefaultIndex),
geometry.NewPoly(exterior, holes, nil),
)
if newPoly.Empty() {
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++ {
points[i] = base.PointAt(i)
}
poly := geometry.NewPoly(points, nil, geometry.DefaultIndex)
poly := geometry.NewPoly(points, nil, nil)
gPoly := geojson.NewPolygon(poly)
return Clip(gPoly, clipper)
}

View File

@ -19,6 +19,7 @@ import (
"github.com/tidwall/buntdb"
"github.com/tidwall/geojson"
"github.com/tidwall/geojson/geometry"
"github.com/tidwall/resp"
"github.com/tidwall/tile38/core"
"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 {
c.geomParseOpts.IndexChildren = int(n)
}
log.Debugf("geom indexing: %d", c.geomParseOpts.IndexGeometry)
log.Debugf("multi indexing: %d", c.geomParseOpts.IndexChildren)
indexKind := os.Getenv("T38IDXGEOMKIND")
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
qdb, err := buntdb.Open(core.QueueFileName)

View File

@ -131,7 +131,7 @@ func fenceMatch(
[]geometry.Point{
details.oldObj.Center(),
details.obj.Center(),
}, geometry.DefaultIndex))
}, nil))
temp := false
if fence.cmd == "within" {
// 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})
i++
}
// TODO: account for the pole and antimerdian. In most cases only a polygon
// is needed, but when the circle bounds passes the 90/180 lines, we need
// to create a multipolygon
// TODO: account for the pole and antimerdian. In most cases only a
// polygon is needed, but when the circle bounds passes the 90/180
// lines, we need to create a multipolygon
points = append(points, points[0])
g.Object = NewPolygon(
geometry.NewPoly(points, nil, geometry.DefaultIndex),
geometry.NewPoly(points, nil, geometry.DefaultIndexOptions),
)
}
return g

View File

@ -30,7 +30,7 @@ func P(x, y float64) Point {
return Point{x, y}
}
func L(points ...Point) *Line {
return NewLine(points, DefaultIndex)
return NewLine(points, DefaultIndexOptions)
}
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
func NewLine(points []Point, index int) *Line {
func NewLine(points []Point, opts *IndexOptions) *Line {
line := new(Line)
line.baseSeries = makeSeries(points, true, false, index)
line.baseSeries = makeSeries(points, true, false, opts)
return line
}

View File

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

View File

@ -17,10 +17,10 @@ func testBig(
) {
N, T := 100000, 4
simple := newRing(points, DefaultIndex)
simple.(*baseSeries).tree = nil
tree := newRing(points, DefaultIndex)
tree.(*baseSeries).buildTree()
simple := newRing(points, DefaultIndexOptions)
simple.(*baseSeries).clearIndex()
tree := newRing(points, DefaultIndexOptions)
tree.(*baseSeries).buildIndex()
pointOn := points[len(points)/2]
// 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) {
expect(t, !P(5, 5).ContainsPoly(NewPoly(nil, nil, DefaultIndex)))
expect(t, !P(5, 5).ContainsPoly(NewPoly([]Point{P(0, 0), P(10, 0)}, 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, DefaultIndexOptions)))
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)}))
}
func TestPointIntersectsPoly(t *testing.T) {
octa := NewPoly(octagon, nil, DefaultIndex)
concave1 := NewPoly(concave1, nil, DefaultIndex)
expect(t, !P(5, 5).IntersectsPoly(NewPoly(nil, nil, DefaultIndex)))
expect(t, !P(5, 5).IntersectsPoly(NewPoly([]Point{P(0, 0), P(10, 0)}, nil, DefaultIndex)))
octa := NewPoly(octagon, nil, DefaultIndexOptions)
concave1 := NewPoly(concave1, nil, DefaultIndexOptions)
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, DefaultIndexOptions)))
expect(t, P(5, 5).IntersectsPoly(octa))
expect(t, P(0, 5).IntersectsPoly(octa))
expect(t, !P(1, 1).IntersectsPoly(octa))

View File

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

View File

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

View File

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

View File

@ -10,16 +10,16 @@ import (
)
func newRingXSimple(points []Point) Ring {
ring := newRing(points, DefaultIndex)
if ring.(*baseSeries).tree != nil {
ring.(*baseSeries).tree = nil
ring := newRing(points, DefaultIndexOptions)
if ring.(*baseSeries).Index() != nil {
ring.(*baseSeries).clearIndex()
}
return ring
}
func newRingXIndexed(points []Point) Ring {
ring := newRing(points, DefaultIndex)
if ring.(*baseSeries).tree == nil {
ring.(*baseSeries).buildTree()
ring := newRing(points, DefaultIndexOptions)
if ring.(*baseSeries).Index() == nil {
ring.(*baseSeries).buildIndex()
}
return ring
}
@ -66,7 +66,7 @@ func TestRingXContainsPoint(t *testing.T) {
P(3, 4), P(1, 4),
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.4, 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) {
ring := newRing([]Point{
P(0, 0), P(4, 0), P(4, 4), P(0, 4), P(0, 0),
}, DefaultIndex)
}, DefaultIndexOptions)
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), false))
@ -145,42 +145,42 @@ func TestRingXIntersectsSegment(t *testing.T) {
t.Run("12", func(t *testing.T) {
ring := newRing([]Point{
{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), false))
})
t.Run("13", func(t *testing.T) {
ring := newRing([]Point{
{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), false))
})
t.Run("14", func(t *testing.T) {
ring := newRing([]Point{
{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), false))
})
t.Run("15", func(t *testing.T) {
ring := newRing([]Point{
{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), false))
})
t.Run("16", func(t *testing.T) {
ring := newRing([]Point{
{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), false))
})
t.Run("17", func(t *testing.T) {
ring := newRing([]Point{
{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), false))
})
@ -815,7 +815,7 @@ func TestRingXContainsSegment(t *testing.T) {
ring := newRing([]Point{
P(0, 0), P(4, 0), P(4, 4), P(3, 4),
P(2, 3), P(1, 4), P(0, 4), P(0, 0),
}, DefaultIndex)
}, DefaultIndexOptions)
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), false))
@ -840,7 +840,7 @@ func TestRingXContainsSegment(t *testing.T) {
ring := newRing([]Point{
P(0, 0), P(4, 0), P(4, 4), P(3, 4),
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), false))
})
@ -849,7 +849,7 @@ func TestRingXContainsSegment(t *testing.T) {
P(0, 0), P(4, 0), P(4, 4), P(3, 4),
P(2.5, 3), P(2, 4), P(1.5, 3),
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), false))
})
@ -858,7 +858,7 @@ func TestRingXContainsSegment(t *testing.T) {
P(0, 0), P(4, 0), P(4, 4), P(3, 4),
P(2.5, 5), P(2, 4), P(1.5, 5),
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), false))
})
@ -882,14 +882,14 @@ func TestRingXContainsSegment(t *testing.T) {
ring := newRing([]Point{
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),
}, DefaultIndex)
}, DefaultIndexOptions)
expect(t, ringContainsSegment(ring, S(1, 0, 3, 0), true))
expect(t, !ringContainsSegment(ring, S(1, 0, 3, 0), false))
})
t.Run("14", func(t *testing.T) {
ring := newRing([]Point{
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(3, 1, 1, 3), true))
expect(t, !ringContainsSegment(ring, S(1, 3, 3, 1), false))
@ -908,24 +908,24 @@ func TestRingXContainsSegment(t *testing.T) {
func TestRingXContainsRing(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(R(0, 0, 1, 1), newRing(nil, DefaultIndex), true))
expect(t, !ringContainsRing(newRing(nil, DefaultIndexOptions), R(0, 0, 1, 1), true))
expect(t, !ringContainsRing(R(0, 0, 1, 1), newRing(nil, DefaultIndexOptions), true))
})
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), false))
expect(t, ringContainsRing(newRing(concave1, DefaultIndex), newRing(concave1, DefaultIndex), true))
expect(t, !ringContainsRing(newRing(concave1, DefaultIndex), newRing(concave1, DefaultIndex), false))
expect(t, ringContainsRing(newRing(octagon, DefaultIndex), newRing(octagon, DefaultIndex), true))
expect(t, !ringContainsRing(newRing(octagon, DefaultIndex), newRing(octagon, DefaultIndex), false))
expect(t, ringContainsRing(newRing(concave1, DefaultIndexOptions), newRing(concave1, DefaultIndexOptions), true))
expect(t, !ringContainsRing(newRing(concave1, DefaultIndexOptions), newRing(concave1, DefaultIndexOptions), false))
expect(t, ringContainsRing(newRing(octagon, DefaultIndexOptions), newRing(octagon, DefaultIndexOptions), true))
expect(t, !ringContainsRing(newRing(octagon, DefaultIndexOptions), newRing(octagon, DefaultIndexOptions), false))
})
t.Run("Cases", func(t *testing.T) {
// concave
ring := newRing([]Point{
P(0, 0), P(4, 0), P(4, 4), P(3, 4),
P(2, 3), P(1, 4), P(0, 4), P(0, 0),
}, DefaultIndex)
}, DefaultIndexOptions)
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), false))
@ -951,7 +951,7 @@ func TestRingXContainsRing(t *testing.T) {
P(0, 0), P(4, 0), P(4, 4),
P(3, 4), P(2, 5), P(1, 4),
P(0, 4), P(0, 0),
}, DefaultIndex)
}, DefaultIndexOptions)
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), false))
@ -968,7 +968,7 @@ func TestRingXContainsRing(t *testing.T) {
ring = newRing([]Point{
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),
}, DefaultIndex)
}, DefaultIndexOptions)
expect(t, ringContainsRing(ring, R(1, 0, 3, 1), true))
expect(t, !ringContainsRing(ring, R(1, 0, 3, 1), false))
})
@ -976,13 +976,13 @@ func TestRingXContainsRing(t *testing.T) {
ring = newRing([]Point{
P(0, 0), P(4, 0), P(4, 3), P(2, 4),
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), false))
})
ring = newRing([]Point{
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) {
expect(t, ringContainsRing(ring, R(1, 1, 3, 2), true))
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) {
shapes := []Ring{
R(0, 0, 10, 10),
newRing(octagon, DefaultIndex),
newRing(concave1, DefaultIndex), newRing(concave2, DefaultIndex),
newRing(concave3, DefaultIndex), newRing(concave4, DefaultIndex),
newRing(ri, DefaultIndex),
newRing(octagon, DefaultIndexOptions),
newRing(concave1, DefaultIndexOptions), newRing(concave2, DefaultIndexOptions),
newRing(concave3, DefaultIndexOptions), newRing(concave4, DefaultIndexOptions),
newRing(ri, DefaultIndexOptions),
}
for i, shape := range shapes {
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) {
// use rhode island
ring := newRing(ri, DefaultIndex)
ring := newRing(ri, DefaultIndexOptions)
expect(t, ringContainsRing(ring.Rect(), ring, true))
expect(t, ringContainsRing(ring, ring, true))
@ -1055,15 +1055,15 @@ func TestRingXIntersectsRing(t *testing.T) {
return t1
}
t.Run("Empty", func(t *testing.T) {
expect(t, !intersectsBothWays(newRing(nil, DefaultIndex), R(0, 0, 1, 1), true))
expect(t, !intersectsBothWays(R(0, 0, 1, 1), newRing(nil, DefaultIndex), true))
expect(t, !intersectsBothWays(newRing(nil, DefaultIndexOptions), R(0, 0, 1, 1), true))
expect(t, !intersectsBothWays(R(0, 0, 1, 1), newRing(nil, DefaultIndexOptions), true))
})
t.Run("Cases", func(t *testing.T) {
// concave
ring := newRing([]Point{
P(0, 0), P(4, 0), P(4, 4), P(3, 4),
P(2, 3), P(1, 4), P(0, 4), P(0, 0),
}, DefaultIndex)
}, DefaultIndexOptions)
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), false))
@ -1089,7 +1089,7 @@ func TestRingXIntersectsRing(t *testing.T) {
P(0, 0), P(4, 0), P(4, 4),
P(3, 4), P(2, 5), P(1, 4),
P(0, 4), P(0, 0),
}, DefaultIndex)
}, DefaultIndexOptions)
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), false))
@ -1106,7 +1106,7 @@ func TestRingXIntersectsRing(t *testing.T) {
ring = newRing([]Point{
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),
}, DefaultIndex)
}, DefaultIndexOptions)
expect(t, intersectsBothWays(ring, R(1, 0, 3, 1), true))
expect(t, intersectsBothWays(ring, R(1, 0, 3, 1), false))
})
@ -1114,13 +1114,13 @@ func TestRingXIntersectsRing(t *testing.T) {
ring = newRing([]Point{
P(0, 0), P(4, 0), P(4, 3), P(2, 4),
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), false))
})
ring = newRing([]Point{
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) {
expect(t, intersectsBothWays(ring, R(1, 1, 3, 2), true))
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(3, 4), P(1, 4),
P(0, 3), P(0, 0),
}, DefaultIndex)
}, DefaultIndexOptions)
makeLine := func(start Point) *Line {
return NewLine([]Point{
start, start.Move(0, 1), start.Move(1, 1),
start.Move(1, 2), start.Move(2, 2),
}, DefaultIndex)
}, DefaultIndexOptions)
}
t.Run("1", func(t *testing.T) {
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(3, 4), P(1, 4),
P(0, 3), P(0, 0),
}, DefaultIndex)
}, DefaultIndexOptions)
makeLine := func(start Point) *Line {
return NewLine([]Point{
start, start.Move(0, 1), start.Move(1, 1),
start.Move(1, 2), start.Move(2, 2),
}, DefaultIndex)
}, DefaultIndexOptions)
}
t.Run("1", func(t *testing.T) {
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
import "github.com/tidwall/boxtree/d2"
import (
"encoding/binary"
"reflect"
"unsafe"
)
// DefaultIndex are the minumum number of points required before it makes
// sense to index the segments.
// 64 seems to be the sweet spot
const DefaultIndex = 64
// IndexKind is the kind of index to use in the options.
type IndexKind byte
// 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
// 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
clockwise bool // points move clockwise
convex bool // points create a convex shape
indexKind IndexKind // index kind
index interface{} // actual index
rect Rect // minumum bounding rectangle
points []Point // original points
tree *d2.BoxTree // segment tree.
}
// makeSeries returns a processed baseSeries.
func makeSeries(points []Point, copyPoints, closed bool, index int) baseSeries {
func makeSeries(
points []Point, copyPoints, closed bool, opts *IndexOptions,
) baseSeries {
if opts == nil {
opts = DefaultIndexOptions
}
var series baseSeries
series.closed = closed
if copyPoints {
@ -55,20 +102,17 @@ func makeSeries(points []Point, copyPoints, closed bool, index int) baseSeries {
} else {
series.points = points
}
if index != 0 && len(points) >= int(index) {
series.tree = new(d2.BoxTree)
series.convex, series.rect, series.clockwise = processPoints(points, closed)
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
}
// Index ...
func (series *baseSeries) Index() interface{} {
if series.tree == nil {
return nil
}
return series.tree
return series.index
}
// Clockwise ...
@ -82,9 +126,10 @@ func (series *baseSeries) Move(deltaX, deltaY float64) Series {
points[i].X = series.points[i].X + deltaX
points[i].Y = series.points[i].Y + deltaY
}
nseries := makeSeries(points, false, series.closed, 0)
if series.tree != nil {
nseries.buildTree()
nseries := makeSeries(points, false, series.closed, nil)
nseries.indexKind = series.indexKind
if series.Index() != nil {
nseries.buildIndex()
}
return &nseries
}
@ -123,8 +168,11 @@ func (series *baseSeries) PointAt(index int) Point {
}
// Search finds a searches for segments that intersect the provided rectangle
func (series *baseSeries) Search(rect Rect, iter func(seg Segment, idx int) bool) {
if series.tree == nil {
func (series *baseSeries) Search(
rect Rect, iter func(seg Segment, idx int) bool,
) {
switch v := series.index.(type) {
default:
n := series.NumSegments()
for i := 0; i < n; i++ {
seg := series.SegmentAt(i)
@ -134,8 +182,8 @@ func (series *baseSeries) Search(rect Rect, iter func(seg Segment, idx int) bool
}
}
}
} else {
series.tree.Search(
case *rTree:
v.Search(
[]float64{rect.Min.X, rect.Min.Y},
[]float64{rect.Max.X, rect.Max.Y},
func(_, _ []float64, value interface{}) bool {
@ -153,6 +201,24 @@ func (series *baseSeries) Search(rect Rect, iter func(seg Segment, idx int) bool
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
}
func (series *baseSeries) buildTree() {
if series.tree == nil {
series.tree = new(d2.BoxTree)
processPoints(series.points, series.closed, series.tree)
}
}
// processPoints tests if the ring is convex, calculates the outer
// rectangle, and inserts segments into a boxtree in one pass.
func processPoints(points []Point, closed bool, tree *d2.BoxTree) (
func processPoints(points []Point, closed bool) (
convex bool, rect Rect, clockwise bool,
) {
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 dir int
var a, b, c Point
var segCount int
var cwc float64
if closed {
segCount = len(points)
} else {
segCount = len(points) - 1
}
for i := 0; i < len(points); i++ {
// process the segments for tree insertion
if tree != nil && i < segCount {
var seg Segment
seg.A = points[i]
if closed && i == len(points)-1 {
if seg.A == points[0] {
break
}
seg.B = points[0]
} else {
seg.B = points[i+1]
}
rect := seg.Rect()
tree.Insert(
[]float64{rect.Min.X, rect.Min.Y},
[]float64{rect.Max.X, rect.Max.Y}, i)
}
// process the rectangle inflation
if i == 0 {
rect = Rect{points[i], points[i]}
@ -286,3 +321,54 @@ func processPoints(points []Point, closed bool, tree *d2.BoxTree) (
}
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) {
var series *baseSeries
expect(t, series.Empty())
series2 := makeSeries(nil, false, false, 0)
series2 := makeSeries(nil, false, false, &IndexOptions{Kind: None})
expect(t, series2.Empty())
}
func TestSeriesIndex(t *testing.T) {
series := makeSeries(nil, false, false, 0)
series := makeSeries(nil, false, false, &IndexOptions{Kind: None})
expect(t, series.Index() == nil)
series = makeSeries([]Point{
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)
}
@ -48,27 +48,27 @@ func TestSeriesClockwise(t *testing.T) {
var series baseSeries
series = makeSeries([]Point{
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())
series = makeSeries([]Point{
P(0, 0), P(10, 0), P(10, 10), P(0, 10),
}, true, true, DefaultIndex)
}, true, true, DefaultIndexOptions)
expect(t, !series.Clockwise())
series = makeSeries([]Point{
P(0, 0), P(10, 0), P(10, 10),
}, true, true, DefaultIndex)
}, true, true, DefaultIndexOptions)
expect(t, !series.Clockwise())
series = makeSeries([]Point{
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())
series = makeSeries([]Point{
P(0, 0), P(0, 10), P(10, 10), P(10, 0),
}, true, true, DefaultIndex)
}, true, true, DefaultIndexOptions)
expect(t, series.Clockwise())
series = makeSeries([]Point{
P(0, 0), P(0, 10), P(10, 10),
}, true, true, DefaultIndex)
}, true, true, DefaultIndexOptions)
expect(t, series.Clockwise())
}
@ -76,19 +76,19 @@ func TestSeriesConvex(t *testing.T) {
t.Run("1", func(t *testing.T) {
series := makeSeries([]Point{
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())
})
t.Run("2", func(t *testing.T) {
series := makeSeries([]Point{
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())
})
t.Run("3", func(t *testing.T) {
series := makeSeries([]Point{
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())
})
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(3, 4), P(2, 5), P(1, 4),
P(0, 4), P(0, 0),
}, true, true, DefaultIndex)
}, true, true, DefaultIndexOptions)
expect(t, !series.Convex())
})
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(3, 4), P(2, 3), P(1, 4),
P(0, 4), P(0, 0),
}, true, true, DefaultIndex)
}, true, true, DefaultIndexOptions)
expect(t, !series.Convex())
})
t.Run("5", func(t *testing.T) {
series := makeSeries([]Point{
P(0, 0), P(4, 0), P(4, 4), P(0, 4),
P(-1, 2), P(0, 0),
}, true, true, DefaultIndex)
}, true, true, DefaultIndexOptions)
expect(t, series.Convex())
})
t.Run("6", func(t *testing.T) {
series := makeSeries([]Point{
P(0, 0), P(4, 0), P(4, 4), P(0, 4), P(0, 3),
P(-1, 2), P(0, 0),
}, true, true, DefaultIndex)
}, true, true, DefaultIndexOptions)
expect(t, !series.Convex())
})
t.Run("6", func(t *testing.T) {
series := makeSeries([]Point{
P(0, 0), P(4, 0), P(4, 4), P(0, 4),
P(-1, 2), P(0, 1), P(0, 0),
}, true, true, DefaultIndex)
}, true, true, DefaultIndexOptions)
expect(t, !series.Convex())
})
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(3, 3), P(2, 5), P(1, 3), P(0, 4),
P(0, 0),
}, true, true, DefaultIndex)
}, true, true, DefaultIndexOptions)
expect(t, !series.Convex())
})
t.Run("8", func(t *testing.T) {
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),
}, true, true, DefaultIndex)
}, true, true, DefaultIndexOptions)
expect(t, !series.Convex())
})
}
@ -151,9 +151,9 @@ func testScanSeries(
) []Segment {
t.Helper()
if index {
series.buildTree()
series.buildIndex()
} else {
series.tree = nil
series.clearIndex()
}
var segs1 []Segment
seriesForEachSegment(series, func(seg Segment) bool {
@ -178,51 +178,51 @@ func testScanSeries(
}
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, series.Convex())
expect(t, series.Rect() == R(0, 0, 10, 10))
expect(t, series.Closed())
series = makeSeries(octagon, false, true, DefaultIndex)
series = makeSeries(octagon, false, true, DefaultIndexOptions)
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, false, len(ri)-1, false)
// small lines
series = makeSeries([]Point{}, true, false, DefaultIndex)
series = makeSeries([]Point{}, true, false, DefaultIndexOptions)
testScanSeries(t, &series, true, 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, 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, false, 1, false)
// small rings
series = makeSeries([]Point{}, true, true, DefaultIndex)
series = makeSeries([]Point{}, true, true, DefaultIndexOptions)
testScanSeries(t, &series, true, 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, 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, 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, false, 3, false)
}
func TestSeriesSearch(t *testing.T) {
series := makeSeries(octagon, true, true, DefaultIndex)
series := makeSeries(octagon, true, true, DefaultIndexOptions)
var segs []Segment
series.Search(R(0, 0, 0, 0), func(seg Segment, _ int) bool {
segs = append(segs, seg)
@ -284,7 +284,7 @@ func TestSeriesSearch(t *testing.T) {
func TestSeriesBig(t *testing.T) {
t.Run("Closed", func(t *testing.T) {
// 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
series.Search(series.Rect(), func(seg Segment, idx int) bool {
seg2sA = append(seg2sA, seg)
@ -299,7 +299,7 @@ func TestSeriesBig(t *testing.T) {
expect(t, checkSegsDups(seg2sA, seg2sB))
// use all points
series2 := makeSeries(ri, true, true, DefaultIndex)
series2 := makeSeries(ri, true, true, DefaultIndexOptions)
var seg2sC []Segment
seriesForEachSegment(&series2, func(seg Segment) bool {
seg2sC = append(seg2sC, seg)
@ -318,7 +318,7 @@ func TestSeriesBig(t *testing.T) {
expect(t, first == seg2sA[0])
})
t.Run("Opened", func(t *testing.T) {
series := makeSeries(az, true, false, DefaultIndex)
series := makeSeries(az, true, false, DefaultIndexOptions)
var seg2sA []Segment
series.Search(series.Rect(), func(seg Segment, idx int) bool {
seg2sA = append(seg2sA, seg)
@ -339,7 +339,7 @@ func TestSeriesReverse(t *testing.T) {
for i := len(shape) - 1; i >= 0; i-- {
rev = append(rev, shape[i])
}
series := makeSeries(rev, true, true, DefaultIndex)
series := makeSeries(rev, true, true, DefaultIndexOptions)
var seg2sA []Segment
series.Search(series.Rect(), func(seg Segment, idx int) bool {
seg2sA = append(seg2sA, seg)
@ -375,14 +375,14 @@ func checkSegsDups(a, b []Segment) bool {
func TestSeriesMove(t *testing.T) {
shapes := [][]Point{ri, octagon}
for _, shape := range shapes {
series := makeSeries(shape, true, true, DefaultIndex)
series.tree = nil
series := makeSeries(shape, true, true, DefaultIndexOptions)
series.clearIndex()
series2 := series.Move(60, 70)
expect(t, series2.NumPoints() == len(shape))
for i := 0; i < len(shape); i++ {
expect(t, series2.PointAt(i) == shape[i].Move(60, 70))
}
series.buildTree()
series.buildIndex()
series2 = series.Move(60, 70)
expect(t, series2.NumPoints() == len(shape))
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
return nil, errCoordinatesInvalid
}
line := geometry.NewLine(points, opts.IndexGeometry)
gopts := toGeometryOpts(opts)
line := geometry.NewLine(points, &gopts)
g.base = *line
g.extra = ex
if err := parseBBoxAndExtras(&g.extra, keys, opts); err != nil {

View File

@ -69,7 +69,8 @@ func parseJSONMultiLineString(
err = errCoordinatesInvalid
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})
return true
})

View File

@ -79,7 +79,8 @@ func parseJSONMultiPolygon(
if len(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})
return true
})

View File

@ -84,12 +84,16 @@ type ParseOptions struct {
// disable indexing.
// The default is 64.
IndexGeometry int
// IndexGeometryKind is the kind of index implementation.
// Default is QuadTreeCompressed
IndexGeometryKind geometry.IndexKind
}
// DefaultParseOptions ...
var DefaultParseOptions = &ParseOptions{
IndexChildren: geometry.DefaultIndex,
IndexGeometry: geometry.DefaultIndex,
IndexChildren: 64,
IndexGeometry: 64,
IndexGeometryKind: geometry.QuadTreeCompressed,
}
// 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 {
rCoordinates 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 {
return NewLineString(geometry.NewLine(points, geometry.DefaultIndex))
return NewLineString(geometry.NewLine(points, nil))
}
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 {

View File

@ -156,7 +156,8 @@ func parseJSONPolygon(keys *parseKeys, opts *ParseOptions) (Object, error) {
if len(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.extra = ex
if err := parseBBoxAndExtras(&g.extra, keys, opts); err != nil {