mirror of https://github.com/tidwall/tile38.git
Added multiple indexing kinds
This commit is contained in:
parent
8ee4c10862
commit
7cc4008442
|
@ -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"
|
||||
|
|
|
@ -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])
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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)))
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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++ {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
})
|
||||
|
|
|
@ -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
|
||||
})
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in New Issue