mirror of https://github.com/tidwall/tile38.git
Better polygon detection logic
Refactored shared detection logic. Fixed linestring, multilinestring, and multipoint bugs. Added more geojson unit tests.
This commit is contained in:
parent
b1b7eda877
commit
4c2c1dd186
|
@ -0,0 +1,294 @@
|
||||||
|
package geojson
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/tidwall/tile38/geojson/poly"
|
||||||
|
)
|
||||||
|
|
||||||
|
// withinObjectShared returns true if g is within o
|
||||||
|
func withinObjectShared(g, o Object) bool {
|
||||||
|
bbp := o.bboxPtr()
|
||||||
|
if bbp != nil {
|
||||||
|
if !g.WithinBBox(*bbp) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if o.IsBBoxDefined() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch o := o.(type) {
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
case SimplePoint:
|
||||||
|
return g.WithinBBox(o.CalculatedBBox())
|
||||||
|
case Point:
|
||||||
|
return g.WithinBBox(o.CalculatedBBox())
|
||||||
|
case MultiPoint:
|
||||||
|
for i := range o.Coordinates {
|
||||||
|
if g.Within(o.getPoint(i)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
case LineString:
|
||||||
|
if len(o.Coordinates) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
switch g := g.(type) {
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
case SimplePoint:
|
||||||
|
return poly.Point(Position{X: g.X, Y: g.Y, Z: 0}).IntersectsLineString(polyPositions(o.Coordinates))
|
||||||
|
case Point:
|
||||||
|
return poly.Point(g.Coordinates).IntersectsLineString(polyPositions(o.Coordinates))
|
||||||
|
case MultiPoint:
|
||||||
|
if len(o.Coordinates) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, p := range o.Coordinates {
|
||||||
|
if !poly.Point(p).IntersectsLineString(polyPositions(o.Coordinates)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
case MultiLineString:
|
||||||
|
for i := range o.Coordinates {
|
||||||
|
if g.Within(o.getLineString(i)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
case MultiPolygon:
|
||||||
|
for i := range o.Coordinates {
|
||||||
|
if g.Within(o.getPolygon(i)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
case Feature:
|
||||||
|
return g.Within(o.Geometry)
|
||||||
|
case FeatureCollection:
|
||||||
|
for _, o := range o.Features {
|
||||||
|
if g.Within(o) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
case GeometryCollection:
|
||||||
|
for _, o := range o.Geometries {
|
||||||
|
if g.Within(o) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
case Polygon:
|
||||||
|
if len(o.Coordinates) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
exterior, holes := polyExteriorHoles(o.Coordinates)
|
||||||
|
switch g := g.(type) {
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
case SimplePoint:
|
||||||
|
return poly.Point(Position{X: g.X, Y: g.Y, Z: 0}).Inside(exterior, holes)
|
||||||
|
case Point:
|
||||||
|
return poly.Point(g.Coordinates).Inside(exterior, holes)
|
||||||
|
case MultiPoint:
|
||||||
|
if len(g.Coordinates) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i := range g.Coordinates {
|
||||||
|
if !g.getPoint(i).Within(o) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
case LineString:
|
||||||
|
return polyPositions(g.Coordinates).Inside(exterior, holes)
|
||||||
|
case MultiLineString:
|
||||||
|
if len(g.Coordinates) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i := range g.Coordinates {
|
||||||
|
if !g.getLineString(i).Within(o) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
case Polygon:
|
||||||
|
if len(g.Coordinates) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return polyPositions(g.Coordinates[0]).Inside(exterior, holes)
|
||||||
|
case MultiPolygon:
|
||||||
|
if len(g.Coordinates) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i := range g.Coordinates {
|
||||||
|
if !g.getPolygon(i).Within(o) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
case GeometryCollection:
|
||||||
|
if len(g.Geometries) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, g := range g.Geometries {
|
||||||
|
if !g.Within(o) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
case Feature:
|
||||||
|
return g.Geometry.Within(o)
|
||||||
|
case FeatureCollection:
|
||||||
|
if len(g.Features) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, g := range g.Features {
|
||||||
|
if !g.Within(o) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// intersectsObjectShared detects if g intersects with o
|
||||||
|
func intersectsObjectShared(g, o Object) bool {
|
||||||
|
bbp := o.bboxPtr()
|
||||||
|
if bbp != nil {
|
||||||
|
if !g.IntersectsBBox(*bbp) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if o.IsBBoxDefined() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch o := o.(type) {
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
case SimplePoint:
|
||||||
|
return g.IntersectsBBox(o.CalculatedBBox())
|
||||||
|
case Point:
|
||||||
|
return g.IntersectsBBox(o.CalculatedBBox())
|
||||||
|
case MultiPoint:
|
||||||
|
for i := range o.Coordinates {
|
||||||
|
if o.getPoint(i).Intersects(g) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
case LineString:
|
||||||
|
if g, ok := g.(LineString); ok {
|
||||||
|
a := polyPositions(g.Coordinates)
|
||||||
|
b := polyPositions(o.Coordinates)
|
||||||
|
return a.LineStringIntersectsLineString(b)
|
||||||
|
}
|
||||||
|
return o.Intersects(g)
|
||||||
|
case MultiLineString:
|
||||||
|
for i := range o.Coordinates {
|
||||||
|
if g.Intersects(o.getLineString(i)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
case MultiPolygon:
|
||||||
|
for i := range o.Coordinates {
|
||||||
|
if g.Intersects(o.getPolygon(i)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
case Feature:
|
||||||
|
return g.Intersects(o.Geometry)
|
||||||
|
case FeatureCollection:
|
||||||
|
for _, f := range o.Features {
|
||||||
|
if g.Intersects(f) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
case GeometryCollection:
|
||||||
|
for _, f := range o.Geometries {
|
||||||
|
if g.Intersects(f) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
case Polygon:
|
||||||
|
if len(o.Coordinates) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
exterior, holes := polyExteriorHoles(o.Coordinates)
|
||||||
|
switch g := g.(type) {
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
case SimplePoint:
|
||||||
|
return poly.Point(Position{X: g.X, Y: g.Y, Z: 0}).Intersects(exterior, holes)
|
||||||
|
case Point:
|
||||||
|
return poly.Point(g.Coordinates).Intersects(exterior, holes)
|
||||||
|
case MultiPoint:
|
||||||
|
for i := range g.Coordinates {
|
||||||
|
if g.getPoint(i).Intersects(o) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
case LineString:
|
||||||
|
return polyPositions(g.Coordinates).LineStringIntersects(exterior, holes)
|
||||||
|
case MultiLineString:
|
||||||
|
for i := range g.Coordinates {
|
||||||
|
if g.getLineString(i).Intersects(o) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
case Polygon:
|
||||||
|
if len(g.Coordinates) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return polyPositions(g.Coordinates[0]).Intersects(exterior, holes)
|
||||||
|
case MultiPolygon:
|
||||||
|
for i := range g.Coordinates {
|
||||||
|
if g.getPolygon(i).Intersects(o) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
case GeometryCollection:
|
||||||
|
for _, g := range g.Geometries {
|
||||||
|
if g.Intersects(o) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
case Feature:
|
||||||
|
return g.Geometry.Intersects(o)
|
||||||
|
case FeatureCollection:
|
||||||
|
for _, g := range g.Features {
|
||||||
|
if g.Intersects(o) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The object's calculated bounding box must intersect the radius of the circle to pass.
|
||||||
|
func nearbyObjectShared(g Object, x, y float64, meters float64) bool {
|
||||||
|
if !g.hasPositions() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
center := Position{X: x, Y: y, Z: 0}
|
||||||
|
bbox := g.CalculatedBBox()
|
||||||
|
if bbox.Min.X == bbox.Max.X && bbox.Min.Y == bbox.Max.Y {
|
||||||
|
// just a point, return is point is inside of the circle
|
||||||
|
return center.DistanceTo(bbox.Min) <= meters
|
||||||
|
}
|
||||||
|
circlePoly := CirclePolygon(x, y, meters, 12)
|
||||||
|
return g.Intersects(circlePoly)
|
||||||
|
}
|
|
@ -0,0 +1,362 @@
|
||||||
|
package geojson
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/tidwall/gjson"
|
||||||
|
)
|
||||||
|
|
||||||
|
// https://gist.github.com/tidwall/5524c468fa4212b89e9c3532a5b1f355
|
||||||
|
var detectJSON = `{"type":"FeatureCollection","features":[
|
||||||
|
{"type":"Feature","properties":{"id":"point1"},"geometry":{"type":"Point","coordinates":[-73.98549556732178,40.72198994979771]}},
|
||||||
|
{"type":"Feature","properties":{"id":"polygon1"},"geometry":{"type":"Polygon","coordinates":[[[-74.0035629272461,40.71994085251552],[-73.98914337158203,40.71994085251552],[-73.98914337158203,40.72755146730012],[-74.0035629272461,40.72755146730012],[-74.0035629272461,40.71994085251552]]]}},
|
||||||
|
{"type":"Feature","properties":{"id":"linestring1"},"geometry":{"type":"LineString","coordinates":[[-73.98382186889648,40.73652697126574],[-73.98821868896482,40.73652697126574],[-73.97420883178711,40.72943772441242]]}},
|
||||||
|
{"type":"Feature","properties":{"id":"linestring2"},"geometry":{"type":"LineString","coordinates":[[-73.98146152496338,40.72716120053256],[-73.99098873138428,40.724754504892424]]}},
|
||||||
|
{"type":"Feature","properties":{"id":"linestring3"},"geometry":{"type":"LineString","coordinates":[[-73.98386478424072,40.72696606629052],[-73.98090362548828,40.72501469240076],[-73.97837162017821,40.72621804639551]]}},
|
||||||
|
{"type":"Feature","properties":{"id":"polygon2","fill":"#0433ff"},"geometry":{"type":"Polygon","coordinates":[[[-73.98661136627197,40.72540497175607],[-73.99064540863037,40.71938791069558],[-73.98807048797607,40.71779411151556],[-73.97571086883545,40.72338850378556],[-73.98017406463623,40.72960033028089],[-73.98661136627197,40.72540497175607]]]}},
|
||||||
|
{"type":"Feature","properties":{"id":"polygon3"},"geometry":{"type":"Polygon","coordinates":[[[-73.98352146148682,40.72550254123727],[-73.98579597473145,40.72088409560772],[-73.97914409637451,40.72251034541217],[-73.98017406463623,40.72599038649773],[-73.98352146148682,40.72550254123727]],[[-73.98300647735596,40.72439674540761],[-73.98111820220947,40.72446179272971],[-73.98141860961913,40.7221525738643],[-73.98300647735596,40.72439674540761]]]}},
|
||||||
|
{"type":"Feature","properties":{"id":"multipoint1","marker-color":"#941751"},"geometry":{"type":"MultiPoint","coordinates":[[-73.98957252502441,40.72049378974239],[-73.9897871017456,40.720233584560724],[-73.9897871017456,40.721664700472566],[-73.99085998535155,40.720916620993194],[-73.9912462234497,40.720331161623065]]}},
|
||||||
|
{"type":"Feature","properties":{"id":"multilinestring1","stroke":"#941751"},"geometry":{"type":"MultiLineString","coordinates":[[[-73.98442268371582,40.72459188718318],[-73.98463726043701,40.72384384060296],[-73.98382186889648,40.72355112443509]],[[-73.9850664138794,40.72358364851732],[-73.98476600646973,40.72485207532725],[-73.9854097366333,40.72491712220435]]]}},
|
||||||
|
{"type":"Feature","properties":{"id":"multipolygon1","fill":"#941751"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-73.98021697998047,40.72429917430525],[-73.97892951965332,40.7250472157678],[-73.98000240325926,40.72524235563617],[-73.98021697998047,40.72429917430525]]],[[[-73.97901535034178,40.72452683998823],[-73.9788007736206,40.72345355209305],[-73.97764205932617,40.72410403167144],[-73.97901535034178,40.72452683998823]]]]}},
|
||||||
|
{"type":"Feature","properties":{"id":"point2"},"geometry":{"type":"Point","coordinates":[-73.98326396942139,40.723681220668624]}},
|
||||||
|
{"type":"Feature","properties":{"id":"point3"},"geometry":{"type":"Point","coordinates":[-73.98196396942139,40.723681220668624]}},
|
||||||
|
{"type":"Feature","properties":{"id":"point4"},"geometry":{"type":"Point","coordinates":[-73.9785396942139,40.7238220668624]}}
|
||||||
|
]}`
|
||||||
|
|
||||||
|
func getByID(id string) Object {
|
||||||
|
var r gjson.Result
|
||||||
|
gjson.Get(detectJSON, "features").ForEach(func(_, v gjson.Result) bool {
|
||||||
|
if v.Get("properties.id").String() == id {
|
||||||
|
r = v.Get("geometry")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
if !r.Exists() {
|
||||||
|
panic("not found '" + id + "'")
|
||||||
|
}
|
||||||
|
o, err := ObjectJSON(r.String())
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if p, ok := o.(SimplePoint); ok {
|
||||||
|
o = Point{Coordinates: Position{X: p.X, Y: p.Y}}
|
||||||
|
}
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
|
||||||
|
func toSimplePoint(p Object) Object {
|
||||||
|
return SimplePoint{X: p.(Point).Coordinates.X, Y: p.(Point).Coordinates.Y}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Basic geometry detections
|
||||||
|
// Point -> Point
|
||||||
|
// Point -> MultiPoint
|
||||||
|
// Point -> LineString
|
||||||
|
// Point -> MultiLineString
|
||||||
|
// Point -> Polygon
|
||||||
|
// Point -> MultiPolygon
|
||||||
|
// MultiPoint -> Point
|
||||||
|
// MultiPoint -> MultiPoint
|
||||||
|
// MultiPoint -> LineString
|
||||||
|
// MultiPoint -> MultiLineString
|
||||||
|
// MultiPoint -> Polygon
|
||||||
|
// MultiPoint -> MultiPolygon
|
||||||
|
// LineString -> Point
|
||||||
|
// LineString -> MultiPoint
|
||||||
|
// LineString -> LineString
|
||||||
|
// LineString -> MultiLineString
|
||||||
|
// LineString -> Polygon
|
||||||
|
// LineString -> MultiPolygon
|
||||||
|
// MultiLineString -> Point
|
||||||
|
// MultiLineString -> MultiPoint
|
||||||
|
// MultiLineString -> LineString
|
||||||
|
// MultiLineString -> MultiLineString
|
||||||
|
// MultiLineString -> Polygon
|
||||||
|
// MultiLineString -> MultiPolygon
|
||||||
|
// Polygon -> Point
|
||||||
|
// Polygon -> MultiPoint
|
||||||
|
// Polygon -> LineString
|
||||||
|
// Polygon -> MultiLineString
|
||||||
|
// Polygon -> Polygon
|
||||||
|
// Polygon -> MultiPolygon
|
||||||
|
// MultiPolygon -> Point
|
||||||
|
// MultiPolygon -> MultiPoint
|
||||||
|
// MultiPolygon -> LineString
|
||||||
|
// MultiPolygon -> MultiLineString
|
||||||
|
// MultiPolygon -> Polygon
|
||||||
|
// MultiPolygon -> MultiPolygon
|
||||||
|
|
||||||
|
func TestDetectSimplePointSimplePoint(t *testing.T) {
|
||||||
|
p1 := toSimplePoint(getByID("point1"))
|
||||||
|
p2 := toSimplePoint(getByID("point2"))
|
||||||
|
if p1.Intersects(p2) {
|
||||||
|
t.Fatal("expected false")
|
||||||
|
}
|
||||||
|
if !p1.Intersects(p1) {
|
||||||
|
t.Fatal("expected true")
|
||||||
|
}
|
||||||
|
if p1.Within(p2) {
|
||||||
|
t.Fatal("expected false")
|
||||||
|
}
|
||||||
|
if !p1.Within(p1) {
|
||||||
|
t.Fatal("expected true")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func TestDetectPointPoint(t *testing.T) {
|
||||||
|
p1 := getByID("point1")
|
||||||
|
p2 := getByID("point2")
|
||||||
|
if p1.Intersects(p2) {
|
||||||
|
t.Fatal("expected false")
|
||||||
|
}
|
||||||
|
if !p1.Intersects(p1) {
|
||||||
|
t.Fatal("expected true")
|
||||||
|
}
|
||||||
|
if p1.Within(p2) {
|
||||||
|
t.Fatal("expected false")
|
||||||
|
}
|
||||||
|
if !p1.Within(p1) {
|
||||||
|
t.Fatal("expected true")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func TestDetectPointMultPoint(t *testing.T) {
|
||||||
|
p1 := getByID("point1")
|
||||||
|
mp1 := getByID("multipoint1")
|
||||||
|
if p1.Intersects(mp1) {
|
||||||
|
t.Fatal("expected false")
|
||||||
|
}
|
||||||
|
if p1.Within(mp1) {
|
||||||
|
t.Fatal("expected false")
|
||||||
|
}
|
||||||
|
pp := Point{Coordinates: mp1.(MultiPoint).Coordinates[0]}
|
||||||
|
if !pp.Intersects(mp1) {
|
||||||
|
t.Fatal("expected true")
|
||||||
|
}
|
||||||
|
if !pp.Within(mp1) {
|
||||||
|
t.Fatal("expected true")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func TestDetectPointLineString(t *testing.T) {
|
||||||
|
p1 := getByID("point1")
|
||||||
|
ls1 := getByID("linestring1")
|
||||||
|
if p1.Intersects(ls1) {
|
||||||
|
t.Fatal("expected false")
|
||||||
|
}
|
||||||
|
if p1.Within(ls1) {
|
||||||
|
t.Fatal("expected false")
|
||||||
|
}
|
||||||
|
pp := Point{Coordinates: ls1.(LineString).Coordinates[0]}
|
||||||
|
if !pp.Intersects(ls1) {
|
||||||
|
t.Fatal("expected true")
|
||||||
|
}
|
||||||
|
if !pp.Within(ls1) {
|
||||||
|
t.Fatal("expected true")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func TestDetectPointMultiLineString(t *testing.T) {
|
||||||
|
p1 := getByID("point1")
|
||||||
|
mls1 := getByID("multilinestring1")
|
||||||
|
if p1.Intersects(mls1) {
|
||||||
|
t.Fatal("expected false")
|
||||||
|
}
|
||||||
|
if p1.Within(mls1) {
|
||||||
|
t.Fatal("expected false")
|
||||||
|
}
|
||||||
|
pp := Point{Coordinates: mls1.(MultiLineString).Coordinates[0][1]}
|
||||||
|
if !pp.Intersects(mls1) {
|
||||||
|
t.Fatal("expected true")
|
||||||
|
}
|
||||||
|
if !pp.Within(mls1) {
|
||||||
|
t.Fatal("expected true")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func TestDetectPointPolygon(t *testing.T) {
|
||||||
|
p1 := getByID("point1")
|
||||||
|
pl3 := getByID("polygon3")
|
||||||
|
if p1.Intersects(pl3) {
|
||||||
|
t.Fatal("expected false")
|
||||||
|
}
|
||||||
|
if p1.Within(pl3) {
|
||||||
|
t.Fatal("expected false")
|
||||||
|
}
|
||||||
|
p2 := getByID("point2")
|
||||||
|
if !p2.Intersects(pl3) {
|
||||||
|
t.Fatal("expected true")
|
||||||
|
}
|
||||||
|
if !p2.Within(pl3) {
|
||||||
|
t.Fatal("expected true")
|
||||||
|
}
|
||||||
|
p3 := getByID("point3")
|
||||||
|
if p3.Intersects(pl3) {
|
||||||
|
t.Fatal("expected false")
|
||||||
|
}
|
||||||
|
if p3.Within(pl3) {
|
||||||
|
t.Fatal("expected false")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDetectPointMultiPolygon(t *testing.T) {
|
||||||
|
p3 := getByID("point3")
|
||||||
|
p4 := getByID("point4")
|
||||||
|
mp1 := getByID("multipolygon1")
|
||||||
|
if p3.Intersects(mp1) {
|
||||||
|
t.Fatal("expected false")
|
||||||
|
}
|
||||||
|
if p3.Within(mp1) {
|
||||||
|
t.Fatal("expected false")
|
||||||
|
}
|
||||||
|
if !p4.Intersects(mp1) {
|
||||||
|
t.Fatal("expected true")
|
||||||
|
}
|
||||||
|
if !p4.Within(mp1) {
|
||||||
|
t.Fatal("expected true")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDetectMultiPointPoint(t *testing.T) {
|
||||||
|
p1 := getByID("point1")
|
||||||
|
mp1 := getByID("multipoint1")
|
||||||
|
if mp1.Intersects(p1) {
|
||||||
|
t.Fatal("expected false")
|
||||||
|
}
|
||||||
|
if mp1.Within(p1) {
|
||||||
|
t.Fatal("expected false")
|
||||||
|
}
|
||||||
|
pp := Point{Coordinates: mp1.(MultiPoint).Coordinates[0]}
|
||||||
|
if !mp1.Intersects(pp) {
|
||||||
|
t.Fatal("expected true")
|
||||||
|
}
|
||||||
|
if mp1.Within(pp) {
|
||||||
|
t.Fatal("expected false")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDetectMultiPointPolygon(t *testing.T) {
|
||||||
|
mp1 := getByID("multipoint1")
|
||||||
|
pl1 := getByID("polygon1")
|
||||||
|
pl2 := getByID("polygon2")
|
||||||
|
pl3 := getByID("polygon3")
|
||||||
|
if !mp1.Intersects(pl1) {
|
||||||
|
t.Fatal("expected true")
|
||||||
|
}
|
||||||
|
if !mp1.Within(pl1) {
|
||||||
|
t.Fatal("expected true")
|
||||||
|
}
|
||||||
|
if !mp1.Intersects(pl2) {
|
||||||
|
t.Fatal("expected true")
|
||||||
|
}
|
||||||
|
if mp1.Within(pl2) {
|
||||||
|
t.Fatal("expected false")
|
||||||
|
}
|
||||||
|
if mp1.Intersects(pl3) {
|
||||||
|
t.Fatal("expected false")
|
||||||
|
}
|
||||||
|
if mp1.Within(pl3) {
|
||||||
|
t.Fatal("expected false")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDetectLineStringLineString(t *testing.T) {
|
||||||
|
ls1 := getByID("linestring1")
|
||||||
|
ls2 := getByID("linestring2")
|
||||||
|
ls3 := getByID("linestring3")
|
||||||
|
if ls1.Intersects(ls2) {
|
||||||
|
t.Fatal("expected false")
|
||||||
|
}
|
||||||
|
if ls2.Intersects(ls1) {
|
||||||
|
t.Fatal("expected false")
|
||||||
|
}
|
||||||
|
if !ls2.Intersects(ls3) {
|
||||||
|
t.Fatal("expected true")
|
||||||
|
}
|
||||||
|
if !ls2.Intersects(ls3) {
|
||||||
|
t.Fatal("expected true")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func TestDetectLineStringPolygon(t *testing.T) {
|
||||||
|
ls1 := getByID("linestring1")
|
||||||
|
ls2 := getByID("linestring2")
|
||||||
|
ls3 := getByID("linestring3")
|
||||||
|
pl1 := getByID("polygon1")
|
||||||
|
pl2 := getByID("polygon2")
|
||||||
|
pl3 := getByID("polygon3")
|
||||||
|
|
||||||
|
if ls1.Intersects(pl1) {
|
||||||
|
t.Fatal("expected false")
|
||||||
|
}
|
||||||
|
if pl1.Intersects(ls1) {
|
||||||
|
t.Fatal("expected false")
|
||||||
|
}
|
||||||
|
if ls1.Intersects(pl3) {
|
||||||
|
t.Fatal("expected false")
|
||||||
|
}
|
||||||
|
if pl3.Intersects(ls1) {
|
||||||
|
t.Fatal("expected false")
|
||||||
|
}
|
||||||
|
if !ls2.Intersects(pl1) {
|
||||||
|
t.Fatal("expected true")
|
||||||
|
}
|
||||||
|
if !pl2.Intersects(ls2) {
|
||||||
|
t.Fatal("expected true")
|
||||||
|
}
|
||||||
|
if ls2.Within(pl1) {
|
||||||
|
t.Fatal("expected false")
|
||||||
|
}
|
||||||
|
if ls2.Intersects(pl3) {
|
||||||
|
t.Fatal("expected false")
|
||||||
|
}
|
||||||
|
if pl3.Intersects(ls2) {
|
||||||
|
t.Fatal("expected false")
|
||||||
|
}
|
||||||
|
if !ls3.Intersects(pl2) {
|
||||||
|
t.Fatal("expected true")
|
||||||
|
}
|
||||||
|
if !pl2.Intersects(ls3) {
|
||||||
|
t.Fatal("expected true")
|
||||||
|
}
|
||||||
|
if !ls3.Within(pl2) {
|
||||||
|
t.Fatal("expected true")
|
||||||
|
}
|
||||||
|
if ls2.Intersects(pl3) {
|
||||||
|
t.Fatal("expected false")
|
||||||
|
}
|
||||||
|
if pl3.Intersects(ls2) {
|
||||||
|
t.Fatal("expected false")
|
||||||
|
}
|
||||||
|
if ls2.Intersects(pl3) {
|
||||||
|
t.Fatal("expected false")
|
||||||
|
}
|
||||||
|
if pl3.Intersects(ls2) {
|
||||||
|
t.Fatal("expected false")
|
||||||
|
}
|
||||||
|
if !ls3.Intersects(pl3) {
|
||||||
|
t.Fatal("expected true")
|
||||||
|
}
|
||||||
|
if !pl3.Intersects(ls3) {
|
||||||
|
t.Fatal("expected true")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func TestDetectMultiLineStringPolygon(t *testing.T) {
|
||||||
|
mls1 := getByID("multilinestring1")
|
||||||
|
pl1 := getByID("polygon1")
|
||||||
|
pl2 := getByID("polygon2")
|
||||||
|
pl3 := getByID("polygon3")
|
||||||
|
if mls1.Intersects(pl1) {
|
||||||
|
t.Fatal("expected false")
|
||||||
|
}
|
||||||
|
if mls1.Within(pl1) {
|
||||||
|
t.Fatal("expected false")
|
||||||
|
}
|
||||||
|
if !mls1.Intersects(pl2) {
|
||||||
|
t.Fatal("expected true")
|
||||||
|
}
|
||||||
|
if !mls1.Within(pl2) {
|
||||||
|
t.Fatal("expected true")
|
||||||
|
}
|
||||||
|
if !mls1.Intersects(pl3) {
|
||||||
|
t.Fatal("expected true")
|
||||||
|
}
|
||||||
|
if mls1.Within(pl3) {
|
||||||
|
t.Fatal("expected false")
|
||||||
|
}
|
||||||
|
}
|
|
@ -192,20 +192,12 @@ func (g Feature) IntersectsBBox(bbox BBox) bool {
|
||||||
|
|
||||||
// Within detects if the object is fully contained inside another object.
|
// Within detects if the object is fully contained inside another object.
|
||||||
func (g Feature) Within(o Object) bool {
|
func (g Feature) Within(o Object) bool {
|
||||||
return withinObjectShared(g, o,
|
return withinObjectShared(g, o)
|
||||||
func(v Polygon) bool {
|
|
||||||
return g.Geometry.Within(o)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Intersects detects if the object intersects another object.
|
// Intersects detects if the object intersects another object.
|
||||||
func (g Feature) Intersects(o Object) bool {
|
func (g Feature) Intersects(o Object) bool {
|
||||||
return intersectsObjectShared(g, o,
|
return intersectsObjectShared(g, o)
|
||||||
func(v Polygon) bool {
|
|
||||||
return g.Geometry.Intersects(o)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Nearby detects if the object is nearby a position.
|
// Nearby detects if the object is nearby a position.
|
||||||
|
|
|
@ -178,36 +178,12 @@ func (g FeatureCollection) IntersectsBBox(bbox BBox) bool {
|
||||||
|
|
||||||
// Within detects if the object is fully contained inside another object.
|
// Within detects if the object is fully contained inside another object.
|
||||||
func (g FeatureCollection) Within(o Object) bool {
|
func (g FeatureCollection) Within(o Object) bool {
|
||||||
return withinObjectShared(g, o,
|
return withinObjectShared(g, o)
|
||||||
func(v Polygon) bool {
|
|
||||||
if len(g.Features) == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
for _, f := range g.Features {
|
|
||||||
if !f.Within(o) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Intersects detects if the object intersects another object.
|
// Intersects detects if the object intersects another object.
|
||||||
func (g FeatureCollection) Intersects(o Object) bool {
|
func (g FeatureCollection) Intersects(o Object) bool {
|
||||||
return intersectsObjectShared(g, o,
|
return intersectsObjectShared(g, o)
|
||||||
func(v Polygon) bool {
|
|
||||||
if len(g.Features) == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
for _, f := range g.Features {
|
|
||||||
if f.Intersects(o) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Nearby detects if the object is nearby a position.
|
// Nearby detects if the object is nearby a position.
|
||||||
|
|
|
@ -177,36 +177,12 @@ func (g GeometryCollection) IntersectsBBox(bbox BBox) bool {
|
||||||
|
|
||||||
// Within detects if the object is fully contained inside another object.
|
// Within detects if the object is fully contained inside another object.
|
||||||
func (g GeometryCollection) Within(o Object) bool {
|
func (g GeometryCollection) Within(o Object) bool {
|
||||||
return withinObjectShared(g, o,
|
return withinObjectShared(g, o)
|
||||||
func(v Polygon) bool {
|
|
||||||
if len(g.Geometries) == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
for _, g := range g.Geometries {
|
|
||||||
if !g.Within(o) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Intersects detects if the object intersects another object.
|
// Intersects detects if the object intersects another object.
|
||||||
func (g GeometryCollection) Intersects(o Object) bool {
|
func (g GeometryCollection) Intersects(o Object) bool {
|
||||||
return intersectsObjectShared(g, o,
|
return intersectsObjectShared(g, o)
|
||||||
func(v Polygon) bool {
|
|
||||||
if len(g.Geometries) == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
for _, g := range g.Geometries {
|
|
||||||
if g.Intersects(o) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Nearby detects if the object is nearby a position.
|
// Nearby detects if the object is nearby a position.
|
||||||
|
|
|
@ -98,20 +98,12 @@ func (g LineString) IntersectsBBox(bbox BBox) bool {
|
||||||
|
|
||||||
// Within detects if the object is fully contained inside another object.
|
// Within detects if the object is fully contained inside another object.
|
||||||
func (g LineString) Within(o Object) bool {
|
func (g LineString) Within(o Object) bool {
|
||||||
return withinObjectShared(g, o,
|
return withinObjectShared(g, o)
|
||||||
func(v Polygon) bool {
|
|
||||||
return polyPositions(g.Coordinates).Inside(polyExteriorHoles(v.Coordinates))
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Intersects detects if the object intersects another object.
|
// Intersects detects if the object intersects another object.
|
||||||
func (g LineString) Intersects(o Object) bool {
|
func (g LineString) Intersects(o Object) bool {
|
||||||
return intersectsObjectShared(g, o,
|
return intersectsObjectShared(g, o)
|
||||||
func(v Polygon) bool {
|
|
||||||
return polyPositions(g.Coordinates).LineStringIntersects(polyExteriorHoles(v.Coordinates))
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Nearby detects if the object is nearby a position.
|
// Nearby detects if the object is nearby a position.
|
||||||
|
|
|
@ -2,7 +2,6 @@ package geojson
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/tidwall/tile38/geojson/geohash"
|
"github.com/tidwall/tile38/geojson/geohash"
|
||||||
"github.com/tidwall/tile38/geojson/poly"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// MultiLineString is a geojson object with the type "MultiLineString"
|
// MultiLineString is a geojson object with the type "MultiLineString"
|
||||||
|
@ -10,32 +9,50 @@ type MultiLineString struct {
|
||||||
Coordinates [][]Position
|
Coordinates [][]Position
|
||||||
BBox *BBox
|
BBox *BBox
|
||||||
bboxDefined bool
|
bboxDefined bool
|
||||||
|
linestrings []LineString
|
||||||
}
|
}
|
||||||
|
|
||||||
func fillMultiLineString(coordinates [][]Position, bbox *BBox, err error) (MultiLineString, error) {
|
func fillMultiLineString(coordinates [][]Position, bbox *BBox, err error) (MultiLineString, error) {
|
||||||
|
linestrings := make([]LineString, len(coordinates))
|
||||||
if err == nil {
|
if err == nil {
|
||||||
for _, coordinates := range coordinates {
|
for i, ps := range coordinates {
|
||||||
if len(coordinates) < 2 {
|
linestrings[i], err = fillLineString(ps, nil, nil)
|
||||||
err = errLineStringInvalidCoordinates
|
if err != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bboxDefined := bbox != nil
|
bboxDefined := bbox != nil
|
||||||
if !bboxDefined {
|
if !bboxDefined {
|
||||||
cbbox := level3CalculatedBBox(coordinates, nil, false)
|
cbbox := mlCalculatedBBox(linestrings, nil)
|
||||||
bbox = &cbbox
|
bbox = &cbbox
|
||||||
}
|
}
|
||||||
return MultiLineString{
|
return MultiLineString{
|
||||||
Coordinates: coordinates,
|
Coordinates: coordinates,
|
||||||
BBox: bbox,
|
BBox: bbox,
|
||||||
bboxDefined: bboxDefined,
|
bboxDefined: bboxDefined,
|
||||||
|
linestrings: linestrings,
|
||||||
}, err
|
}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func mlCalculatedBBox(linestrings []LineString, bbox *BBox) BBox {
|
||||||
|
if bbox != nil {
|
||||||
|
return *bbox
|
||||||
|
}
|
||||||
|
var cbbox BBox
|
||||||
|
for i, g := range linestrings {
|
||||||
|
if i == 0 {
|
||||||
|
cbbox = g.CalculatedBBox()
|
||||||
|
} else {
|
||||||
|
cbbox = cbbox.union(g.CalculatedBBox())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cbbox
|
||||||
|
}
|
||||||
|
|
||||||
// CalculatedBBox is exterior bbox containing the object.
|
// CalculatedBBox is exterior bbox containing the object.
|
||||||
func (g MultiLineString) CalculatedBBox() BBox {
|
func (g MultiLineString) CalculatedBBox() BBox {
|
||||||
return level3CalculatedBBox(g.Coordinates, g.BBox, false)
|
return mlCalculatedBBox(g.linestrings, g.BBox)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CalculatedPoint is a point representation of the object.
|
// CalculatedPoint is a point representation of the object.
|
||||||
|
@ -93,6 +110,13 @@ func (g MultiLineString) hasPositions() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (g MultiLineString) getLineString(index int) LineString {
|
||||||
|
if index < len(g.linestrings) {
|
||||||
|
return g.linestrings[index]
|
||||||
|
}
|
||||||
|
return LineString{Coordinates: g.Coordinates[index]}
|
||||||
|
}
|
||||||
|
|
||||||
// WithinBBox detects if the object is fully contained inside a bbox.
|
// WithinBBox detects if the object is fully contained inside a bbox.
|
||||||
func (g MultiLineString) WithinBBox(bbox BBox) bool {
|
func (g MultiLineString) WithinBBox(bbox BBox) bool {
|
||||||
if g.bboxDefined {
|
if g.bboxDefined {
|
||||||
|
@ -101,15 +125,10 @@ func (g MultiLineString) WithinBBox(bbox BBox) bool {
|
||||||
if len(g.Coordinates) == 0 {
|
if len(g.Coordinates) == 0 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
for _, ls := range g.Coordinates {
|
for i := range g.Coordinates {
|
||||||
if len(ls) == 0 {
|
if !g.getLineString(i).WithinBBox(bbox) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
for _, p := range ls {
|
|
||||||
if !poly.Point(p).InsideRect(rectBBox(bbox)) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -119,8 +138,8 @@ func (g MultiLineString) IntersectsBBox(bbox BBox) bool {
|
||||||
if g.bboxDefined {
|
if g.bboxDefined {
|
||||||
return rectBBox(g.CalculatedBBox()).IntersectsRect(rectBBox(bbox))
|
return rectBBox(g.CalculatedBBox()).IntersectsRect(rectBBox(bbox))
|
||||||
}
|
}
|
||||||
for _, ls := range g.Coordinates {
|
for i := range g.Coordinates {
|
||||||
if polyPositions(ls).IntersectsRect(rectBBox(bbox)) {
|
if g.getLineString(i).IntersectsBBox(bbox) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -129,36 +148,12 @@ func (g MultiLineString) IntersectsBBox(bbox BBox) bool {
|
||||||
|
|
||||||
// Within detects if the object is fully contained inside another object.
|
// Within detects if the object is fully contained inside another object.
|
||||||
func (g MultiLineString) Within(o Object) bool {
|
func (g MultiLineString) Within(o Object) bool {
|
||||||
return withinObjectShared(g, o,
|
return withinObjectShared(g, o)
|
||||||
func(v Polygon) bool {
|
|
||||||
if len(g.Coordinates) == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
for _, ls := range g.Coordinates {
|
|
||||||
if !polyPositions(ls).Inside(polyExteriorHoles(v.Coordinates)) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Intersects detects if the object intersects another object.
|
// Intersects detects if the object intersects another object.
|
||||||
func (g MultiLineString) Intersects(o Object) bool {
|
func (g MultiLineString) Intersects(o Object) bool {
|
||||||
return intersectsObjectShared(g, o,
|
return intersectsObjectShared(g, o)
|
||||||
func(v Polygon) bool {
|
|
||||||
if len(g.Coordinates) == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
for _, ls := range g.Coordinates {
|
|
||||||
if polyPositions(ls).Intersects(polyExteriorHoles(v.Coordinates)) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Nearby detects if the object is nearby a position.
|
// Nearby detects if the object is nearby a position.
|
||||||
|
|
|
@ -25,6 +25,10 @@ func fillMultiPoint(coordinates []Position, bbox *BBox, err error) (MultiPoint,
|
||||||
}, err
|
}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (g MultiPoint) getPoint(index int) Point {
|
||||||
|
return Point{Coordinates: g.Coordinates[index]}
|
||||||
|
}
|
||||||
|
|
||||||
// CalculatedBBox is exterior bbox containing the object.
|
// CalculatedBBox is exterior bbox containing the object.
|
||||||
func (g MultiPoint) CalculatedBBox() BBox {
|
func (g MultiPoint) CalculatedBBox() BBox {
|
||||||
return level2CalculatedBBox(g.Coordinates, g.BBox)
|
return level2CalculatedBBox(g.Coordinates, g.BBox)
|
||||||
|
@ -108,36 +112,12 @@ func (g MultiPoint) IntersectsBBox(bbox BBox) bool {
|
||||||
|
|
||||||
// Within detects if the object is fully contained inside another object.
|
// Within detects if the object is fully contained inside another object.
|
||||||
func (g MultiPoint) Within(o Object) bool {
|
func (g MultiPoint) Within(o Object) bool {
|
||||||
return withinObjectShared(g, o,
|
return withinObjectShared(g, o)
|
||||||
func(v Polygon) bool {
|
|
||||||
if len(g.Coordinates) == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
for _, p := range g.Coordinates {
|
|
||||||
if !poly.Point(p).Inside(polyExteriorHoles(v.Coordinates)) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Intersects detects if the object intersects another object.
|
// Intersects detects if the object intersects another object.
|
||||||
func (g MultiPoint) Intersects(o Object) bool {
|
func (g MultiPoint) Intersects(o Object) bool {
|
||||||
return intersectsObjectShared(g, o,
|
return intersectsObjectShared(g, o)
|
||||||
func(v Polygon) bool {
|
|
||||||
if len(g.Coordinates) == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
for _, p := range g.Coordinates {
|
|
||||||
if poly.Point(p).Intersects(polyExteriorHoles(v.Coordinates)) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Nearby detects if the object is nearby a position.
|
// Nearby detects if the object is nearby a position.
|
||||||
|
|
|
@ -22,7 +22,7 @@ func fillMultiPolygon(coordinates [][][]Position, bbox *BBox, err error) (MultiP
|
||||||
}
|
}
|
||||||
bboxDefined := bbox != nil
|
bboxDefined := bbox != nil
|
||||||
if !bboxDefined {
|
if !bboxDefined {
|
||||||
cbbox := calculatedBBox(polygons, nil)
|
cbbox := mpCalculatedBBox(polygons, nil)
|
||||||
bbox = &cbbox
|
bbox = &cbbox
|
||||||
}
|
}
|
||||||
return MultiPolygon{
|
return MultiPolygon{
|
||||||
|
@ -33,16 +33,16 @@ func fillMultiPolygon(coordinates [][][]Position, bbox *BBox, err error) (MultiP
|
||||||
}, err
|
}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func calculatedBBox(polygons []Polygon, bbox *BBox) BBox {
|
func mpCalculatedBBox(polygons []Polygon, bbox *BBox) BBox {
|
||||||
if bbox != nil {
|
if bbox != nil {
|
||||||
return *bbox
|
return *bbox
|
||||||
}
|
}
|
||||||
var cbbox BBox
|
var cbbox BBox
|
||||||
for i, p := range polygons {
|
for i, g := range polygons {
|
||||||
if i == 0 {
|
if i == 0 {
|
||||||
cbbox = p.CalculatedBBox()
|
cbbox = g.CalculatedBBox()
|
||||||
} else {
|
} else {
|
||||||
cbbox = cbbox.union(p.CalculatedBBox())
|
cbbox = cbbox.union(g.CalculatedBBox())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return cbbox
|
return cbbox
|
||||||
|
@ -50,7 +50,7 @@ func calculatedBBox(polygons []Polygon, bbox *BBox) BBox {
|
||||||
|
|
||||||
// CalculatedBBox is exterior bbox containing the object.
|
// CalculatedBBox is exterior bbox containing the object.
|
||||||
func (g MultiPolygon) CalculatedBBox() BBox {
|
func (g MultiPolygon) CalculatedBBox() BBox {
|
||||||
return calculatedBBox(g.polygons, g.BBox)
|
return mpCalculatedBBox(g.polygons, g.BBox)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CalculatedPoint is a point representation of the object.
|
// CalculatedPoint is a point representation of the object.
|
||||||
|
@ -148,32 +148,12 @@ func (g MultiPolygon) IntersectsBBox(bbox BBox) bool {
|
||||||
|
|
||||||
// Within detects if the object is fully contained inside another object.
|
// Within detects if the object is fully contained inside another object.
|
||||||
func (g MultiPolygon) Within(o Object) bool {
|
func (g MultiPolygon) Within(o Object) bool {
|
||||||
return withinObjectShared(g, o,
|
return withinObjectShared(g, o)
|
||||||
func(v Polygon) bool {
|
|
||||||
if len(g.Coordinates) == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if !v.Within(o) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Intersects detects if the object intersects another object.
|
// Intersects detects if the object intersects another object.
|
||||||
func (g MultiPolygon) Intersects(o Object) bool {
|
func (g MultiPolygon) Intersects(o Object) bool {
|
||||||
return intersectsObjectShared(g, o,
|
return intersectsObjectShared(g, o)
|
||||||
func(v Polygon) bool {
|
|
||||||
if len(g.Coordinates) == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if v.Intersects(o) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Nearby detects if the object is nearby a position.
|
// Nearby detects if the object is nearby a position.
|
||||||
|
|
|
@ -182,114 +182,6 @@ func objectMap(json string, from int) (Object, error) {
|
||||||
return o, err
|
return o, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func withinObjectShared(g Object, o Object, pin func(v Polygon) bool) bool {
|
|
||||||
bbp := o.bboxPtr()
|
|
||||||
if bbp != nil {
|
|
||||||
if !g.WithinBBox(*bbp) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if o.IsBBoxDefined() {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
switch v := o.(type) {
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
case Point:
|
|
||||||
return g.WithinBBox(v.CalculatedBBox())
|
|
||||||
case SimplePoint:
|
|
||||||
return g.WithinBBox(v.CalculatedBBox())
|
|
||||||
case Polygon:
|
|
||||||
if len(v.Coordinates) == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return pin(v)
|
|
||||||
case MultiPolygon:
|
|
||||||
for i := range v.Coordinates {
|
|
||||||
if pin(v.getPolygon(i)) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
case Feature:
|
|
||||||
return g.Within(v.Geometry)
|
|
||||||
case FeatureCollection:
|
|
||||||
if len(v.Features) == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
for _, f := range v.Features {
|
|
||||||
if !g.Within(f) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
case GeometryCollection:
|
|
||||||
if len(v.Geometries) == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
for _, f := range v.Geometries {
|
|
||||||
if !g.Within(f) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func intersectsObjectShared(g Object, o Object, pin func(v Polygon) bool) bool {
|
|
||||||
bbp := o.bboxPtr()
|
|
||||||
if bbp != nil {
|
|
||||||
if !g.IntersectsBBox(*bbp) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if o.IsBBoxDefined() {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
switch v := o.(type) {
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
case Point:
|
|
||||||
return g.IntersectsBBox(v.CalculatedBBox())
|
|
||||||
case SimplePoint:
|
|
||||||
return g.IntersectsBBox(v.CalculatedBBox())
|
|
||||||
case Polygon:
|
|
||||||
if len(v.Coordinates) == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return pin(v)
|
|
||||||
case MultiPolygon:
|
|
||||||
for _, coords := range v.Coordinates {
|
|
||||||
if pin(Polygon{Coordinates: coords}) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
case Feature:
|
|
||||||
return g.Intersects(v.Geometry)
|
|
||||||
case FeatureCollection:
|
|
||||||
if len(v.Features) == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
for _, f := range v.Features {
|
|
||||||
if g.Intersects(f) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
case GeometryCollection:
|
|
||||||
if len(v.Geometries) == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
for _, f := range v.Geometries {
|
|
||||||
if g.Intersects(f) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// CirclePolygon returns a Polygon around the radius.
|
// CirclePolygon returns a Polygon around the radius.
|
||||||
func CirclePolygon(x, y, meters float64, steps int) Polygon {
|
func CirclePolygon(x, y, meters float64, steps int) Polygon {
|
||||||
if steps < 3 {
|
if steps < 3 {
|
||||||
|
@ -310,21 +202,6 @@ func CirclePolygon(x, y, meters float64, steps int) Polygon {
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
// The object's calculated bounding box must intersect the radius of the circle to pass.
|
|
||||||
func nearbyObjectShared(g Object, x, y float64, meters float64) bool {
|
|
||||||
if !g.hasPositions() {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
center := Position{X: x, Y: y, Z: 0}
|
|
||||||
bbox := g.CalculatedBBox()
|
|
||||||
if bbox.Min.X == bbox.Max.X && bbox.Min.Y == bbox.Max.Y {
|
|
||||||
// just a point, return is point is inside of the circle
|
|
||||||
return center.DistanceTo(bbox.Min) <= meters
|
|
||||||
}
|
|
||||||
circlePoly := CirclePolygon(x, y, meters, 12)
|
|
||||||
return g.Intersects(circlePoly)
|
|
||||||
}
|
|
||||||
|
|
||||||
func jsonMarshalString(s string) []byte {
|
func jsonMarshalString(s string) []byte {
|
||||||
for i := 0; i < len(s); i++ {
|
for i := 0; i < len(s); i++ {
|
||||||
if s[i] < ' ' || s[i] == '\\' || s[i] == '"' || s[i] > 126 {
|
if s[i] < ' ' || s[i] == '\\' || s[i] == '"' || s[i] > 126 {
|
||||||
|
|
|
@ -42,3 +42,72 @@ func TestCirclePolygon(t *testing.T) {
|
||||||
t.Fatal("should intersect")
|
t.Fatal("should intersect")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIssue281(t *testing.T) {
|
||||||
|
p := testJSON(t, `{"type":"Polygon","coordinates":[[
|
||||||
|
[-74.008283,40.718249],
|
||||||
|
[-74.007339,40.713305],
|
||||||
|
[-73.999013,40.714866],
|
||||||
|
[-74.001760,40.720851],
|
||||||
|
[-74.008283,40.718249]
|
||||||
|
]]}`)
|
||||||
|
|
||||||
|
// intersects polygon
|
||||||
|
ls1 := testJSON(t, `{"type":"LineString","coordinates":[
|
||||||
|
[-74.003648,40.717533],
|
||||||
|
[-73.99575233459473,40.72046126415031],
|
||||||
|
[-73.99721145629883,40.72338850378556]
|
||||||
|
]}`)
|
||||||
|
|
||||||
|
// outside polygon
|
||||||
|
ls2 := testJSON(t, `{"type":"LineString","coordinates":[
|
||||||
|
[-74.007682,40.722998],
|
||||||
|
[-74.001932,40.728462],
|
||||||
|
[-74.001846,40.723583]
|
||||||
|
]}`)
|
||||||
|
|
||||||
|
// inside polygon
|
||||||
|
ls3 := testJSON(t, `{"type":"LineString","coordinates":[
|
||||||
|
[-74.006910,40.717598],
|
||||||
|
[-74.006137,40.715387],
|
||||||
|
[-74.001331,40.715907]
|
||||||
|
]}`)
|
||||||
|
|
||||||
|
if !ls1.Intersects(p) {
|
||||||
|
t.Fatalf("expected true")
|
||||||
|
}
|
||||||
|
if !p.Intersects(ls1) {
|
||||||
|
t.Fatalf("expected true")
|
||||||
|
}
|
||||||
|
if ls1.Within(p) {
|
||||||
|
t.Fatalf("expected false")
|
||||||
|
}
|
||||||
|
if p.Within(ls1) {
|
||||||
|
t.Fatalf("expected false")
|
||||||
|
}
|
||||||
|
if ls2.Intersects(p) {
|
||||||
|
t.Fatalf("expected false")
|
||||||
|
}
|
||||||
|
if p.Intersects(ls2) {
|
||||||
|
t.Fatalf("expected false")
|
||||||
|
}
|
||||||
|
if ls2.Within(p) {
|
||||||
|
t.Fatalf("expected false")
|
||||||
|
}
|
||||||
|
if p.Within(ls2) {
|
||||||
|
t.Fatalf("expected false")
|
||||||
|
}
|
||||||
|
if !ls3.Intersects(p) {
|
||||||
|
t.Fatalf("expected true")
|
||||||
|
}
|
||||||
|
if !p.Intersects(ls3) {
|
||||||
|
t.Fatalf("expected true")
|
||||||
|
}
|
||||||
|
if !ls3.Within(p) {
|
||||||
|
t.Fatalf("expected true")
|
||||||
|
}
|
||||||
|
if p.Within(ls3) {
|
||||||
|
t.Fatalf("expected false")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -105,20 +105,12 @@ func (g Point) IntersectsBBox(bbox BBox) bool {
|
||||||
|
|
||||||
// Within detects if the object is fully contained inside another object.
|
// Within detects if the object is fully contained inside another object.
|
||||||
func (g Point) Within(o Object) bool {
|
func (g Point) Within(o Object) bool {
|
||||||
return withinObjectShared(g, o,
|
return withinObjectShared(g, o)
|
||||||
func(v Polygon) bool {
|
|
||||||
return poly.Point(g.Coordinates).Inside(polyExteriorHoles(v.Coordinates))
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Intersects detects if the object intersects another object.
|
// Intersects detects if the object intersects another object.
|
||||||
func (g Point) Intersects(o Object) bool {
|
func (g Point) Intersects(o Object) bool {
|
||||||
return intersectsObjectShared(g, o,
|
return intersectsObjectShared(g, o)
|
||||||
func(v Polygon) bool {
|
|
||||||
return poly.Point(g.Coordinates).Intersects(polyExteriorHoles(v.Coordinates))
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Nearby detects if the object is nearby a position.
|
// Nearby detects if the object is nearby a position.
|
||||||
|
|
|
@ -1,5 +1,15 @@
|
||||||
package poly
|
package poly
|
||||||
|
|
||||||
|
// IntersectsLineString detect if a point intersects a linestring
|
||||||
|
func (p Point) IntersectsLineString(exterior Polygon) bool {
|
||||||
|
for j := 0; j < len(exterior); j++ {
|
||||||
|
if raycast(p, exterior[j], exterior[(j+1)%len(exterior)]).on {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// Intersects detects if a point intersects another polygon
|
// Intersects detects if a point intersects another polygon
|
||||||
func (p Point) Intersects(exterior Polygon, holes []Polygon) bool {
|
func (p Point) Intersects(exterior Polygon, holes []Polygon) bool {
|
||||||
return p.Inside(exterior, holes)
|
return p.Inside(exterior, holes)
|
||||||
|
@ -7,7 +17,7 @@ func (p Point) Intersects(exterior Polygon, holes []Polygon) bool {
|
||||||
|
|
||||||
// Intersects detects if a rect intersects another polygon
|
// Intersects detects if a rect intersects another polygon
|
||||||
func (r Rect) Intersects(exterior Polygon, holes []Polygon) bool {
|
func (r Rect) Intersects(exterior Polygon, holes []Polygon) bool {
|
||||||
return r.Polygon().Inside(exterior, holes)
|
return r.Polygon().Intersects(exterior, holes)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Intersects detects if a polygon intersects another polygon
|
// Intersects detects if a polygon intersects another polygon
|
||||||
|
@ -15,7 +25,24 @@ func (shape Polygon) Intersects(exterior Polygon, holes []Polygon) bool {
|
||||||
return shape.doesIntersects(false, exterior, holes)
|
return shape.doesIntersects(false, exterior, holes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LineStringIntersectsLineString detects if a linestring intersects a linestring
|
||||||
|
// assume shape and exterior are actually linestrings
|
||||||
|
func (shape Polygon) LineStringIntersectsLineString(exterior Polygon) bool {
|
||||||
|
for i := 0; i < len(shape); i++ {
|
||||||
|
for j := 0; j < len(exterior); j++ {
|
||||||
|
if lineintersects(
|
||||||
|
shape[i], shape[(i+1)%len(shape)],
|
||||||
|
exterior[j], exterior[(j+1)%len(exterior)],
|
||||||
|
) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// LineStringIntersects detects if a polygon intersects a linestring
|
// LineStringIntersects detects if a polygon intersects a linestring
|
||||||
|
// assume shape is a linestring
|
||||||
func (shape Polygon) LineStringIntersects(exterior Polygon, holes []Polygon) bool {
|
func (shape Polygon) LineStringIntersects(exterior Polygon, holes []Polygon) bool {
|
||||||
return shape.doesIntersects(true, exterior, holes)
|
return shape.doesIntersects(true, exterior, holes)
|
||||||
}
|
}
|
||||||
|
|
|
@ -145,26 +145,12 @@ func (g Polygon) IntersectsBBox(bbox BBox) bool {
|
||||||
|
|
||||||
// Within detects if the object is fully contained inside another object.
|
// Within detects if the object is fully contained inside another object.
|
||||||
func (g Polygon) Within(o Object) bool {
|
func (g Polygon) Within(o Object) bool {
|
||||||
return withinObjectShared(g, o,
|
return withinObjectShared(g, o)
|
||||||
func(v Polygon) bool {
|
|
||||||
if len(g.Coordinates) == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return polyPositions(g.Coordinates[0]).Inside(polyExteriorHoles(v.Coordinates))
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Intersects detects if the object intersects another object.
|
// Intersects detects if the object intersects another object.
|
||||||
func (g Polygon) Intersects(o Object) bool {
|
func (g Polygon) Intersects(o Object) bool {
|
||||||
return intersectsObjectShared(g, o,
|
return intersectsObjectShared(g, o)
|
||||||
func(v Polygon) bool {
|
|
||||||
if len(g.Coordinates) == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return polyPositions(g.Coordinates[0]).Intersects(polyExteriorHoles(v.Coordinates))
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Nearby detects if the object is nearby a position.
|
// Nearby detects if the object is nearby a position.
|
||||||
|
|
|
@ -87,20 +87,12 @@ func (g SimplePoint) IntersectsBBox(bbox BBox) bool {
|
||||||
|
|
||||||
// Within detects if the object is fully contained inside another object.
|
// Within detects if the object is fully contained inside another object.
|
||||||
func (g SimplePoint) Within(o Object) bool {
|
func (g SimplePoint) Within(o Object) bool {
|
||||||
return withinObjectShared(g, o,
|
return withinObjectShared(g, o)
|
||||||
func(v Polygon) bool {
|
|
||||||
return poly.Point(Position{X: g.X, Y: g.Y, Z: 0}).Inside(polyExteriorHoles(v.Coordinates))
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Intersects detects if the object intersects another object.
|
// Intersects detects if the object intersects another object.
|
||||||
func (g SimplePoint) Intersects(o Object) bool {
|
func (g SimplePoint) Intersects(o Object) bool {
|
||||||
return intersectsObjectShared(g, o,
|
return intersectsObjectShared(g, o)
|
||||||
func(v Polygon) bool {
|
|
||||||
return poly.Point(Position{X: g.X, Y: g.Y, Z: 0}).Intersects(polyExteriorHoles(v.Coordinates))
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Nearby detects if the object is nearby a position.
|
// Nearby detects if the object is nearby a position.
|
||||||
|
|
Loading…
Reference in New Issue