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]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
digest = "1:c27e7d5bfc68a4ad2166d1ae909917013a985ba4c8b5255b91bd253bf3ab5730"
|
digest = "1:9fcbc64769ddda5230be91f192aa87aa8233f9776053108c1c89e1263f2c4a98"
|
||||||
name = "github.com/tidwall/geojson"
|
name = "github.com/tidwall/geojson"
|
||||||
packages = [
|
packages = [
|
||||||
".",
|
".",
|
||||||
|
@ -235,7 +235,7 @@
|
||||||
"geometry",
|
"geometry",
|
||||||
]
|
]
|
||||||
pruneopts = ""
|
pruneopts = ""
|
||||||
revision = "5302514a34feb71743bf597938742b51831ba289"
|
revision = "2d2f7893c3d92f36cef52047d457573ed44d6f04"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
digest = "1:3ddca2bd5496c6922a2a9e636530e178a43c2a534ea6634211acdc7d10222794"
|
digest = "1:3ddca2bd5496c6922a2a9e636530e178a43c2a534ea6634211acdc7d10222794"
|
||||||
|
|
|
@ -34,7 +34,7 @@ func clipLineString(
|
||||||
var children []*geometry.Line
|
var children []*geometry.Line
|
||||||
for _, points := range newPoints {
|
for _, points := range newPoints {
|
||||||
children = append(children,
|
children = append(children,
|
||||||
geometry.NewLine(points, geometry.DefaultIndex))
|
geometry.NewLine(points, nil))
|
||||||
}
|
}
|
||||||
if len(children) == 1 {
|
if len(children) == 1 {
|
||||||
return geojson.NewLineString(children[0])
|
return geojson.NewLineString(children[0])
|
||||||
|
|
|
@ -30,7 +30,7 @@ func clipPolygon(
|
||||||
holes = newPoints[1:]
|
holes = newPoints[1:]
|
||||||
}
|
}
|
||||||
newPoly := geojson.NewPolygon(
|
newPoly := geojson.NewPolygon(
|
||||||
geometry.NewPoly(exterior, holes, geometry.DefaultIndex),
|
geometry.NewPoly(exterior, holes, nil),
|
||||||
)
|
)
|
||||||
if newPoly.Empty() {
|
if newPoly.Empty() {
|
||||||
return geojson.NewMultiPolygon(nil)
|
return geojson.NewMultiPolygon(nil)
|
||||||
|
|
|
@ -11,7 +11,7 @@ func clipRect(rect *geojson.Rect, clipper geojson.Object) geojson.Object {
|
||||||
for i := 0; i < len(points); i++ {
|
for i := 0; i < len(points); i++ {
|
||||||
points[i] = base.PointAt(i)
|
points[i] = base.PointAt(i)
|
||||||
}
|
}
|
||||||
poly := geometry.NewPoly(points, nil, geometry.DefaultIndex)
|
poly := geometry.NewPoly(points, nil, nil)
|
||||||
gPoly := geojson.NewPolygon(poly)
|
gPoly := geojson.NewPolygon(poly)
|
||||||
return Clip(gPoly, clipper)
|
return Clip(gPoly, clipper)
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ import (
|
||||||
|
|
||||||
"github.com/tidwall/buntdb"
|
"github.com/tidwall/buntdb"
|
||||||
"github.com/tidwall/geojson"
|
"github.com/tidwall/geojson"
|
||||||
|
"github.com/tidwall/geojson/geometry"
|
||||||
"github.com/tidwall/resp"
|
"github.com/tidwall/resp"
|
||||||
"github.com/tidwall/tile38/core"
|
"github.com/tidwall/tile38/core"
|
||||||
"github.com/tidwall/tile38/internal/collection"
|
"github.com/tidwall/tile38/internal/collection"
|
||||||
|
@ -174,8 +175,33 @@ func ListenAndServeEx(host string, port int, dir string, ln *net.Listener, http
|
||||||
if err == nil {
|
if err == nil {
|
||||||
c.geomParseOpts.IndexChildren = int(n)
|
c.geomParseOpts.IndexChildren = int(n)
|
||||||
}
|
}
|
||||||
log.Debugf("geom indexing: %d", c.geomParseOpts.IndexGeometry)
|
indexKind := os.Getenv("T38IDXGEOMKIND")
|
||||||
log.Debugf("multi indexing: %d", c.geomParseOpts.IndexChildren)
|
switch indexKind {
|
||||||
|
default:
|
||||||
|
log.Errorf("Unknown index kind: %s", indexKind)
|
||||||
|
case "":
|
||||||
|
case "None":
|
||||||
|
c.geomParseOpts.IndexGeometryKind = geometry.None
|
||||||
|
case "RTree":
|
||||||
|
c.geomParseOpts.IndexGeometryKind = geometry.RTree
|
||||||
|
case "RTreeCompressed":
|
||||||
|
c.geomParseOpts.IndexGeometryKind = geometry.RTreeCompressed
|
||||||
|
case "QuadTree":
|
||||||
|
c.geomParseOpts.IndexGeometryKind = geometry.QuadTree
|
||||||
|
case "QuadTreeCompressed":
|
||||||
|
c.geomParseOpts.IndexGeometryKind = geometry.QuadTreeCompressed
|
||||||
|
}
|
||||||
|
if c.geomParseOpts.IndexGeometryKind == geometry.None {
|
||||||
|
log.Debugf("Geom indexing: %s",
|
||||||
|
c.geomParseOpts.IndexGeometryKind,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
log.Debugf("Geom indexing: %s (%d points)",
|
||||||
|
c.geomParseOpts.IndexGeometryKind,
|
||||||
|
c.geomParseOpts.IndexGeometry,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
log.Debugf("Multi indexing: RTree (%d points)", c.geomParseOpts.IndexChildren)
|
||||||
|
|
||||||
// load the queue before the aof
|
// load the queue before the aof
|
||||||
qdb, err := buntdb.Open(core.QueueFileName)
|
qdb, err := buntdb.Open(core.QueueFileName)
|
||||||
|
|
|
@ -131,7 +131,7 @@ func fenceMatch(
|
||||||
[]geometry.Point{
|
[]geometry.Point{
|
||||||
details.oldObj.Center(),
|
details.oldObj.Center(),
|
||||||
details.obj.Center(),
|
details.obj.Center(),
|
||||||
}, geometry.DefaultIndex))
|
}, nil))
|
||||||
temp := false
|
temp := false
|
||||||
if fence.cmd == "within" {
|
if fence.cmd == "within" {
|
||||||
// because we are testing if the line croses the area we need to use
|
// because we are testing if the line croses the area we need to use
|
||||||
|
|
|
@ -37,12 +37,12 @@ func NewCircle(center geometry.Point, meters float64, steps int) *Circle {
|
||||||
points = append(points, geometry.Point{X: lon, Y: lat})
|
points = append(points, geometry.Point{X: lon, Y: lat})
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
// TODO: account for the pole and antimerdian. In most cases only a polygon
|
// TODO: account for the pole and antimerdian. In most cases only a
|
||||||
// is needed, but when the circle bounds passes the 90/180 lines, we need
|
// polygon is needed, but when the circle bounds passes the 90/180
|
||||||
// to create a multipolygon
|
// lines, we need to create a multipolygon
|
||||||
points = append(points, points[0])
|
points = append(points, points[0])
|
||||||
g.Object = NewPolygon(
|
g.Object = NewPolygon(
|
||||||
geometry.NewPoly(points, nil, geometry.DefaultIndex),
|
geometry.NewPoly(points, nil, geometry.DefaultIndexOptions),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return g
|
return g
|
||||||
|
|
|
@ -30,7 +30,7 @@ func P(x, y float64) Point {
|
||||||
return Point{x, y}
|
return Point{x, y}
|
||||||
}
|
}
|
||||||
func L(points ...Point) *Line {
|
func L(points ...Point) *Line {
|
||||||
return NewLine(points, DefaultIndex)
|
return NewLine(points, DefaultIndexOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -10,9 +10,9 @@ type Line struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewLine creates a new Line
|
// NewLine creates a new Line
|
||||||
func NewLine(points []Point, index int) *Line {
|
func NewLine(points []Point, opts *IndexOptions) *Line {
|
||||||
line := new(Line)
|
line := new(Line)
|
||||||
line.baseSeries = makeSeries(points, true, false, index)
|
line.baseSeries = makeSeries(points, true, false, opts)
|
||||||
return line
|
return line
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ package geometry
|
||||||
import "testing"
|
import "testing"
|
||||||
|
|
||||||
func TestLineNewLine(t *testing.T) {
|
func TestLineNewLine(t *testing.T) {
|
||||||
line := NewLine(u1, DefaultIndex)
|
line := NewLine(u1, DefaultIndexOptions)
|
||||||
expect(t, !line.Empty())
|
expect(t, !line.Empty())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,12 +43,12 @@ func TestLineMove(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLineContainsPoint(t *testing.T) {
|
func TestLineContainsPoint(t *testing.T) {
|
||||||
line := NewLine(u1, DefaultIndex)
|
line := NewLine(u1, DefaultIndexOptions)
|
||||||
expect(t, line.ContainsPoint(P(0, 0)))
|
expect(t, line.ContainsPoint(P(0, 0)))
|
||||||
expect(t, line.ContainsPoint(P(10, 10)))
|
expect(t, line.ContainsPoint(P(10, 10)))
|
||||||
expect(t, line.ContainsPoint(P(0, 5)))
|
expect(t, line.ContainsPoint(P(0, 5)))
|
||||||
expect(t, !line.ContainsPoint(P(5, 5)))
|
expect(t, !line.ContainsPoint(P(5, 5)))
|
||||||
line = NewLine(v1, DefaultIndex)
|
line = NewLine(v1, DefaultIndexOptions)
|
||||||
expect(t, line.ContainsPoint(P(0, 10)))
|
expect(t, line.ContainsPoint(P(0, 10)))
|
||||||
expect(t, !line.ContainsPoint(P(0, 0)))
|
expect(t, !line.ContainsPoint(P(0, 0)))
|
||||||
expect(t, line.ContainsPoint(P(5, 0)))
|
expect(t, line.ContainsPoint(P(5, 0)))
|
||||||
|
@ -59,7 +59,7 @@ func TestLineContainsPoint(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLineIntersectsPoint(t *testing.T) {
|
func TestLineIntersectsPoint(t *testing.T) {
|
||||||
line := NewLine(v1, DefaultIndex)
|
line := NewLine(v1, DefaultIndexOptions)
|
||||||
expect(t, line.IntersectsPoint(P(0, 10)))
|
expect(t, line.IntersectsPoint(P(0, 10)))
|
||||||
expect(t, !line.IntersectsPoint(P(0, 0)))
|
expect(t, !line.IntersectsPoint(P(0, 0)))
|
||||||
expect(t, line.IntersectsPoint(P(5, 0)))
|
expect(t, line.IntersectsPoint(P(5, 0)))
|
||||||
|
@ -70,10 +70,10 @@ func TestLineIntersectsPoint(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLineContainsRect(t *testing.T) {
|
func TestLineContainsRect(t *testing.T) {
|
||||||
line := NewLine(v1, DefaultIndex)
|
line := NewLine(v1, DefaultIndexOptions)
|
||||||
expect(t, !line.ContainsRect(R(0, 0, 10, 10)))
|
expect(t, !line.ContainsRect(R(0, 0, 10, 10)))
|
||||||
expect(t, line.ContainsRect(R(0, 10, 0, 10)))
|
expect(t, line.ContainsRect(R(0, 10, 0, 10)))
|
||||||
line = NewLine(u1, DefaultIndex)
|
line = NewLine(u1, DefaultIndexOptions)
|
||||||
expect(t, line.ContainsRect(R(0, 0, 0, 10)))
|
expect(t, line.ContainsRect(R(0, 0, 0, 10)))
|
||||||
line = nil
|
line = nil
|
||||||
expect(t, !line.ContainsRect(Rect{}))
|
expect(t, !line.ContainsRect(Rect{}))
|
||||||
|
@ -81,7 +81,7 @@ func TestLineContainsRect(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLineIntersectsRect(t *testing.T) {
|
func TestLineIntersectsRect(t *testing.T) {
|
||||||
line := NewLine(v1, DefaultIndex)
|
line := NewLine(v1, DefaultIndexOptions)
|
||||||
expect(t, line.IntersectsRect(R(0, 0, 10, 10)))
|
expect(t, line.IntersectsRect(R(0, 0, 10, 10)))
|
||||||
expect(t, line.IntersectsRect(R(0, 0, 2.5, 5)))
|
expect(t, line.IntersectsRect(R(0, 0, 2.5, 5)))
|
||||||
expect(t, !line.IntersectsRect(R(0, 0, 2.4, 5)))
|
expect(t, !line.IntersectsRect(R(0, 0, 2.4, 5)))
|
||||||
|
@ -130,15 +130,15 @@ func TestLineIntersectsLine(t *testing.T) {
|
||||||
lns := [][]Point{u1, u2, u3, u4, v1, v2, v3, v4}
|
lns := [][]Point{u1, u2, u3, u4, v1, v2, v3, v4}
|
||||||
for i := 0; i < len(lns); i++ {
|
for i := 0; i < len(lns); i++ {
|
||||||
for j := 0; j < len(lns); j++ {
|
for j := 0; j < len(lns); j++ {
|
||||||
expect(t, NewLine(lns[i], DefaultIndex).IntersectsLine(
|
expect(t, NewLine(lns[i], DefaultIndexOptions).IntersectsLine(
|
||||||
NewLine(lns[j], DefaultIndex),
|
NewLine(lns[j], DefaultIndexOptions),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
line := NewLine(u1, DefaultIndex)
|
line := NewLine(u1, DefaultIndexOptions)
|
||||||
expect(t, !line.IntersectsLine(NewLine(nil, DefaultIndex)))
|
expect(t, !line.IntersectsLine(NewLine(nil, DefaultIndexOptions)))
|
||||||
expect(t, !NewLine(nil, DefaultIndex).IntersectsLine(NewLine(nil, DefaultIndex)))
|
expect(t, !NewLine(nil, DefaultIndexOptions).IntersectsLine(NewLine(nil, DefaultIndexOptions)))
|
||||||
expect(t, !NewLine(nil, DefaultIndex).IntersectsLine(line))
|
expect(t, !NewLine(nil, DefaultIndexOptions).IntersectsLine(line))
|
||||||
expect(t, line.IntersectsLine(line.Move(5, 0)))
|
expect(t, line.IntersectsLine(line.Move(5, 0)))
|
||||||
expect(t, line.IntersectsLine(line.Move(10, 0)))
|
expect(t, line.IntersectsLine(line.Move(10, 0)))
|
||||||
expect(t, !line.IntersectsLine(line.Move(11, 0)))
|
expect(t, !line.IntersectsLine(line.Move(11, 0)))
|
||||||
|
@ -147,27 +147,27 @@ func TestLineIntersectsLine(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLineContainsPoly(t *testing.T) {
|
func TestLineContainsPoly(t *testing.T) {
|
||||||
line := NewLine(u1, DefaultIndex)
|
line := NewLine(u1, DefaultIndexOptions)
|
||||||
poly := NewPoly(octagon, nil, DefaultIndex)
|
poly := NewPoly(octagon, nil, DefaultIndexOptions)
|
||||||
expect(t, !line.ContainsPoly(poly))
|
expect(t, !line.ContainsPoly(poly))
|
||||||
expect(t, line.ContainsPoly(NewPoly(
|
expect(t, line.ContainsPoly(NewPoly(
|
||||||
[]Point{P(0, 10), P(0, 0), P(0, 10)},
|
[]Point{P(0, 10), P(0, 0), P(0, 10)},
|
||||||
nil, DefaultIndex,
|
nil, DefaultIndexOptions,
|
||||||
)))
|
)))
|
||||||
expect(t, line.ContainsPoly(NewPoly(
|
expect(t, line.ContainsPoly(NewPoly(
|
||||||
[]Point{P(0, 0), P(10, 0), P(0, 0)},
|
[]Point{P(0, 0), P(10, 0), P(0, 0)},
|
||||||
nil, DefaultIndex,
|
nil, DefaultIndexOptions,
|
||||||
)))
|
)))
|
||||||
expect(t, !L().ContainsPoly(NewPoly(
|
expect(t, !L().ContainsPoly(NewPoly(
|
||||||
[]Point{P(0, 0), P(10, 0), P(0, 0)},
|
[]Point{P(0, 0), P(10, 0), P(0, 0)},
|
||||||
nil, DefaultIndex,
|
nil, DefaultIndexOptions,
|
||||||
)))
|
)))
|
||||||
expect(t, !line.ContainsPoly(NewPoly(nil, nil, DefaultIndex)))
|
expect(t, !line.ContainsPoly(NewPoly(nil, nil, DefaultIndexOptions)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLineIntersectsPoly(t *testing.T) {
|
func TestLineIntersectsPoly(t *testing.T) {
|
||||||
line := NewLine(u1, DefaultIndex)
|
line := NewLine(u1, DefaultIndexOptions)
|
||||||
poly := NewPoly(octagon, nil, DefaultIndex)
|
poly := NewPoly(octagon, nil, DefaultIndexOptions)
|
||||||
expect(t, line.IntersectsPoly(poly))
|
expect(t, line.IntersectsPoly(poly))
|
||||||
expect(t, line.IntersectsPoly(poly.Move(5, 0)))
|
expect(t, line.IntersectsPoly(poly.Move(5, 0)))
|
||||||
expect(t, line.IntersectsPoly(poly.Move(10, 0)))
|
expect(t, line.IntersectsPoly(poly.Move(10, 0)))
|
||||||
|
|
|
@ -17,10 +17,10 @@ func testBig(
|
||||||
) {
|
) {
|
||||||
N, T := 100000, 4
|
N, T := 100000, 4
|
||||||
|
|
||||||
simple := newRing(points, DefaultIndex)
|
simple := newRing(points, DefaultIndexOptions)
|
||||||
simple.(*baseSeries).tree = nil
|
simple.(*baseSeries).clearIndex()
|
||||||
tree := newRing(points, DefaultIndex)
|
tree := newRing(points, DefaultIndexOptions)
|
||||||
tree.(*baseSeries).buildTree()
|
tree.(*baseSeries).buildIndex()
|
||||||
pointOn := points[len(points)/2]
|
pointOn := points[len(points)/2]
|
||||||
|
|
||||||
// ioutil.WriteFile(label+".svg", []byte(tools.SVG(tree.(*baseSeries).tree)), 0666)
|
// ioutil.WriteFile(label+".svg", []byte(tools.SVG(tree.(*baseSeries).tree)), 0666)
|
||||||
|
|
|
@ -74,17 +74,17 @@ func TestPointIntersectsLine(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPointContainsPoly(t *testing.T) {
|
func TestPointContainsPoly(t *testing.T) {
|
||||||
expect(t, !P(5, 5).ContainsPoly(NewPoly(nil, nil, DefaultIndex)))
|
expect(t, !P(5, 5).ContainsPoly(NewPoly(nil, nil, DefaultIndexOptions)))
|
||||||
expect(t, !P(5, 5).ContainsPoly(NewPoly([]Point{P(0, 0), P(10, 0)}, nil, DefaultIndex)))
|
expect(t, !P(5, 5).ContainsPoly(NewPoly([]Point{P(0, 0), P(10, 0)}, nil, DefaultIndexOptions)))
|
||||||
expect(t, !P(5, 5).ContainsPoly(&Poly{Exterior: R(0, 0, 10, 10)}))
|
expect(t, !P(5, 5).ContainsPoly(&Poly{Exterior: R(0, 0, 10, 10)}))
|
||||||
expect(t, P(5, 5).ContainsPoly(&Poly{Exterior: R(5, 5, 5, 5)}))
|
expect(t, P(5, 5).ContainsPoly(&Poly{Exterior: R(5, 5, 5, 5)}))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPointIntersectsPoly(t *testing.T) {
|
func TestPointIntersectsPoly(t *testing.T) {
|
||||||
octa := NewPoly(octagon, nil, DefaultIndex)
|
octa := NewPoly(octagon, nil, DefaultIndexOptions)
|
||||||
concave1 := NewPoly(concave1, nil, DefaultIndex)
|
concave1 := NewPoly(concave1, nil, DefaultIndexOptions)
|
||||||
expect(t, !P(5, 5).IntersectsPoly(NewPoly(nil, nil, DefaultIndex)))
|
expect(t, !P(5, 5).IntersectsPoly(NewPoly(nil, nil, DefaultIndexOptions)))
|
||||||
expect(t, !P(5, 5).IntersectsPoly(NewPoly([]Point{P(0, 0), P(10, 0)}, nil, DefaultIndex)))
|
expect(t, !P(5, 5).IntersectsPoly(NewPoly([]Point{P(0, 0), P(10, 0)}, nil, DefaultIndexOptions)))
|
||||||
expect(t, P(5, 5).IntersectsPoly(octa))
|
expect(t, P(5, 5).IntersectsPoly(octa))
|
||||||
expect(t, P(0, 5).IntersectsPoly(octa))
|
expect(t, P(0, 5).IntersectsPoly(octa))
|
||||||
expect(t, !P(1, 1).IntersectsPoly(octa))
|
expect(t, !P(1, 1).IntersectsPoly(octa))
|
||||||
|
|
|
@ -11,13 +11,13 @@ type Poly struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPoly ...
|
// NewPoly ...
|
||||||
func NewPoly(exterior []Point, holes [][]Point, index int) *Poly {
|
func NewPoly(exterior []Point, holes [][]Point, opts *IndexOptions) *Poly {
|
||||||
poly := new(Poly)
|
poly := new(Poly)
|
||||||
poly.Exterior = newRing(exterior, index)
|
poly.Exterior = newRing(exterior, opts)
|
||||||
if len(holes) > 0 {
|
if len(holes) > 0 {
|
||||||
poly.Holes = make([]Ring, len(holes))
|
poly.Holes = make([]Ring, len(holes))
|
||||||
for i := range holes {
|
for i := range holes {
|
||||||
poly.Holes[i] = newRing(holes[i], index)
|
poly.Holes[i] = newRing(holes[i], opts)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return poly
|
return poly
|
||||||
|
@ -60,7 +60,7 @@ func (poly *Poly) Move(deltaX, deltaY float64) *Poly {
|
||||||
npoly.Exterior = Ring(series.Move(deltaX, deltaY))
|
npoly.Exterior = Ring(series.Move(deltaX, deltaY))
|
||||||
} else {
|
} else {
|
||||||
nseries := makeSeries(
|
nseries := makeSeries(
|
||||||
seriesCopyPoints(poly.Exterior), false, true, DefaultIndex)
|
seriesCopyPoints(poly.Exterior), false, true, DefaultIndexOptions)
|
||||||
npoly.Exterior = Ring(nseries.Move(deltaX, deltaY))
|
npoly.Exterior = Ring(nseries.Move(deltaX, deltaY))
|
||||||
}
|
}
|
||||||
if len(poly.Holes) > 0 {
|
if len(poly.Holes) > 0 {
|
||||||
|
@ -70,7 +70,7 @@ func (poly *Poly) Move(deltaX, deltaY float64) *Poly {
|
||||||
npoly.Holes[i] = Ring(series.Move(deltaX, deltaY))
|
npoly.Holes[i] = Ring(series.Move(deltaX, deltaY))
|
||||||
} else {
|
} else {
|
||||||
nseries := makeSeries(
|
nseries := makeSeries(
|
||||||
seriesCopyPoints(hole), false, true, DefaultIndex)
|
seriesCopyPoints(hole), false, true, DefaultIndexOptions)
|
||||||
npoly.Holes[i] = Ring(nseries.Move(deltaX, deltaY))
|
npoly.Holes[i] = Ring(nseries.Move(deltaX, deltaY))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,19 +9,19 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func newPolyIndexed(exterior []Point, holes [][]Point) *Poly {
|
func newPolyIndexed(exterior []Point, holes [][]Point) *Poly {
|
||||||
poly := NewPoly(exterior, holes, DefaultIndex)
|
poly := NewPoly(exterior, holes, DefaultIndexOptions)
|
||||||
poly.Exterior.(*baseSeries).buildTree()
|
poly.Exterior.(*baseSeries).buildIndex()
|
||||||
for _, hole := range poly.Holes {
|
for _, hole := range poly.Holes {
|
||||||
hole.(*baseSeries).buildTree()
|
hole.(*baseSeries).buildIndex()
|
||||||
}
|
}
|
||||||
return poly
|
return poly
|
||||||
}
|
}
|
||||||
|
|
||||||
func newPolySimple(exterior []Point, holes [][]Point) *Poly {
|
func newPolySimple(exterior []Point, holes [][]Point) *Poly {
|
||||||
poly := NewPoly(exterior, holes, DefaultIndex)
|
poly := NewPoly(exterior, holes, DefaultIndexOptions)
|
||||||
poly.Exterior.(*baseSeries).tree = nil
|
poly.Exterior.(*baseSeries).clearIndex()
|
||||||
for _, hole := range poly.Holes {
|
for _, hole := range poly.Holes {
|
||||||
hole.(*baseSeries).tree = nil
|
hole.(*baseSeries).clearIndex()
|
||||||
}
|
}
|
||||||
return poly
|
return poly
|
||||||
}
|
}
|
||||||
|
@ -169,22 +169,22 @@ func TestPolyIntersectsLine(t *testing.T) {
|
||||||
func TestPolyContainsPoly(t *testing.T) {
|
func TestPolyContainsPoly(t *testing.T) {
|
||||||
holes1 := [][]Point{[]Point{{4, 4}, {6, 4}, {6, 6}, {4, 6}, {4, 4}}}
|
holes1 := [][]Point{[]Point{{4, 4}, {6, 4}, {6, 6}, {4, 6}, {4, 4}}}
|
||||||
holes2 := [][]Point{[]Point{{5, 4}, {7, 4}, {7, 6}, {5, 6}, {5, 4}}}
|
holes2 := [][]Point{[]Point{{5, 4}, {7, 4}, {7, 6}, {5, 6}, {5, 4}}}
|
||||||
poly1 := NewPoly(octagon, holes1, DefaultIndex)
|
poly1 := NewPoly(octagon, holes1, DefaultIndexOptions)
|
||||||
poly2 := NewPoly(octagon, holes2, DefaultIndex)
|
poly2 := NewPoly(octagon, holes2, DefaultIndexOptions)
|
||||||
|
|
||||||
expect(t, !poly1.ContainsPoly(NewPoly(holes2[0], nil, DefaultIndex)))
|
expect(t, !poly1.ContainsPoly(NewPoly(holes2[0], nil, DefaultIndexOptions)))
|
||||||
expect(t, !poly1.ContainsPoly(poly2))
|
expect(t, !poly1.ContainsPoly(poly2))
|
||||||
|
|
||||||
dualPolyTest(t, octagon, holes1, func(t *testing.T, poly *Poly) {
|
dualPolyTest(t, octagon, holes1, func(t *testing.T, poly *Poly) {
|
||||||
expect(t, poly.ContainsPoly(poly1))
|
expect(t, poly.ContainsPoly(poly1))
|
||||||
expect(t, !poly.ContainsPoly(poly1.Move(1, 0)))
|
expect(t, !poly.ContainsPoly(poly1.Move(1, 0)))
|
||||||
expect(t, poly.ContainsPoly(NewPoly(holes1[0], nil, DefaultIndex)))
|
expect(t, poly.ContainsPoly(NewPoly(holes1[0], nil, DefaultIndexOptions)))
|
||||||
expect(t, !poly.ContainsPoly(NewPoly(holes2[0], nil, DefaultIndex)))
|
expect(t, !poly.ContainsPoly(NewPoly(holes2[0], nil, DefaultIndexOptions)))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPolyClockwise(t *testing.T) {
|
func TestPolyClockwise(t *testing.T) {
|
||||||
expect(t, !NewPoly(bowtie, nil, DefaultIndex).Clockwise())
|
expect(t, !NewPoly(bowtie, nil, DefaultIndexOptions).Clockwise())
|
||||||
var poly *Poly
|
var poly *Poly
|
||||||
expect(t, !poly.Clockwise())
|
expect(t, !poly.Clockwise())
|
||||||
}
|
}
|
||||||
|
@ -212,28 +212,28 @@ func Test369(t *testing.T) {
|
||||||
{-122.44109272956847, 37.7326855231885},
|
{-122.44109272956847, 37.7326855231885},
|
||||||
{-122.44109272956847, 37.731870943026074},
|
{-122.44109272956847, 37.731870943026074},
|
||||||
},
|
},
|
||||||
}, DefaultIndex)
|
}, DefaultIndexOptions)
|
||||||
a := NewPoly([]Point{
|
a := NewPoly([]Point{
|
||||||
{-122.4408378, 37.7341129},
|
{-122.4408378, 37.7341129},
|
||||||
{-122.4408378, 37.733},
|
{-122.4408378, 37.733},
|
||||||
{-122.44, 37.733},
|
{-122.44, 37.733},
|
||||||
{-122.44, 37.7343129},
|
{-122.44, 37.7343129},
|
||||||
{-122.4408378, 37.7341129},
|
{-122.4408378, 37.7341129},
|
||||||
}, nil, DefaultIndex)
|
}, nil, DefaultIndexOptions)
|
||||||
b := NewPoly([]Point{
|
b := NewPoly([]Point{
|
||||||
{-122.44091033935547, 37.731981251280985},
|
{-122.44091033935547, 37.731981251280985},
|
||||||
{-122.43994474411011, 37.731981251280985},
|
{-122.43994474411011, 37.731981251280985},
|
||||||
{-122.43994474411011, 37.73254976045042},
|
{-122.43994474411011, 37.73254976045042},
|
||||||
{-122.44091033935547, 37.73254976045042},
|
{-122.44091033935547, 37.73254976045042},
|
||||||
{-122.44091033935547, 37.731981251280985},
|
{-122.44091033935547, 37.731981251280985},
|
||||||
}, nil, DefaultIndex)
|
}, nil, DefaultIndexOptions)
|
||||||
c := NewPoly([]Point{
|
c := NewPoly([]Point{
|
||||||
{-122.4408378, 37.7341129},
|
{-122.4408378, 37.7341129},
|
||||||
{-122.4408378, 37.733},
|
{-122.4408378, 37.733},
|
||||||
{-122.44, 37.733},
|
{-122.44, 37.733},
|
||||||
{-122.44, 37.7341129},
|
{-122.44, 37.7341129},
|
||||||
{-122.4408378, 37.7341129},
|
{-122.4408378, 37.7341129},
|
||||||
}, nil, DefaultIndex)
|
}, nil, DefaultIndexOptions)
|
||||||
expect(t, polyHoles.IntersectsPoly(a))
|
expect(t, polyHoles.IntersectsPoly(a))
|
||||||
expect(t, !polyHoles.IntersectsPoly(b))
|
expect(t, !polyHoles.IntersectsPoly(b))
|
||||||
expect(t, !polyHoles.IntersectsPoly(c))
|
expect(t, !polyHoles.IntersectsPoly(c))
|
||||||
|
|
|
@ -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) {
|
func TestRectContainsPoly(t *testing.T) {
|
||||||
oct := NewPoly(octagon, nil, DefaultIndex)
|
oct := NewPoly(octagon, nil, DefaultIndexOptions)
|
||||||
expect(t, R(0, 0, 10, 10).ContainsPoly(oct))
|
expect(t, R(0, 0, 10, 10).ContainsPoly(oct))
|
||||||
expect(t, !R(0, 0, 10, 10).ContainsPoly(oct.Move(1, 0)))
|
expect(t, !R(0, 0, 10, 10).ContainsPoly(oct.Move(1, 0)))
|
||||||
expect(t, !R(0, 0, 10, 10).ContainsPoly(oct.Move(1, 1)))
|
expect(t, !R(0, 0, 10, 10).ContainsPoly(oct.Move(1, 1)))
|
||||||
expect(t, !R(0, 0, 10, 10).ContainsPoly(NewPoly(nil, nil, DefaultIndex)))
|
expect(t, !R(0, 0, 10, 10).ContainsPoly(NewPoly(nil, nil, DefaultIndexOptions)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRectIntersectsPoly(t *testing.T) {
|
func TestRectIntersectsPoly(t *testing.T) {
|
||||||
oct := NewPoly(octagon, nil, DefaultIndex)
|
oct := NewPoly(octagon, nil, DefaultIndexOptions)
|
||||||
expect(t, R(0, 0, 10, 10).IntersectsPoly(oct))
|
expect(t, R(0, 0, 10, 10).IntersectsPoly(oct))
|
||||||
expect(t, R(0, 0, 10, 10).IntersectsPoly(oct.Move(1, 0)))
|
expect(t, R(0, 0, 10, 10).IntersectsPoly(oct.Move(1, 0)))
|
||||||
expect(t, R(0, 0, 10, 10).IntersectsPoly(oct.Move(0, 1)))
|
expect(t, R(0, 0, 10, 10).IntersectsPoly(oct.Move(0, 1)))
|
||||||
expect(t, !R(0, 0, 10, 10).IntersectsPoly(oct.Move(10, 10)))
|
expect(t, !R(0, 0, 10, 10).IntersectsPoly(oct.Move(10, 10)))
|
||||||
expect(t, !R(0, 0, 10, 10).IntersectsPoly(oct.Move(11, 10)))
|
expect(t, !R(0, 0, 10, 10).IntersectsPoly(oct.Move(11, 10)))
|
||||||
expect(t, !R(0, 0, 10, 10).IntersectsPoly(oct.Move(-11, 0)))
|
expect(t, !R(0, 0, 10, 10).IntersectsPoly(oct.Move(-11, 0)))
|
||||||
expect(t, !R(0, 0, 10, 10).IntersectsPoly(NewPoly(nil, nil, DefaultIndex)))
|
expect(t, !R(0, 0, 10, 10).IntersectsPoly(NewPoly(nil, nil, DefaultIndexOptions)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRectClockwise(t *testing.T) {
|
func TestRectClockwise(t *testing.T) {
|
||||||
|
|
|
@ -13,8 +13,8 @@ const complexRingMinPoints = 16
|
||||||
// Ring ...
|
// Ring ...
|
||||||
type Ring = Series
|
type Ring = Series
|
||||||
|
|
||||||
func newRing(points []Point, index int) Ring {
|
func newRing(points []Point, opts *IndexOptions) Ring {
|
||||||
series := makeSeries(points, true, true, index)
|
series := makeSeries(points, true, true, opts)
|
||||||
return &series
|
return &series
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,16 +10,16 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func newRingXSimple(points []Point) Ring {
|
func newRingXSimple(points []Point) Ring {
|
||||||
ring := newRing(points, DefaultIndex)
|
ring := newRing(points, DefaultIndexOptions)
|
||||||
if ring.(*baseSeries).tree != nil {
|
if ring.(*baseSeries).Index() != nil {
|
||||||
ring.(*baseSeries).tree = nil
|
ring.(*baseSeries).clearIndex()
|
||||||
}
|
}
|
||||||
return ring
|
return ring
|
||||||
}
|
}
|
||||||
func newRingXIndexed(points []Point) Ring {
|
func newRingXIndexed(points []Point) Ring {
|
||||||
ring := newRing(points, DefaultIndex)
|
ring := newRing(points, DefaultIndexOptions)
|
||||||
if ring.(*baseSeries).tree == nil {
|
if ring.(*baseSeries).Index() == nil {
|
||||||
ring.(*baseSeries).buildTree()
|
ring.(*baseSeries).buildIndex()
|
||||||
}
|
}
|
||||||
return ring
|
return ring
|
||||||
}
|
}
|
||||||
|
@ -66,7 +66,7 @@ func TestRingXContainsPoint(t *testing.T) {
|
||||||
P(3, 4), P(1, 4),
|
P(3, 4), P(1, 4),
|
||||||
P(0, 3), P(0, 0),
|
P(0, 3), P(0, 0),
|
||||||
}
|
}
|
||||||
ring := newRing(shape, DefaultIndex)
|
ring := newRing(shape, DefaultIndexOptions)
|
||||||
expect(t, !ringContainsPoint(ring, P(0, 3.5), true).hit)
|
expect(t, !ringContainsPoint(ring, P(0, 3.5), true).hit)
|
||||||
expect(t, !ringContainsPoint(ring, P(0.4, 3.5), true).hit)
|
expect(t, !ringContainsPoint(ring, P(0.4, 3.5), true).hit)
|
||||||
expect(t, ringContainsPoint(ring, P(0.5, 3.5), true).hit)
|
expect(t, ringContainsPoint(ring, P(0.5, 3.5), true).hit)
|
||||||
|
@ -97,7 +97,7 @@ func TestRingXIntersectsSegment(t *testing.T) {
|
||||||
t.Run("Cases", func(t *testing.T) {
|
t.Run("Cases", func(t *testing.T) {
|
||||||
ring := newRing([]Point{
|
ring := newRing([]Point{
|
||||||
P(0, 0), P(4, 0), P(4, 4), P(0, 4), P(0, 0),
|
P(0, 0), P(4, 0), P(4, 4), P(0, 4), P(0, 0),
|
||||||
}, DefaultIndex)
|
}, DefaultIndexOptions)
|
||||||
t.Run("1", func(t *testing.T) {
|
t.Run("1", func(t *testing.T) {
|
||||||
expect(t, ringIntersectsSegment(ring, S(2, 2, 4, 4), true))
|
expect(t, ringIntersectsSegment(ring, S(2, 2, 4, 4), true))
|
||||||
expect(t, ringIntersectsSegment(ring, S(2, 2, 4, 4), false))
|
expect(t, ringIntersectsSegment(ring, S(2, 2, 4, 4), false))
|
||||||
|
@ -145,42 +145,42 @@ func TestRingXIntersectsSegment(t *testing.T) {
|
||||||
t.Run("12", func(t *testing.T) {
|
t.Run("12", func(t *testing.T) {
|
||||||
ring := newRing([]Point{
|
ring := newRing([]Point{
|
||||||
{0, 0}, {2, 0}, {4, 0}, {4, 4}, {0, 4}, {0, 0},
|
{0, 0}, {2, 0}, {4, 0}, {4, 4}, {0, 4}, {0, 0},
|
||||||
}, DefaultIndex)
|
}, DefaultIndexOptions)
|
||||||
expect(t, ringIntersectsSegment(ring, S(1, 0, 3, 0), true))
|
expect(t, ringIntersectsSegment(ring, S(1, 0, 3, 0), true))
|
||||||
expect(t, !ringIntersectsSegment(ring, S(1, 0, 3, 0), false))
|
expect(t, !ringIntersectsSegment(ring, S(1, 0, 3, 0), false))
|
||||||
})
|
})
|
||||||
t.Run("13", func(t *testing.T) {
|
t.Run("13", func(t *testing.T) {
|
||||||
ring := newRing([]Point{
|
ring := newRing([]Point{
|
||||||
{0, 0}, {4, 0}, {4, 4}, {2, 4}, {0, 4}, {0, 0},
|
{0, 0}, {4, 0}, {4, 4}, {2, 4}, {0, 4}, {0, 0},
|
||||||
}, DefaultIndex)
|
}, DefaultIndexOptions)
|
||||||
expect(t, ringIntersectsSegment(ring, S(0, 4, 4, 4), true))
|
expect(t, ringIntersectsSegment(ring, S(0, 4, 4, 4), true))
|
||||||
expect(t, !ringIntersectsSegment(ring, S(0, 4, 4, 4), false))
|
expect(t, !ringIntersectsSegment(ring, S(0, 4, 4, 4), false))
|
||||||
})
|
})
|
||||||
t.Run("14", func(t *testing.T) {
|
t.Run("14", func(t *testing.T) {
|
||||||
ring := newRing([]Point{
|
ring := newRing([]Point{
|
||||||
{0, 0}, {4, 0}, {4, 4}, {2, 4}, {0, 4}, {0, 0},
|
{0, 0}, {4, 0}, {4, 4}, {2, 4}, {0, 4}, {0, 0},
|
||||||
}, DefaultIndex)
|
}, DefaultIndexOptions)
|
||||||
expect(t, ringIntersectsSegment(ring, S(-1, -2, 0, 0), true))
|
expect(t, ringIntersectsSegment(ring, S(-1, -2, 0, 0), true))
|
||||||
expect(t, !ringIntersectsSegment(ring, S(-1, -2, 0, 0), false))
|
expect(t, !ringIntersectsSegment(ring, S(-1, -2, 0, 0), false))
|
||||||
})
|
})
|
||||||
t.Run("15", func(t *testing.T) {
|
t.Run("15", func(t *testing.T) {
|
||||||
ring := newRing([]Point{
|
ring := newRing([]Point{
|
||||||
{0, 0}, {4, 0}, {4, 4}, {2, 4}, {0, 4}, {0, 0},
|
{0, 0}, {4, 0}, {4, 4}, {2, 4}, {0, 4}, {0, 0},
|
||||||
}, DefaultIndex)
|
}, DefaultIndexOptions)
|
||||||
expect(t, ringIntersectsSegment(ring, S(0, 4, 5, 4), true))
|
expect(t, ringIntersectsSegment(ring, S(0, 4, 5, 4), true))
|
||||||
expect(t, !ringIntersectsSegment(ring, S(0, 4, 5, 4), false))
|
expect(t, !ringIntersectsSegment(ring, S(0, 4, 5, 4), false))
|
||||||
})
|
})
|
||||||
t.Run("16", func(t *testing.T) {
|
t.Run("16", func(t *testing.T) {
|
||||||
ring := newRing([]Point{
|
ring := newRing([]Point{
|
||||||
{0, 0}, {4, 0}, {4, 4}, {2, 4}, {0, 4}, {0, 0},
|
{0, 0}, {4, 0}, {4, 4}, {2, 4}, {0, 4}, {0, 0},
|
||||||
}, DefaultIndex)
|
}, DefaultIndexOptions)
|
||||||
expect(t, ringIntersectsSegment(ring, S(1, 4, 5, 4), true))
|
expect(t, ringIntersectsSegment(ring, S(1, 4, 5, 4), true))
|
||||||
expect(t, !ringIntersectsSegment(ring, S(1, 4, 5, 4), false))
|
expect(t, !ringIntersectsSegment(ring, S(1, 4, 5, 4), false))
|
||||||
})
|
})
|
||||||
t.Run("17", func(t *testing.T) {
|
t.Run("17", func(t *testing.T) {
|
||||||
ring := newRing([]Point{
|
ring := newRing([]Point{
|
||||||
{0, 0}, {4, 0}, {4, 4}, {2, 4}, {0, 4}, {0, 0},
|
{0, 0}, {4, 0}, {4, 4}, {2, 4}, {0, 4}, {0, 0},
|
||||||
}, DefaultIndex)
|
}, DefaultIndexOptions)
|
||||||
expect(t, ringIntersectsSegment(ring, S(1, 4, 4, 4), true))
|
expect(t, ringIntersectsSegment(ring, S(1, 4, 4, 4), true))
|
||||||
expect(t, !ringIntersectsSegment(ring, S(1, 4, 4, 4), false))
|
expect(t, !ringIntersectsSegment(ring, S(1, 4, 4, 4), false))
|
||||||
})
|
})
|
||||||
|
@ -815,7 +815,7 @@ func TestRingXContainsSegment(t *testing.T) {
|
||||||
ring := newRing([]Point{
|
ring := newRing([]Point{
|
||||||
P(0, 0), P(4, 0), P(4, 4), P(3, 4),
|
P(0, 0), P(4, 0), P(4, 4), P(3, 4),
|
||||||
P(2, 3), P(1, 4), P(0, 4), P(0, 0),
|
P(2, 3), P(1, 4), P(0, 4), P(0, 0),
|
||||||
}, DefaultIndex)
|
}, DefaultIndexOptions)
|
||||||
t.Run("1", func(t *testing.T) {
|
t.Run("1", func(t *testing.T) {
|
||||||
expect(t, !ringContainsSegment(ring, S(1.5, 3.5, 2.5, 3.5), true))
|
expect(t, !ringContainsSegment(ring, S(1.5, 3.5, 2.5, 3.5), true))
|
||||||
expect(t, !ringContainsSegment(ring, S(1.5, 3.5, 2.5, 3.5), false))
|
expect(t, !ringContainsSegment(ring, S(1.5, 3.5, 2.5, 3.5), false))
|
||||||
|
@ -840,7 +840,7 @@ func TestRingXContainsSegment(t *testing.T) {
|
||||||
ring := newRing([]Point{
|
ring := newRing([]Point{
|
||||||
P(0, 0), P(4, 0), P(4, 4), P(3, 4),
|
P(0, 0), P(4, 0), P(4, 4), P(3, 4),
|
||||||
P(2, 5), P(1, 4), P(0, 4), P(0, 0),
|
P(2, 5), P(1, 4), P(0, 4), P(0, 0),
|
||||||
}, DefaultIndex)
|
}, DefaultIndexOptions)
|
||||||
expect(t, ringContainsSegment(ring, S(1.5, 4.5, 2.5, 4.5), true))
|
expect(t, ringContainsSegment(ring, S(1.5, 4.5, 2.5, 4.5), true))
|
||||||
expect(t, !ringContainsSegment(ring, S(1.5, 4.5, 2.5, 4.5), false))
|
expect(t, !ringContainsSegment(ring, S(1.5, 4.5, 2.5, 4.5), false))
|
||||||
})
|
})
|
||||||
|
@ -849,7 +849,7 @@ func TestRingXContainsSegment(t *testing.T) {
|
||||||
P(0, 0), P(4, 0), P(4, 4), P(3, 4),
|
P(0, 0), P(4, 0), P(4, 4), P(3, 4),
|
||||||
P(2.5, 3), P(2, 4), P(1.5, 3),
|
P(2.5, 3), P(2, 4), P(1.5, 3),
|
||||||
P(1, 4), P(0, 4), P(0, 0),
|
P(1, 4), P(0, 4), P(0, 0),
|
||||||
}, DefaultIndex)
|
}, DefaultIndexOptions)
|
||||||
expect(t, !ringContainsSegment(ring, S(1.25, 3.5, 2.75, 3.5), true))
|
expect(t, !ringContainsSegment(ring, S(1.25, 3.5, 2.75, 3.5), true))
|
||||||
expect(t, !ringContainsSegment(ring, S(1.25, 3.5, 2.75, 3.5), false))
|
expect(t, !ringContainsSegment(ring, S(1.25, 3.5, 2.75, 3.5), false))
|
||||||
})
|
})
|
||||||
|
@ -858,7 +858,7 @@ func TestRingXContainsSegment(t *testing.T) {
|
||||||
P(0, 0), P(4, 0), P(4, 4), P(3, 4),
|
P(0, 0), P(4, 0), P(4, 4), P(3, 4),
|
||||||
P(2.5, 5), P(2, 4), P(1.5, 5),
|
P(2.5, 5), P(2, 4), P(1.5, 5),
|
||||||
P(1, 4), P(0, 4), P(0, 0),
|
P(1, 4), P(0, 4), P(0, 0),
|
||||||
}, DefaultIndex)
|
}, DefaultIndexOptions)
|
||||||
expect(t, !ringContainsSegment(ring, S(1.25, 4.5, 2.75, 4.5), true))
|
expect(t, !ringContainsSegment(ring, S(1.25, 4.5, 2.75, 4.5), true))
|
||||||
expect(t, !ringContainsSegment(ring, S(1.25, 4.5, 2.75, 4.5), false))
|
expect(t, !ringContainsSegment(ring, S(1.25, 4.5, 2.75, 4.5), false))
|
||||||
})
|
})
|
||||||
|
@ -882,14 +882,14 @@ func TestRingXContainsSegment(t *testing.T) {
|
||||||
ring := newRing([]Point{
|
ring := newRing([]Point{
|
||||||
P(0, 0), P(2, 0), P(4, 0), P(4, 4), P(3, 4),
|
P(0, 0), P(2, 0), P(4, 0), P(4, 4), P(3, 4),
|
||||||
P(2, 3), P(1, 4), P(0, 4), P(0, 0),
|
P(2, 3), P(1, 4), P(0, 4), P(0, 0),
|
||||||
}, DefaultIndex)
|
}, DefaultIndexOptions)
|
||||||
expect(t, ringContainsSegment(ring, S(1, 0, 3, 0), true))
|
expect(t, ringContainsSegment(ring, S(1, 0, 3, 0), true))
|
||||||
expect(t, !ringContainsSegment(ring, S(1, 0, 3, 0), false))
|
expect(t, !ringContainsSegment(ring, S(1, 0, 3, 0), false))
|
||||||
})
|
})
|
||||||
t.Run("14", func(t *testing.T) {
|
t.Run("14", func(t *testing.T) {
|
||||||
ring := newRing([]Point{
|
ring := newRing([]Point{
|
||||||
P(0, 0), P(4, 0), P(2, 2), P(0, 4), P(0, 0),
|
P(0, 0), P(4, 0), P(2, 2), P(0, 4), P(0, 0),
|
||||||
}, DefaultIndex)
|
}, DefaultIndexOptions)
|
||||||
expect(t, ringContainsSegment(ring, S(1, 3, 3, 1), true))
|
expect(t, ringContainsSegment(ring, S(1, 3, 3, 1), true))
|
||||||
expect(t, ringContainsSegment(ring, S(3, 1, 1, 3), true))
|
expect(t, ringContainsSegment(ring, S(3, 1, 1, 3), true))
|
||||||
expect(t, !ringContainsSegment(ring, S(1, 3, 3, 1), false))
|
expect(t, !ringContainsSegment(ring, S(1, 3, 3, 1), false))
|
||||||
|
@ -908,24 +908,24 @@ func TestRingXContainsSegment(t *testing.T) {
|
||||||
|
|
||||||
func TestRingXContainsRing(t *testing.T) {
|
func TestRingXContainsRing(t *testing.T) {
|
||||||
t.Run("Empty", func(t *testing.T) {
|
t.Run("Empty", func(t *testing.T) {
|
||||||
expect(t, !ringContainsRing(newRing(nil, DefaultIndex), R(0, 0, 1, 1), true))
|
expect(t, !ringContainsRing(newRing(nil, DefaultIndexOptions), R(0, 0, 1, 1), true))
|
||||||
expect(t, !ringContainsRing(R(0, 0, 1, 1), newRing(nil, DefaultIndex), true))
|
expect(t, !ringContainsRing(R(0, 0, 1, 1), newRing(nil, DefaultIndexOptions), true))
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Exact", func(t *testing.T) {
|
t.Run("Exact", func(t *testing.T) {
|
||||||
expect(t, ringContainsRing(R(0, 0, 1, 1), R(0, 0, 1, 1), true))
|
expect(t, ringContainsRing(R(0, 0, 1, 1), R(0, 0, 1, 1), true))
|
||||||
expect(t, !ringContainsRing(R(0, 0, 1, 1), R(0, 0, 1, 1), false))
|
expect(t, !ringContainsRing(R(0, 0, 1, 1), R(0, 0, 1, 1), false))
|
||||||
expect(t, ringContainsRing(newRing(concave1, DefaultIndex), newRing(concave1, DefaultIndex), true))
|
expect(t, ringContainsRing(newRing(concave1, DefaultIndexOptions), newRing(concave1, DefaultIndexOptions), true))
|
||||||
expect(t, !ringContainsRing(newRing(concave1, DefaultIndex), newRing(concave1, DefaultIndex), false))
|
expect(t, !ringContainsRing(newRing(concave1, DefaultIndexOptions), newRing(concave1, DefaultIndexOptions), false))
|
||||||
expect(t, ringContainsRing(newRing(octagon, DefaultIndex), newRing(octagon, DefaultIndex), true))
|
expect(t, ringContainsRing(newRing(octagon, DefaultIndexOptions), newRing(octagon, DefaultIndexOptions), true))
|
||||||
expect(t, !ringContainsRing(newRing(octagon, DefaultIndex), newRing(octagon, DefaultIndex), false))
|
expect(t, !ringContainsRing(newRing(octagon, DefaultIndexOptions), newRing(octagon, DefaultIndexOptions), false))
|
||||||
})
|
})
|
||||||
t.Run("Cases", func(t *testing.T) {
|
t.Run("Cases", func(t *testing.T) {
|
||||||
// concave
|
// concave
|
||||||
ring := newRing([]Point{
|
ring := newRing([]Point{
|
||||||
P(0, 0), P(4, 0), P(4, 4), P(3, 4),
|
P(0, 0), P(4, 0), P(4, 4), P(3, 4),
|
||||||
P(2, 3), P(1, 4), P(0, 4), P(0, 0),
|
P(2, 3), P(1, 4), P(0, 4), P(0, 0),
|
||||||
}, DefaultIndex)
|
}, DefaultIndexOptions)
|
||||||
t.Run("1", func(t *testing.T) {
|
t.Run("1", func(t *testing.T) {
|
||||||
expect(t, ringContainsRing(ring, R(1, 1, 3, 2), true))
|
expect(t, ringContainsRing(ring, R(1, 1, 3, 2), true))
|
||||||
expect(t, ringContainsRing(ring, R(1, 1, 3, 2), false))
|
expect(t, ringContainsRing(ring, R(1, 1, 3, 2), false))
|
||||||
|
@ -951,7 +951,7 @@ func TestRingXContainsRing(t *testing.T) {
|
||||||
P(0, 0), P(4, 0), P(4, 4),
|
P(0, 0), P(4, 0), P(4, 4),
|
||||||
P(3, 4), P(2, 5), P(1, 4),
|
P(3, 4), P(2, 5), P(1, 4),
|
||||||
P(0, 4), P(0, 0),
|
P(0, 4), P(0, 0),
|
||||||
}, DefaultIndex)
|
}, DefaultIndexOptions)
|
||||||
t.Run("6", func(t *testing.T) {
|
t.Run("6", func(t *testing.T) {
|
||||||
expect(t, ringContainsRing(ring, R(1, 2, 3, 3), true))
|
expect(t, ringContainsRing(ring, R(1, 2, 3, 3), true))
|
||||||
expect(t, ringContainsRing(ring, R(1, 2, 3, 3), false))
|
expect(t, ringContainsRing(ring, R(1, 2, 3, 3), false))
|
||||||
|
@ -968,7 +968,7 @@ func TestRingXContainsRing(t *testing.T) {
|
||||||
ring = newRing([]Point{
|
ring = newRing([]Point{
|
||||||
P(0, 0), P(2, 0), P(4, 0), P(4, 4), P(3, 4),
|
P(0, 0), P(2, 0), P(4, 0), P(4, 4), P(3, 4),
|
||||||
P(2, 5), P(1, 4), P(0, 4), P(0, 0),
|
P(2, 5), P(1, 4), P(0, 4), P(0, 0),
|
||||||
}, DefaultIndex)
|
}, DefaultIndexOptions)
|
||||||
expect(t, ringContainsRing(ring, R(1, 0, 3, 1), true))
|
expect(t, ringContainsRing(ring, R(1, 0, 3, 1), true))
|
||||||
expect(t, !ringContainsRing(ring, R(1, 0, 3, 1), false))
|
expect(t, !ringContainsRing(ring, R(1, 0, 3, 1), false))
|
||||||
})
|
})
|
||||||
|
@ -976,13 +976,13 @@ func TestRingXContainsRing(t *testing.T) {
|
||||||
ring = newRing([]Point{
|
ring = newRing([]Point{
|
||||||
P(0, 0), P(4, 0), P(4, 3), P(2, 4),
|
P(0, 0), P(4, 0), P(4, 3), P(2, 4),
|
||||||
P(0, 3), P(0, 0),
|
P(0, 3), P(0, 0),
|
||||||
}, DefaultIndex)
|
}, DefaultIndexOptions)
|
||||||
expect(t, ringContainsRing(ring, R(1, 1, 3, 2), true))
|
expect(t, ringContainsRing(ring, R(1, 1, 3, 2), true))
|
||||||
expect(t, ringContainsRing(ring, R(1, 1, 3, 2), false))
|
expect(t, ringContainsRing(ring, R(1, 1, 3, 2), false))
|
||||||
})
|
})
|
||||||
ring = newRing([]Point{
|
ring = newRing([]Point{
|
||||||
P(0, 0), P(4, 0), P(4, 3), P(3, 4), P(1, 4), P(0, 3), P(0, 0),
|
P(0, 0), P(4, 0), P(4, 3), P(3, 4), P(1, 4), P(0, 3), P(0, 0),
|
||||||
}, DefaultIndex)
|
}, DefaultIndexOptions)
|
||||||
t.Run("11", func(t *testing.T) {
|
t.Run("11", func(t *testing.T) {
|
||||||
expect(t, ringContainsRing(ring, R(1, 1, 3, 2), true))
|
expect(t, ringContainsRing(ring, R(1, 1, 3, 2), true))
|
||||||
expect(t, ringContainsRing(ring, R(1, 1, 3, 2), false))
|
expect(t, ringContainsRing(ring, R(1, 1, 3, 2), false))
|
||||||
|
@ -1024,10 +1024,10 @@ func TestRingXContainsRing(t *testing.T) {
|
||||||
t.Run("Identical", func(t *testing.T) {
|
t.Run("Identical", func(t *testing.T) {
|
||||||
shapes := []Ring{
|
shapes := []Ring{
|
||||||
R(0, 0, 10, 10),
|
R(0, 0, 10, 10),
|
||||||
newRing(octagon, DefaultIndex),
|
newRing(octagon, DefaultIndexOptions),
|
||||||
newRing(concave1, DefaultIndex), newRing(concave2, DefaultIndex),
|
newRing(concave1, DefaultIndexOptions), newRing(concave2, DefaultIndexOptions),
|
||||||
newRing(concave3, DefaultIndex), newRing(concave4, DefaultIndex),
|
newRing(concave3, DefaultIndexOptions), newRing(concave4, DefaultIndexOptions),
|
||||||
newRing(ri, DefaultIndex),
|
newRing(ri, DefaultIndexOptions),
|
||||||
}
|
}
|
||||||
for i, shape := range shapes {
|
for i, shape := range shapes {
|
||||||
t.Run(fmt.Sprintf("Shape%d", i), func(t *testing.T) {
|
t.Run(fmt.Sprintf("Shape%d", i), func(t *testing.T) {
|
||||||
|
@ -1038,7 +1038,7 @@ func TestRingXContainsRing(t *testing.T) {
|
||||||
|
|
||||||
t.Run("Big", func(t *testing.T) {
|
t.Run("Big", func(t *testing.T) {
|
||||||
// use rhode island
|
// use rhode island
|
||||||
ring := newRing(ri, DefaultIndex)
|
ring := newRing(ri, DefaultIndexOptions)
|
||||||
expect(t, ringContainsRing(ring.Rect(), ring, true))
|
expect(t, ringContainsRing(ring.Rect(), ring, true))
|
||||||
expect(t, ringContainsRing(ring, ring, true))
|
expect(t, ringContainsRing(ring, ring, true))
|
||||||
|
|
||||||
|
@ -1055,15 +1055,15 @@ func TestRingXIntersectsRing(t *testing.T) {
|
||||||
return t1
|
return t1
|
||||||
}
|
}
|
||||||
t.Run("Empty", func(t *testing.T) {
|
t.Run("Empty", func(t *testing.T) {
|
||||||
expect(t, !intersectsBothWays(newRing(nil, DefaultIndex), R(0, 0, 1, 1), true))
|
expect(t, !intersectsBothWays(newRing(nil, DefaultIndexOptions), R(0, 0, 1, 1), true))
|
||||||
expect(t, !intersectsBothWays(R(0, 0, 1, 1), newRing(nil, DefaultIndex), true))
|
expect(t, !intersectsBothWays(R(0, 0, 1, 1), newRing(nil, DefaultIndexOptions), true))
|
||||||
})
|
})
|
||||||
t.Run("Cases", func(t *testing.T) {
|
t.Run("Cases", func(t *testing.T) {
|
||||||
// concave
|
// concave
|
||||||
ring := newRing([]Point{
|
ring := newRing([]Point{
|
||||||
P(0, 0), P(4, 0), P(4, 4), P(3, 4),
|
P(0, 0), P(4, 0), P(4, 4), P(3, 4),
|
||||||
P(2, 3), P(1, 4), P(0, 4), P(0, 0),
|
P(2, 3), P(1, 4), P(0, 4), P(0, 0),
|
||||||
}, DefaultIndex)
|
}, DefaultIndexOptions)
|
||||||
t.Run("1", func(t *testing.T) {
|
t.Run("1", func(t *testing.T) {
|
||||||
expect(t, intersectsBothWays(ring, R(1, 1, 3, 2), true))
|
expect(t, intersectsBothWays(ring, R(1, 1, 3, 2), true))
|
||||||
expect(t, intersectsBothWays(ring, R(1, 1, 3, 2), false))
|
expect(t, intersectsBothWays(ring, R(1, 1, 3, 2), false))
|
||||||
|
@ -1089,7 +1089,7 @@ func TestRingXIntersectsRing(t *testing.T) {
|
||||||
P(0, 0), P(4, 0), P(4, 4),
|
P(0, 0), P(4, 0), P(4, 4),
|
||||||
P(3, 4), P(2, 5), P(1, 4),
|
P(3, 4), P(2, 5), P(1, 4),
|
||||||
P(0, 4), P(0, 0),
|
P(0, 4), P(0, 0),
|
||||||
}, DefaultIndex)
|
}, DefaultIndexOptions)
|
||||||
t.Run("6", func(t *testing.T) {
|
t.Run("6", func(t *testing.T) {
|
||||||
expect(t, intersectsBothWays(ring, R(1, 2, 3, 3), true))
|
expect(t, intersectsBothWays(ring, R(1, 2, 3, 3), true))
|
||||||
expect(t, intersectsBothWays(ring, R(1, 2, 3, 3), false))
|
expect(t, intersectsBothWays(ring, R(1, 2, 3, 3), false))
|
||||||
|
@ -1106,7 +1106,7 @@ func TestRingXIntersectsRing(t *testing.T) {
|
||||||
ring = newRing([]Point{
|
ring = newRing([]Point{
|
||||||
P(0, 0), P(2, 0), P(4, 0), P(4, 4), P(3, 4),
|
P(0, 0), P(2, 0), P(4, 0), P(4, 4), P(3, 4),
|
||||||
P(2, 5), P(1, 4), P(0, 4), P(0, 0),
|
P(2, 5), P(1, 4), P(0, 4), P(0, 0),
|
||||||
}, DefaultIndex)
|
}, DefaultIndexOptions)
|
||||||
expect(t, intersectsBothWays(ring, R(1, 0, 3, 1), true))
|
expect(t, intersectsBothWays(ring, R(1, 0, 3, 1), true))
|
||||||
expect(t, intersectsBothWays(ring, R(1, 0, 3, 1), false))
|
expect(t, intersectsBothWays(ring, R(1, 0, 3, 1), false))
|
||||||
})
|
})
|
||||||
|
@ -1114,13 +1114,13 @@ func TestRingXIntersectsRing(t *testing.T) {
|
||||||
ring = newRing([]Point{
|
ring = newRing([]Point{
|
||||||
P(0, 0), P(4, 0), P(4, 3), P(2, 4),
|
P(0, 0), P(4, 0), P(4, 3), P(2, 4),
|
||||||
P(0, 3), P(0, 0),
|
P(0, 3), P(0, 0),
|
||||||
}, DefaultIndex)
|
}, DefaultIndexOptions)
|
||||||
expect(t, intersectsBothWays(ring, R(1, 1, 3, 2), true))
|
expect(t, intersectsBothWays(ring, R(1, 1, 3, 2), true))
|
||||||
expect(t, intersectsBothWays(ring, R(1, 1, 3, 2), false))
|
expect(t, intersectsBothWays(ring, R(1, 1, 3, 2), false))
|
||||||
})
|
})
|
||||||
ring = newRing([]Point{
|
ring = newRing([]Point{
|
||||||
P(0, 0), P(4, 0), P(4, 3), P(3, 4), P(1, 4), P(0, 3), P(0, 0),
|
P(0, 0), P(4, 0), P(4, 3), P(3, 4), P(1, 4), P(0, 3), P(0, 0),
|
||||||
}, DefaultIndex)
|
}, DefaultIndexOptions)
|
||||||
t.Run("11", func(t *testing.T) {
|
t.Run("11", func(t *testing.T) {
|
||||||
expect(t, intersectsBothWays(ring, R(1, 1, 3, 2), true))
|
expect(t, intersectsBothWays(ring, R(1, 1, 3, 2), true))
|
||||||
expect(t, intersectsBothWays(ring, R(1, 1, 3, 2), false))
|
expect(t, intersectsBothWays(ring, R(1, 1, 3, 2), false))
|
||||||
|
@ -1167,12 +1167,12 @@ func TestRingXContainsLine(t *testing.T) {
|
||||||
P(0, 0), P(4, 0), P(4, 3),
|
P(0, 0), P(4, 0), P(4, 3),
|
||||||
P(3, 4), P(1, 4),
|
P(3, 4), P(1, 4),
|
||||||
P(0, 3), P(0, 0),
|
P(0, 3), P(0, 0),
|
||||||
}, DefaultIndex)
|
}, DefaultIndexOptions)
|
||||||
makeLine := func(start Point) *Line {
|
makeLine := func(start Point) *Line {
|
||||||
return NewLine([]Point{
|
return NewLine([]Point{
|
||||||
start, start.Move(0, 1), start.Move(1, 1),
|
start, start.Move(0, 1), start.Move(1, 1),
|
||||||
start.Move(1, 2), start.Move(2, 2),
|
start.Move(1, 2), start.Move(2, 2),
|
||||||
}, DefaultIndex)
|
}, DefaultIndexOptions)
|
||||||
}
|
}
|
||||||
t.Run("1", func(t *testing.T) {
|
t.Run("1", func(t *testing.T) {
|
||||||
expect(t, ringContainsLine(ring, makeLine(P(1, 1)), true))
|
expect(t, ringContainsLine(ring, makeLine(P(1, 1)), true))
|
||||||
|
@ -1285,12 +1285,12 @@ func TestRingXIntersectsLine(t *testing.T) {
|
||||||
P(0, 0), P(4, 0), P(4, 3),
|
P(0, 0), P(4, 0), P(4, 3),
|
||||||
P(3, 4), P(1, 4),
|
P(3, 4), P(1, 4),
|
||||||
P(0, 3), P(0, 0),
|
P(0, 3), P(0, 0),
|
||||||
}, DefaultIndex)
|
}, DefaultIndexOptions)
|
||||||
makeLine := func(start Point) *Line {
|
makeLine := func(start Point) *Line {
|
||||||
return NewLine([]Point{
|
return NewLine([]Point{
|
||||||
start, start.Move(0, 1), start.Move(1, 1),
|
start, start.Move(0, 1), start.Move(1, 1),
|
||||||
start.Move(1, 2), start.Move(2, 2),
|
start.Move(1, 2), start.Move(2, 2),
|
||||||
}, DefaultIndex)
|
}, DefaultIndexOptions)
|
||||||
}
|
}
|
||||||
t.Run("1", func(t *testing.T) {
|
t.Run("1", func(t *testing.T) {
|
||||||
expect(t, ringIntersectsLine(ring, makeLine(P(1, 1)), true))
|
expect(t, ringIntersectsLine(ring, makeLine(P(1, 1)), true))
|
||||||
|
|
|
@ -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
|
package geometry
|
||||||
|
|
||||||
import "github.com/tidwall/boxtree/d2"
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"reflect"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
// DefaultIndex are the minumum number of points required before it makes
|
// IndexKind is the kind of index to use in the options.
|
||||||
// sense to index the segments.
|
type IndexKind byte
|
||||||
// 64 seems to be the sweet spot
|
|
||||||
const DefaultIndex = 64
|
// IndexKind types
|
||||||
|
const (
|
||||||
|
None IndexKind = iota
|
||||||
|
RTree
|
||||||
|
RTreeCompressed
|
||||||
|
QuadTree
|
||||||
|
QuadTreeCompressed
|
||||||
|
)
|
||||||
|
|
||||||
|
func (kind IndexKind) String() string {
|
||||||
|
switch kind {
|
||||||
|
default:
|
||||||
|
return "Unknown"
|
||||||
|
case None:
|
||||||
|
return "None"
|
||||||
|
case RTree:
|
||||||
|
return "RTree"
|
||||||
|
case RTreeCompressed:
|
||||||
|
return "RTreeCompressed"
|
||||||
|
case QuadTree:
|
||||||
|
return "QuadTree"
|
||||||
|
case QuadTreeCompressed:
|
||||||
|
return "QuadTreeCompressed"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// IndexOptions are segment indexing options
|
||||||
|
type IndexOptions struct {
|
||||||
|
Kind IndexKind
|
||||||
|
MinPoints int
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultIndexOptions ...
|
||||||
|
var DefaultIndexOptions = &IndexOptions{
|
||||||
|
Kind: QuadTree,
|
||||||
|
MinPoints: 64,
|
||||||
|
}
|
||||||
|
|
||||||
// Series is just a series of points with utilities for efficiently accessing
|
// Series is just a series of points with utilities for efficiently accessing
|
||||||
// segments from rectangle queries, making stuff like point-in-polygon lookups
|
// segments from rectangle queries, making stuff like point-in-polygon lookups
|
||||||
|
@ -40,13 +81,19 @@ type baseSeries struct {
|
||||||
closed bool // points create a closed shape
|
closed bool // points create a closed shape
|
||||||
clockwise bool // points move clockwise
|
clockwise bool // points move clockwise
|
||||||
convex bool // points create a convex shape
|
convex bool // points create a convex shape
|
||||||
|
indexKind IndexKind // index kind
|
||||||
|
index interface{} // actual index
|
||||||
rect Rect // minumum bounding rectangle
|
rect Rect // minumum bounding rectangle
|
||||||
points []Point // original points
|
points []Point // original points
|
||||||
tree *d2.BoxTree // segment tree.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// makeSeries returns a processed baseSeries.
|
// makeSeries returns a processed baseSeries.
|
||||||
func makeSeries(points []Point, copyPoints, closed bool, index int) baseSeries {
|
func makeSeries(
|
||||||
|
points []Point, copyPoints, closed bool, opts *IndexOptions,
|
||||||
|
) baseSeries {
|
||||||
|
if opts == nil {
|
||||||
|
opts = DefaultIndexOptions
|
||||||
|
}
|
||||||
var series baseSeries
|
var series baseSeries
|
||||||
series.closed = closed
|
series.closed = closed
|
||||||
if copyPoints {
|
if copyPoints {
|
||||||
|
@ -55,20 +102,17 @@ func makeSeries(points []Point, copyPoints, closed bool, index int) baseSeries {
|
||||||
} else {
|
} else {
|
||||||
series.points = points
|
series.points = points
|
||||||
}
|
}
|
||||||
if index != 0 && len(points) >= int(index) {
|
series.convex, series.rect, series.clockwise = processPoints(points, closed)
|
||||||
series.tree = new(d2.BoxTree)
|
if opts.MinPoints != 0 && len(points) >= opts.MinPoints {
|
||||||
|
series.indexKind = opts.Kind
|
||||||
|
series.buildIndex()
|
||||||
}
|
}
|
||||||
series.convex, series.rect, series.clockwise =
|
|
||||||
processPoints(points, closed, series.tree)
|
|
||||||
return series
|
return series
|
||||||
}
|
}
|
||||||
|
|
||||||
// Index ...
|
// Index ...
|
||||||
func (series *baseSeries) Index() interface{} {
|
func (series *baseSeries) Index() interface{} {
|
||||||
if series.tree == nil {
|
return series.index
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return series.tree
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clockwise ...
|
// Clockwise ...
|
||||||
|
@ -82,9 +126,10 @@ func (series *baseSeries) Move(deltaX, deltaY float64) Series {
|
||||||
points[i].X = series.points[i].X + deltaX
|
points[i].X = series.points[i].X + deltaX
|
||||||
points[i].Y = series.points[i].Y + deltaY
|
points[i].Y = series.points[i].Y + deltaY
|
||||||
}
|
}
|
||||||
nseries := makeSeries(points, false, series.closed, 0)
|
nseries := makeSeries(points, false, series.closed, nil)
|
||||||
if series.tree != nil {
|
nseries.indexKind = series.indexKind
|
||||||
nseries.buildTree()
|
if series.Index() != nil {
|
||||||
|
nseries.buildIndex()
|
||||||
}
|
}
|
||||||
return &nseries
|
return &nseries
|
||||||
}
|
}
|
||||||
|
@ -123,8 +168,11 @@ func (series *baseSeries) PointAt(index int) Point {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Search finds a searches for segments that intersect the provided rectangle
|
// Search finds a searches for segments that intersect the provided rectangle
|
||||||
func (series *baseSeries) Search(rect Rect, iter func(seg Segment, idx int) bool) {
|
func (series *baseSeries) Search(
|
||||||
if series.tree == nil {
|
rect Rect, iter func(seg Segment, idx int) bool,
|
||||||
|
) {
|
||||||
|
switch v := series.index.(type) {
|
||||||
|
default:
|
||||||
n := series.NumSegments()
|
n := series.NumSegments()
|
||||||
for i := 0; i < n; i++ {
|
for i := 0; i < n; i++ {
|
||||||
seg := series.SegmentAt(i)
|
seg := series.SegmentAt(i)
|
||||||
|
@ -134,8 +182,8 @@ func (series *baseSeries) Search(rect Rect, iter func(seg Segment, idx int) bool
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
case *rTree:
|
||||||
series.tree.Search(
|
v.Search(
|
||||||
[]float64{rect.Min.X, rect.Min.Y},
|
[]float64{rect.Min.X, rect.Min.Y},
|
||||||
[]float64{rect.Max.X, rect.Max.Y},
|
[]float64{rect.Max.X, rect.Max.Y},
|
||||||
func(_, _ []float64, value interface{}) bool {
|
func(_, _ []float64, value interface{}) bool {
|
||||||
|
@ -153,6 +201,24 @@ func (series *baseSeries) Search(rect Rect, iter func(seg Segment, idx int) bool
|
||||||
return true
|
return true
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
case *qNode:
|
||||||
|
v.search(series, series.rect, rect, iter)
|
||||||
|
case *byte:
|
||||||
|
// convert the byte pointer back to a valid slice
|
||||||
|
data := *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{
|
||||||
|
Data: uintptr(unsafe.Pointer(v)),
|
||||||
|
Len: 0xFFFFFFFF,
|
||||||
|
Cap: 0xFFFFFFFF,
|
||||||
|
}))
|
||||||
|
n := binary.LittleEndian.Uint32(data[1:])
|
||||||
|
data = data[:n:n]
|
||||||
|
switch data[0] {
|
||||||
|
case 1:
|
||||||
|
rCompressSearch(data, 5, series, rect, iter)
|
||||||
|
case 2:
|
||||||
|
qCompressSearch(data, 5, series, series.rect, rect, iter)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -185,16 +251,9 @@ func (series *baseSeries) SegmentAt(index int) Segment {
|
||||||
return seg
|
return seg
|
||||||
}
|
}
|
||||||
|
|
||||||
func (series *baseSeries) buildTree() {
|
|
||||||
if series.tree == nil {
|
|
||||||
series.tree = new(d2.BoxTree)
|
|
||||||
processPoints(series.points, series.closed, series.tree)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// processPoints tests if the ring is convex, calculates the outer
|
// processPoints tests if the ring is convex, calculates the outer
|
||||||
// rectangle, and inserts segments into a boxtree in one pass.
|
// rectangle, and inserts segments into a boxtree in one pass.
|
||||||
func processPoints(points []Point, closed bool, tree *d2.BoxTree) (
|
func processPoints(points []Point, closed bool) (
|
||||||
convex bool, rect Rect, clockwise bool,
|
convex bool, rect Rect, clockwise bool,
|
||||||
) {
|
) {
|
||||||
if (closed && len(points) < 3) || len(points) < 2 {
|
if (closed && len(points) < 3) || len(points) < 2 {
|
||||||
|
@ -203,33 +262,9 @@ func processPoints(points []Point, closed bool, tree *d2.BoxTree) (
|
||||||
var concave bool
|
var concave bool
|
||||||
var dir int
|
var dir int
|
||||||
var a, b, c Point
|
var a, b, c Point
|
||||||
var segCount int
|
|
||||||
var cwc float64
|
var cwc float64
|
||||||
if closed {
|
|
||||||
segCount = len(points)
|
|
||||||
} else {
|
|
||||||
segCount = len(points) - 1
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < len(points); i++ {
|
for i := 0; i < len(points); i++ {
|
||||||
// process the segments for tree insertion
|
|
||||||
if tree != nil && i < segCount {
|
|
||||||
var seg Segment
|
|
||||||
seg.A = points[i]
|
|
||||||
if closed && i == len(points)-1 {
|
|
||||||
if seg.A == points[0] {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
seg.B = points[0]
|
|
||||||
} else {
|
|
||||||
seg.B = points[i+1]
|
|
||||||
}
|
|
||||||
rect := seg.Rect()
|
|
||||||
tree.Insert(
|
|
||||||
[]float64{rect.Min.X, rect.Min.Y},
|
|
||||||
[]float64{rect.Max.X, rect.Max.Y}, i)
|
|
||||||
}
|
|
||||||
|
|
||||||
// process the rectangle inflation
|
// process the rectangle inflation
|
||||||
if i == 0 {
|
if i == 0 {
|
||||||
rect = Rect{points[i], points[i]}
|
rect = Rect{points[i], points[i]}
|
||||||
|
@ -286,3 +321,54 @@ func processPoints(points []Point, closed bool, tree *d2.BoxTree) (
|
||||||
}
|
}
|
||||||
return !concave, rect, cwc > 0
|
return !concave, rect, cwc > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (series *baseSeries) clearIndex() {
|
||||||
|
series.index = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (series *baseSeries) setCompressed(data []byte) {
|
||||||
|
binary.LittleEndian.PutUint32(data[1:], uint32(len(data)))
|
||||||
|
smaller := make([]byte, len(data))
|
||||||
|
copy(smaller, data)
|
||||||
|
// use the byte point instead of a double reference to the byte slice
|
||||||
|
series.index = &smaller[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (series *baseSeries) buildIndex() {
|
||||||
|
if series.index != nil {
|
||||||
|
// already built
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch series.indexKind {
|
||||||
|
case RTree, RTreeCompressed:
|
||||||
|
tr := new(rTree)
|
||||||
|
n := series.NumSegments()
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
rect := series.SegmentAt(i).Rect()
|
||||||
|
tr.Insert(
|
||||||
|
[]float64{rect.Min.X, rect.Min.Y},
|
||||||
|
[]float64{rect.Max.X, rect.Max.Y}, i)
|
||||||
|
}
|
||||||
|
if series.indexKind == RTreeCompressed {
|
||||||
|
series.setCompressed(
|
||||||
|
tr.compress([]byte{1, 0, 0, 0, 0}),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
series.index = tr
|
||||||
|
}
|
||||||
|
case QuadTree, QuadTreeCompressed:
|
||||||
|
root := new(qNode)
|
||||||
|
n := series.NumSegments()
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
seg := series.SegmentAt(i)
|
||||||
|
root.insert(series, series.rect, seg.Rect(), i, 0)
|
||||||
|
}
|
||||||
|
if series.indexKind == QuadTreeCompressed {
|
||||||
|
series.setCompressed(
|
||||||
|
root.compress([]byte{2, 0, 0, 0, 0}, series.rect),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
series.index = root
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -30,16 +30,16 @@ func seriesForEachPoint(ring Ring, iter func(point Point) bool) {
|
||||||
func TestSeriesEmpty(t *testing.T) {
|
func TestSeriesEmpty(t *testing.T) {
|
||||||
var series *baseSeries
|
var series *baseSeries
|
||||||
expect(t, series.Empty())
|
expect(t, series.Empty())
|
||||||
series2 := makeSeries(nil, false, false, 0)
|
series2 := makeSeries(nil, false, false, &IndexOptions{Kind: None})
|
||||||
expect(t, series2.Empty())
|
expect(t, series2.Empty())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSeriesIndex(t *testing.T) {
|
func TestSeriesIndex(t *testing.T) {
|
||||||
series := makeSeries(nil, false, false, 0)
|
series := makeSeries(nil, false, false, &IndexOptions{Kind: None})
|
||||||
expect(t, series.Index() == nil)
|
expect(t, series.Index() == nil)
|
||||||
series = makeSeries([]Point{
|
series = makeSeries([]Point{
|
||||||
P(0, 0), P(10, 0), P(10, 10), P(0, 10), P(0, 0),
|
P(0, 0), P(10, 0), P(10, 10), P(0, 10), P(0, 0),
|
||||||
}, true, true, 1)
|
}, true, true, &IndexOptions{Kind: RTree, MinPoints: 1})
|
||||||
|
|
||||||
expect(t, series.Index() != nil)
|
expect(t, series.Index() != nil)
|
||||||
}
|
}
|
||||||
|
@ -48,27 +48,27 @@ func TestSeriesClockwise(t *testing.T) {
|
||||||
var series baseSeries
|
var series baseSeries
|
||||||
series = makeSeries([]Point{
|
series = makeSeries([]Point{
|
||||||
P(0, 0), P(10, 0), P(10, 10), P(0, 10), P(0, 0),
|
P(0, 0), P(10, 0), P(10, 10), P(0, 10), P(0, 0),
|
||||||
}, true, true, DefaultIndex)
|
}, true, true, DefaultIndexOptions)
|
||||||
expect(t, !series.Clockwise())
|
expect(t, !series.Clockwise())
|
||||||
series = makeSeries([]Point{
|
series = makeSeries([]Point{
|
||||||
P(0, 0), P(10, 0), P(10, 10), P(0, 10),
|
P(0, 0), P(10, 0), P(10, 10), P(0, 10),
|
||||||
}, true, true, DefaultIndex)
|
}, true, true, DefaultIndexOptions)
|
||||||
expect(t, !series.Clockwise())
|
expect(t, !series.Clockwise())
|
||||||
series = makeSeries([]Point{
|
series = makeSeries([]Point{
|
||||||
P(0, 0), P(10, 0), P(10, 10),
|
P(0, 0), P(10, 0), P(10, 10),
|
||||||
}, true, true, DefaultIndex)
|
}, true, true, DefaultIndexOptions)
|
||||||
expect(t, !series.Clockwise())
|
expect(t, !series.Clockwise())
|
||||||
series = makeSeries([]Point{
|
series = makeSeries([]Point{
|
||||||
P(0, 0), P(0, 10), P(10, 10), P(10, 0), P(0, 0),
|
P(0, 0), P(0, 10), P(10, 10), P(10, 0), P(0, 0),
|
||||||
}, true, true, DefaultIndex)
|
}, true, true, DefaultIndexOptions)
|
||||||
expect(t, series.Clockwise())
|
expect(t, series.Clockwise())
|
||||||
series = makeSeries([]Point{
|
series = makeSeries([]Point{
|
||||||
P(0, 0), P(0, 10), P(10, 10), P(10, 0),
|
P(0, 0), P(0, 10), P(10, 10), P(10, 0),
|
||||||
}, true, true, DefaultIndex)
|
}, true, true, DefaultIndexOptions)
|
||||||
expect(t, series.Clockwise())
|
expect(t, series.Clockwise())
|
||||||
series = makeSeries([]Point{
|
series = makeSeries([]Point{
|
||||||
P(0, 0), P(0, 10), P(10, 10),
|
P(0, 0), P(0, 10), P(10, 10),
|
||||||
}, true, true, DefaultIndex)
|
}, true, true, DefaultIndexOptions)
|
||||||
expect(t, series.Clockwise())
|
expect(t, series.Clockwise())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,19 +76,19 @@ func TestSeriesConvex(t *testing.T) {
|
||||||
t.Run("1", func(t *testing.T) {
|
t.Run("1", func(t *testing.T) {
|
||||||
series := makeSeries([]Point{
|
series := makeSeries([]Point{
|
||||||
P(0, 0), P(4, 0), P(4, 4), P(0, 4), P(0, 0),
|
P(0, 0), P(4, 0), P(4, 4), P(0, 4), P(0, 0),
|
||||||
}, true, true, DefaultIndex)
|
}, true, true, DefaultIndexOptions)
|
||||||
expect(t, series.Convex())
|
expect(t, series.Convex())
|
||||||
})
|
})
|
||||||
t.Run("2", func(t *testing.T) {
|
t.Run("2", func(t *testing.T) {
|
||||||
series := makeSeries([]Point{
|
series := makeSeries([]Point{
|
||||||
P(0, 0), P(4, 0), P(4, 4), P(3, 4), P(1, 4), P(0, 4), P(0, 0),
|
P(0, 0), P(4, 0), P(4, 4), P(3, 4), P(1, 4), P(0, 4), P(0, 0),
|
||||||
}, true, true, DefaultIndex)
|
}, true, true, DefaultIndexOptions)
|
||||||
expect(t, series.Convex())
|
expect(t, series.Convex())
|
||||||
})
|
})
|
||||||
t.Run("3", func(t *testing.T) {
|
t.Run("3", func(t *testing.T) {
|
||||||
series := makeSeries([]Point{
|
series := makeSeries([]Point{
|
||||||
P(0, 0), P(4, 0), P(4, 4), P(2, 5), P(0, 4), P(0, 0),
|
P(0, 0), P(4, 0), P(4, 4), P(2, 5), P(0, 4), P(0, 0),
|
||||||
}, true, true, DefaultIndex)
|
}, true, true, DefaultIndexOptions)
|
||||||
expect(t, series.Convex())
|
expect(t, series.Convex())
|
||||||
})
|
})
|
||||||
t.Run("4", func(t *testing.T) {
|
t.Run("4", func(t *testing.T) {
|
||||||
|
@ -96,7 +96,7 @@ func TestSeriesConvex(t *testing.T) {
|
||||||
P(0, 0), P(4, 0), P(4, 4),
|
P(0, 0), P(4, 0), P(4, 4),
|
||||||
P(3, 4), P(2, 5), P(1, 4),
|
P(3, 4), P(2, 5), P(1, 4),
|
||||||
P(0, 4), P(0, 0),
|
P(0, 4), P(0, 0),
|
||||||
}, true, true, DefaultIndex)
|
}, true, true, DefaultIndexOptions)
|
||||||
expect(t, !series.Convex())
|
expect(t, !series.Convex())
|
||||||
})
|
})
|
||||||
t.Run("5", func(t *testing.T) {
|
t.Run("5", func(t *testing.T) {
|
||||||
|
@ -104,28 +104,28 @@ func TestSeriesConvex(t *testing.T) {
|
||||||
P(0, 0), P(4, 0), P(4, 4),
|
P(0, 0), P(4, 0), P(4, 4),
|
||||||
P(3, 4), P(2, 3), P(1, 4),
|
P(3, 4), P(2, 3), P(1, 4),
|
||||||
P(0, 4), P(0, 0),
|
P(0, 4), P(0, 0),
|
||||||
}, true, true, DefaultIndex)
|
}, true, true, DefaultIndexOptions)
|
||||||
expect(t, !series.Convex())
|
expect(t, !series.Convex())
|
||||||
})
|
})
|
||||||
t.Run("5", func(t *testing.T) {
|
t.Run("5", func(t *testing.T) {
|
||||||
series := makeSeries([]Point{
|
series := makeSeries([]Point{
|
||||||
P(0, 0), P(4, 0), P(4, 4), P(0, 4),
|
P(0, 0), P(4, 0), P(4, 4), P(0, 4),
|
||||||
P(-1, 2), P(0, 0),
|
P(-1, 2), P(0, 0),
|
||||||
}, true, true, DefaultIndex)
|
}, true, true, DefaultIndexOptions)
|
||||||
expect(t, series.Convex())
|
expect(t, series.Convex())
|
||||||
})
|
})
|
||||||
t.Run("6", func(t *testing.T) {
|
t.Run("6", func(t *testing.T) {
|
||||||
series := makeSeries([]Point{
|
series := makeSeries([]Point{
|
||||||
P(0, 0), P(4, 0), P(4, 4), P(0, 4), P(0, 3),
|
P(0, 0), P(4, 0), P(4, 4), P(0, 4), P(0, 3),
|
||||||
P(-1, 2), P(0, 0),
|
P(-1, 2), P(0, 0),
|
||||||
}, true, true, DefaultIndex)
|
}, true, true, DefaultIndexOptions)
|
||||||
expect(t, !series.Convex())
|
expect(t, !series.Convex())
|
||||||
})
|
})
|
||||||
t.Run("6", func(t *testing.T) {
|
t.Run("6", func(t *testing.T) {
|
||||||
series := makeSeries([]Point{
|
series := makeSeries([]Point{
|
||||||
P(0, 0), P(4, 0), P(4, 4), P(0, 4),
|
P(0, 0), P(4, 0), P(4, 4), P(0, 4),
|
||||||
P(-1, 2), P(0, 1), P(0, 0),
|
P(-1, 2), P(0, 1), P(0, 0),
|
||||||
}, true, true, DefaultIndex)
|
}, true, true, DefaultIndexOptions)
|
||||||
expect(t, !series.Convex())
|
expect(t, !series.Convex())
|
||||||
})
|
})
|
||||||
t.Run("7", func(t *testing.T) {
|
t.Run("7", func(t *testing.T) {
|
||||||
|
@ -133,14 +133,14 @@ func TestSeriesConvex(t *testing.T) {
|
||||||
P(0, 0), P(4, 0), P(4, 4),
|
P(0, 0), P(4, 0), P(4, 4),
|
||||||
P(3, 3), P(2, 5), P(1, 3), P(0, 4),
|
P(3, 3), P(2, 5), P(1, 3), P(0, 4),
|
||||||
P(0, 0),
|
P(0, 0),
|
||||||
}, true, true, DefaultIndex)
|
}, true, true, DefaultIndexOptions)
|
||||||
expect(t, !series.Convex())
|
expect(t, !series.Convex())
|
||||||
})
|
})
|
||||||
t.Run("8", func(t *testing.T) {
|
t.Run("8", func(t *testing.T) {
|
||||||
series := makeSeries([]Point{
|
series := makeSeries([]Point{
|
||||||
P(0, 0), P(0, 4), P(1, 3), P(4, 4), P(2, 5), P(3, 3), P(4, 0),
|
P(0, 0), P(0, 4), P(1, 3), P(4, 4), P(2, 5), P(3, 3), P(4, 0),
|
||||||
P(0, 0),
|
P(0, 0),
|
||||||
}, true, true, DefaultIndex)
|
}, true, true, DefaultIndexOptions)
|
||||||
expect(t, !series.Convex())
|
expect(t, !series.Convex())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -151,9 +151,9 @@ func testScanSeries(
|
||||||
) []Segment {
|
) []Segment {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
if index {
|
if index {
|
||||||
series.buildTree()
|
series.buildIndex()
|
||||||
} else {
|
} else {
|
||||||
series.tree = nil
|
series.clearIndex()
|
||||||
}
|
}
|
||||||
var segs1 []Segment
|
var segs1 []Segment
|
||||||
seriesForEachSegment(series, func(seg Segment) bool {
|
seriesForEachSegment(series, func(seg Segment) bool {
|
||||||
|
@ -178,51 +178,51 @@ func testScanSeries(
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSeriesBasic(t *testing.T) {
|
func TestSeriesBasic(t *testing.T) {
|
||||||
series := makeSeries(octagon, true, true, DefaultIndex)
|
series := makeSeries(octagon, true, true, DefaultIndexOptions)
|
||||||
expect(t, reflect.DeepEqual(seriesCopyPoints(&series), octagon))
|
expect(t, reflect.DeepEqual(seriesCopyPoints(&series), octagon))
|
||||||
expect(t, series.Convex())
|
expect(t, series.Convex())
|
||||||
expect(t, series.Rect() == R(0, 0, 10, 10))
|
expect(t, series.Rect() == R(0, 0, 10, 10))
|
||||||
expect(t, series.Closed())
|
expect(t, series.Closed())
|
||||||
series = makeSeries(octagon, false, true, DefaultIndex)
|
series = makeSeries(octagon, false, true, DefaultIndexOptions)
|
||||||
expect(t, reflect.DeepEqual(seriesCopyPoints(&series), octagon))
|
expect(t, reflect.DeepEqual(seriesCopyPoints(&series), octagon))
|
||||||
|
|
||||||
series = makeSeries(ri, true, true, DefaultIndex)
|
series = makeSeries(ri, true, true, DefaultIndexOptions)
|
||||||
testScanSeries(t, &series, true, len(ri)-1, false)
|
testScanSeries(t, &series, true, len(ri)-1, false)
|
||||||
testScanSeries(t, &series, false, len(ri)-1, false)
|
testScanSeries(t, &series, false, len(ri)-1, false)
|
||||||
|
|
||||||
// small lines
|
// small lines
|
||||||
series = makeSeries([]Point{}, true, false, DefaultIndex)
|
series = makeSeries([]Point{}, true, false, DefaultIndexOptions)
|
||||||
testScanSeries(t, &series, true, 0, true)
|
testScanSeries(t, &series, true, 0, true)
|
||||||
testScanSeries(t, &series, false, 0, true)
|
testScanSeries(t, &series, false, 0, true)
|
||||||
|
|
||||||
series = makeSeries([]Point{P(5, 5)}, true, false, DefaultIndex)
|
series = makeSeries([]Point{P(5, 5)}, true, false, DefaultIndexOptions)
|
||||||
testScanSeries(t, &series, true, 0, true)
|
testScanSeries(t, &series, true, 0, true)
|
||||||
testScanSeries(t, &series, false, 0, true)
|
testScanSeries(t, &series, false, 0, true)
|
||||||
|
|
||||||
series = makeSeries([]Point{P(5, 5), P(10, 10)}, true, false, DefaultIndex)
|
series = makeSeries([]Point{P(5, 5), P(10, 10)}, true, false, DefaultIndexOptions)
|
||||||
testScanSeries(t, &series, true, 1, false)
|
testScanSeries(t, &series, true, 1, false)
|
||||||
testScanSeries(t, &series, false, 1, false)
|
testScanSeries(t, &series, false, 1, false)
|
||||||
|
|
||||||
// small rings
|
// small rings
|
||||||
series = makeSeries([]Point{}, true, true, DefaultIndex)
|
series = makeSeries([]Point{}, true, true, DefaultIndexOptions)
|
||||||
testScanSeries(t, &series, true, 0, true)
|
testScanSeries(t, &series, true, 0, true)
|
||||||
testScanSeries(t, &series, false, 0, true)
|
testScanSeries(t, &series, false, 0, true)
|
||||||
|
|
||||||
series = makeSeries([]Point{P(5, 5)}, true, true, DefaultIndex)
|
series = makeSeries([]Point{P(5, 5)}, true, true, DefaultIndexOptions)
|
||||||
testScanSeries(t, &series, true, 0, true)
|
testScanSeries(t, &series, true, 0, true)
|
||||||
testScanSeries(t, &series, false, 0, true)
|
testScanSeries(t, &series, false, 0, true)
|
||||||
|
|
||||||
series = makeSeries([]Point{P(5, 5), P(10, 10)}, true, true, DefaultIndex)
|
series = makeSeries([]Point{P(5, 5), P(10, 10)}, true, true, DefaultIndexOptions)
|
||||||
testScanSeries(t, &series, true, 0, true)
|
testScanSeries(t, &series, true, 0, true)
|
||||||
testScanSeries(t, &series, false, 0, true)
|
testScanSeries(t, &series, false, 0, true)
|
||||||
|
|
||||||
series = makeSeries([]Point{P(5, 5), P(10, 10), P(10, 5)}, true, true, DefaultIndex)
|
series = makeSeries([]Point{P(5, 5), P(10, 10), P(10, 5)}, true, true, DefaultIndexOptions)
|
||||||
testScanSeries(t, &series, true, 3, false)
|
testScanSeries(t, &series, true, 3, false)
|
||||||
testScanSeries(t, &series, false, 3, false)
|
testScanSeries(t, &series, false, 3, false)
|
||||||
|
|
||||||
}
|
}
|
||||||
func TestSeriesSearch(t *testing.T) {
|
func TestSeriesSearch(t *testing.T) {
|
||||||
series := makeSeries(octagon, true, true, DefaultIndex)
|
series := makeSeries(octagon, true, true, DefaultIndexOptions)
|
||||||
var segs []Segment
|
var segs []Segment
|
||||||
series.Search(R(0, 0, 0, 0), func(seg Segment, _ int) bool {
|
series.Search(R(0, 0, 0, 0), func(seg Segment, _ int) bool {
|
||||||
segs = append(segs, seg)
|
segs = append(segs, seg)
|
||||||
|
@ -284,7 +284,7 @@ func TestSeriesSearch(t *testing.T) {
|
||||||
func TestSeriesBig(t *testing.T) {
|
func TestSeriesBig(t *testing.T) {
|
||||||
t.Run("Closed", func(t *testing.T) {
|
t.Run("Closed", func(t *testing.T) {
|
||||||
// clip off the last point to force an auto closure
|
// clip off the last point to force an auto closure
|
||||||
series := makeSeries(ri[:len(ri)-1], true, true, DefaultIndex)
|
series := makeSeries(ri[:len(ri)-1], true, true, DefaultIndexOptions)
|
||||||
var seg2sA []Segment
|
var seg2sA []Segment
|
||||||
series.Search(series.Rect(), func(seg Segment, idx int) bool {
|
series.Search(series.Rect(), func(seg Segment, idx int) bool {
|
||||||
seg2sA = append(seg2sA, seg)
|
seg2sA = append(seg2sA, seg)
|
||||||
|
@ -299,7 +299,7 @@ func TestSeriesBig(t *testing.T) {
|
||||||
expect(t, checkSegsDups(seg2sA, seg2sB))
|
expect(t, checkSegsDups(seg2sA, seg2sB))
|
||||||
|
|
||||||
// use all points
|
// use all points
|
||||||
series2 := makeSeries(ri, true, true, DefaultIndex)
|
series2 := makeSeries(ri, true, true, DefaultIndexOptions)
|
||||||
var seg2sC []Segment
|
var seg2sC []Segment
|
||||||
seriesForEachSegment(&series2, func(seg Segment) bool {
|
seriesForEachSegment(&series2, func(seg Segment) bool {
|
||||||
seg2sC = append(seg2sC, seg)
|
seg2sC = append(seg2sC, seg)
|
||||||
|
@ -318,7 +318,7 @@ func TestSeriesBig(t *testing.T) {
|
||||||
expect(t, first == seg2sA[0])
|
expect(t, first == seg2sA[0])
|
||||||
})
|
})
|
||||||
t.Run("Opened", func(t *testing.T) {
|
t.Run("Opened", func(t *testing.T) {
|
||||||
series := makeSeries(az, true, false, DefaultIndex)
|
series := makeSeries(az, true, false, DefaultIndexOptions)
|
||||||
var seg2sA []Segment
|
var seg2sA []Segment
|
||||||
series.Search(series.Rect(), func(seg Segment, idx int) bool {
|
series.Search(series.Rect(), func(seg Segment, idx int) bool {
|
||||||
seg2sA = append(seg2sA, seg)
|
seg2sA = append(seg2sA, seg)
|
||||||
|
@ -339,7 +339,7 @@ func TestSeriesReverse(t *testing.T) {
|
||||||
for i := len(shape) - 1; i >= 0; i-- {
|
for i := len(shape) - 1; i >= 0; i-- {
|
||||||
rev = append(rev, shape[i])
|
rev = append(rev, shape[i])
|
||||||
}
|
}
|
||||||
series := makeSeries(rev, true, true, DefaultIndex)
|
series := makeSeries(rev, true, true, DefaultIndexOptions)
|
||||||
var seg2sA []Segment
|
var seg2sA []Segment
|
||||||
series.Search(series.Rect(), func(seg Segment, idx int) bool {
|
series.Search(series.Rect(), func(seg Segment, idx int) bool {
|
||||||
seg2sA = append(seg2sA, seg)
|
seg2sA = append(seg2sA, seg)
|
||||||
|
@ -375,14 +375,14 @@ func checkSegsDups(a, b []Segment) bool {
|
||||||
func TestSeriesMove(t *testing.T) {
|
func TestSeriesMove(t *testing.T) {
|
||||||
shapes := [][]Point{ri, octagon}
|
shapes := [][]Point{ri, octagon}
|
||||||
for _, shape := range shapes {
|
for _, shape := range shapes {
|
||||||
series := makeSeries(shape, true, true, DefaultIndex)
|
series := makeSeries(shape, true, true, DefaultIndexOptions)
|
||||||
series.tree = nil
|
series.clearIndex()
|
||||||
series2 := series.Move(60, 70)
|
series2 := series.Move(60, 70)
|
||||||
expect(t, series2.NumPoints() == len(shape))
|
expect(t, series2.NumPoints() == len(shape))
|
||||||
for i := 0; i < len(shape); i++ {
|
for i := 0; i < len(shape); i++ {
|
||||||
expect(t, series2.PointAt(i) == shape[i].Move(60, 70))
|
expect(t, series2.PointAt(i) == shape[i].Move(60, 70))
|
||||||
}
|
}
|
||||||
series.buildTree()
|
series.buildIndex()
|
||||||
series2 = series.Move(60, 70)
|
series2 = series.Move(60, 70)
|
||||||
expect(t, series2.NumPoints() == len(shape))
|
expect(t, series2.NumPoints() == len(shape))
|
||||||
for i := 0; i < len(shape); i++ {
|
for i := 0; i < len(shape); i++ {
|
||||||
|
|
|
@ -138,7 +138,8 @@ func parseJSONLineString(keys *parseKeys, opts *ParseOptions) (Object, error) {
|
||||||
// https://tools.ietf.org/html/rfc7946#section-3.1.4
|
// https://tools.ietf.org/html/rfc7946#section-3.1.4
|
||||||
return nil, errCoordinatesInvalid
|
return nil, errCoordinatesInvalid
|
||||||
}
|
}
|
||||||
line := geometry.NewLine(points, opts.IndexGeometry)
|
gopts := toGeometryOpts(opts)
|
||||||
|
line := geometry.NewLine(points, &gopts)
|
||||||
g.base = *line
|
g.base = *line
|
||||||
g.extra = ex
|
g.extra = ex
|
||||||
if err := parseBBoxAndExtras(&g.extra, keys, opts); err != nil {
|
if err := parseBBoxAndExtras(&g.extra, keys, opts); err != nil {
|
||||||
|
|
|
@ -69,7 +69,8 @@ func parseJSONMultiLineString(
|
||||||
err = errCoordinatesInvalid
|
err = errCoordinatesInvalid
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
line := geometry.NewLine(coords, opts.IndexGeometry)
|
gopts := toGeometryOpts(opts)
|
||||||
|
line := geometry.NewLine(coords, &gopts)
|
||||||
g.children = append(g.children, &LineString{base: *line, extra: ex})
|
g.children = append(g.children, &LineString{base: *line, extra: ex})
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
|
|
@ -79,7 +79,8 @@ func parseJSONMultiPolygon(
|
||||||
if len(coords) > 1 {
|
if len(coords) > 1 {
|
||||||
holes = coords[1:]
|
holes = coords[1:]
|
||||||
}
|
}
|
||||||
poly := geometry.NewPoly(exterior, holes, opts.IndexGeometry)
|
gopts := toGeometryOpts(opts)
|
||||||
|
poly := geometry.NewPoly(exterior, holes, &gopts)
|
||||||
g.children = append(g.children, &Polygon{base: *poly, extra: ex})
|
g.children = append(g.children, &Polygon{base: *poly, extra: ex})
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
|
|
@ -84,12 +84,16 @@ type ParseOptions struct {
|
||||||
// disable indexing.
|
// disable indexing.
|
||||||
// The default is 64.
|
// The default is 64.
|
||||||
IndexGeometry int
|
IndexGeometry int
|
||||||
|
// IndexGeometryKind is the kind of index implementation.
|
||||||
|
// Default is QuadTreeCompressed
|
||||||
|
IndexGeometryKind geometry.IndexKind
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultParseOptions ...
|
// DefaultParseOptions ...
|
||||||
var DefaultParseOptions = &ParseOptions{
|
var DefaultParseOptions = &ParseOptions{
|
||||||
IndexChildren: geometry.DefaultIndex,
|
IndexChildren: 64,
|
||||||
IndexGeometry: geometry.DefaultIndex,
|
IndexGeometry: 64,
|
||||||
|
IndexGeometryKind: geometry.QuadTreeCompressed,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse a GeoJSON object
|
// Parse a GeoJSON object
|
||||||
|
@ -124,6 +128,17 @@ func Parse(data string, opts *ParseOptions) (Object, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func toGeometryOpts(opts *ParseOptions) geometry.IndexOptions {
|
||||||
|
var gopts geometry.IndexOptions
|
||||||
|
if opts == nil {
|
||||||
|
gopts = *geometry.DefaultIndexOptions
|
||||||
|
} else {
|
||||||
|
gopts.Kind = opts.IndexGeometryKind
|
||||||
|
gopts.MinPoints = opts.IndexGeometry
|
||||||
|
}
|
||||||
|
return gopts
|
||||||
|
}
|
||||||
|
|
||||||
type parseKeys struct {
|
type parseKeys struct {
|
||||||
rCoordinates gjson.Result
|
rCoordinates gjson.Result
|
||||||
rGeometries gjson.Result
|
rGeometries gjson.Result
|
||||||
|
|
|
@ -36,11 +36,11 @@ func RO(minX, minY, maxX, maxY float64) *Rect {
|
||||||
}
|
}
|
||||||
|
|
||||||
func LO(points []geometry.Point) *LineString {
|
func LO(points []geometry.Point) *LineString {
|
||||||
return NewLineString(geometry.NewLine(points, geometry.DefaultIndex))
|
return NewLineString(geometry.NewLine(points, nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
func PPO(exterior []geometry.Point, holes [][]geometry.Point) *Polygon {
|
func PPO(exterior []geometry.Point, holes [][]geometry.Point) *Polygon {
|
||||||
return NewPolygon(geometry.NewPoly(exterior, holes, geometry.DefaultIndex))
|
return NewPolygon(geometry.NewPoly(exterior, holes, nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
func expectJSON(t testing.TB, data string, expect interface{}) Object {
|
func expectJSON(t testing.TB, data string, expect interface{}) Object {
|
||||||
|
|
|
@ -156,7 +156,8 @@ func parseJSONPolygon(keys *parseKeys, opts *ParseOptions) (Object, error) {
|
||||||
if len(coords) > 1 {
|
if len(coords) > 1 {
|
||||||
holes = coords[1:]
|
holes = coords[1:]
|
||||||
}
|
}
|
||||||
poly := geometry.NewPoly(exterior, holes, opts.IndexGeometry)
|
gopts := toGeometryOpts(opts)
|
||||||
|
poly := geometry.NewPoly(exterior, holes, &gopts)
|
||||||
g.base = *poly
|
g.base = *poly
|
||||||
g.extra = ex
|
g.extra = ex
|
||||||
if err := parseBBoxAndExtras(&g.extra, keys, opts); err != nil {
|
if err := parseBBoxAndExtras(&g.extra, keys, opts); err != nil {
|
||||||
|
|
Loading…
Reference in New Issue