diff --git a/geojson/poly/LICENSE b/geojson/poly/LICENSE new file mode 100644 index 00000000..f820c7bd --- /dev/null +++ b/geojson/poly/LICENSE @@ -0,0 +1,21 @@ +Copyright (c) 2016 Josh Baker + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +Contact GitHub API Training Shop Blog About + diff --git a/geojson/poly/README.md b/geojson/poly/README.md new file mode 100644 index 00000000..4a7b11e7 --- /dev/null +++ b/geojson/poly/README.md @@ -0,0 +1,14 @@ +Poly +==== +[![Build Status](https://travis-ci.org/tidwall/poly.svg?branch=master)](https://travis-ci.org/tidwall/poly) +[![GoDoc](https://godoc.org/github.com/tidwall/poly?status.svg)](https://godoc.org/github.com/tidwall/poly) + +Polygon detection methods for Go. + +Contact +------- +Josh Baker [@tidwall](http://twitter.com/tidwall) + +License +------- +Poly source code is available under the MIT [License](/LICENSE). diff --git a/geojson/poly/inside.go b/geojson/poly/inside.go index 271a81ef..3ba6ae33 100644 --- a/geojson/poly/inside.go +++ b/geojson/poly/inside.go @@ -42,10 +42,10 @@ func insideshpext(p Point, shape Polygon, exterior bool) bool { in := false for i := 0; i < len(shape); i++ { res := raycast(p, shape[i], shape[(i+1)%len(shape)]) - if res == on { + if res.on { return exterior } - if res == left { + if res.in { in = !in } } diff --git a/geojson/poly/inside_test.go b/geojson/poly/inside_test.go index 6f961e91..a79296f8 100644 --- a/geojson/poly/inside_test.go +++ b/geojson/poly/inside_test.go @@ -34,6 +34,14 @@ func TestRayInside(t *testing.T) { testRayInside(t, P(1, -0.1), strange, false) } +func TestRayInside2(t *testing.T) { + normal := []Point{P(0, 0), P(4, 3), P(5, 2), P(0, 0)} + testRayInside(t, P(1, 2), normal, false) + testRayInside(t, P(1, 3), normal, false) + testRayInside(t, P(4, 2), normal, true) + testRayInside(t, P(2, 1), normal, true) +} + var texterior = Polygon{ P(0, 0), P(0, 6), @@ -74,6 +82,10 @@ func TestRayExteriorHoles(t *testing.T) { {P(8, -3), false}, {P(8, 1), false}, {P(14, -1), false}, + + {P(8, -0.5), true}, + {P(8, -1.5), true}, + {P(8, -1), true}, } // add the edges, all should be inside for i := 0; i < len(texterior); i++ { diff --git a/geojson/poly/intersects.go b/geojson/poly/intersects.go index 64fd9a7a..96a5f71b 100644 --- a/geojson/poly/intersects.go +++ b/geojson/poly/intersects.go @@ -9,6 +9,8 @@ func (p Point) Intersects(exterior Polygon, holes []Polygon) bool { func (shape Polygon) Intersects(exterior Polygon, holes []Polygon) bool { return shape.doesIntersects(false, exterior, holes) } + +// LineStringIntersects detects if a polygon intersects a linestring func (shape Polygon) LineStringIntersects(exterior Polygon, holes []Polygon) bool { return shape.doesIntersects(true, exterior, holes) } diff --git a/geojson/poly/poly.go b/geojson/poly/poly.go index 377d5691..4ce00ced 100644 --- a/geojson/poly/poly.go +++ b/geojson/poly/poly.go @@ -1,8 +1,9 @@ +// Package poly provides polygon detection methods. package poly import "fmt" -// Point is simple 3D point +// Point is simple 2D point type Point struct { X, Y, Z float64 } diff --git a/geojson/poly/raycast.go b/geojson/poly/raycast.go index 1648561f..e841127e 100644 --- a/geojson/poly/raycast.go +++ b/geojson/poly/raycast.go @@ -1,121 +1,95 @@ package poly -// This implementation of the raycast algorithm test if a point is -// to the left of a line, or on the segment line. Otherwise it is -// assumed that the point is outside of the segment line. +import "math" -type rayres int - -func (r rayres) String() string { - switch r { - default: - return "unknown" - case out: - return "out" - case left: - return "left" - case on: - return "on" - } +type rayres struct { + in, on bool } -const ( - out = rayres(0) // outside of the segment. - left = rayres(1) // to the left of the segment - on = rayres(2) // on segment or vertex, special condition -) - func raycast(p, a, b Point) rayres { + // make sure that the point is inside the segment bounds + if a.Y < b.Y && (p.Y < a.Y || p.Y > b.Y) { + return rayres{false, false} + } else if a.Y > b.Y && (p.Y < b.Y || p.Y > a.Y) { + return rayres{false, false} + } + + // test if point is in on the segment if a.Y == b.Y { - // A and B share the same Y plane. if a.X == b.X { - // AB is just a point. - if p.X == a.X && p.Y == a.Y { - return on + if p == a { + return rayres{false, true} + } else { + return rayres{false, false} } - return out } - // AB is a horizontal line. - if p.Y != a.Y { - // P is not on same Y plane as A and B. - return out - } - // P is on same Y plane as A and B - if a.X < b.X { - if p.X >= a.X && p.X <= b.X { - return on + if p.Y == b.Y { + // horizontal segment + // check if the point in on the line + if a.X < b.X { + if p.X >= a.X && p.X <= b.X { + return rayres{false, true} + } + } else { + if p.X >= b.X && p.X <= a.X { + return rayres{false, true} + } } - if p.X < a.X { - return left + } + } + if a.X == b.X && p.X == b.X { + // vertical segment + // check if the point in on the line + if a.Y < b.Y { + if p.Y >= a.Y && p.Y <= b.Y { + return rayres{false, true} } } else { - if p.X >= b.X && p.X <= a.X { - return on - } - if p.X < b.X { - return left + if p.Y >= b.Y && p.Y <= a.Y { + return rayres{false, true} } } - return out + } + if (p.X-a.X)/(b.X-a.X) == (p.Y-a.Y)/(b.Y-a.Y) { + return rayres{false, true} } - if a.X == b.X { - // AB is a vertical line. - if a.Y > b.Y { - // A is above B - if p.Y > a.Y || p.Y < b.Y { - return out - } - } else { - // B is above A - if p.Y > b.Y || p.Y < a.Y { - return out - } - } - if p.X == a.X { - return on - } - if p.X < a.X { - return left - } - return out + // do the actual raycast here. + for p.Y == a.Y || p.Y == b.Y { + p.Y = math.Nextafter(p.Y, math.Inf(1)) } - - // AB is an angled line - if a.Y > b.Y { - // swap A and B so that A is below B. - a.X, a.Y, b.X, b.Y = b.X, b.Y, a.X, a.Y - } - if p.Y < a.Y || p.Y > b.Y { - return out - } - if a.X < b.X { - if p.X < a.X { - return left - } - if p.X > b.X { - return out + if a.Y < b.Y { + if p.Y < a.Y || p.Y > b.Y { + return rayres{false, false} } } else { - if p.X < b.X { - return left + if p.Y < b.Y || p.Y > a.Y { + return rayres{false, false} } + } + if a.X > b.X { if p.X > a.X { - return out + return rayres{false, false} + } + if p.X < b.X { + return rayres{true, false} + } + } else { + if p.X > b.X { + return rayres{false, false} + } + if p.X < a.X { + return rayres{true, false} } } - if (p.X == a.X && p.Y == a.Y) || (p.X == b.X && p.Y == b.Y) { - // P is on a vertex. - return on + if a.Y < b.Y { + if (p.Y-a.Y)/(p.X-a.X) >= (b.Y-a.Y)/(b.X-a.X) { + return rayres{true, false} + } + } else { + if (p.Y-b.Y)/(p.X-b.X) >= (a.Y-b.Y)/(a.X-b.X) { + return rayres{true, false} + } } - v1 := (p.Y - a.Y) / (p.X - a.X) - v2 := (b.Y - a.Y) / (b.X - a.X) - if v1-v2 == 0 { - // P is on a segment - return on - } - if v1 >= v2 { - return left - } - return out + return rayres{false, false} } diff --git a/geojson/poly/raycast_test.go b/geojson/poly/raycast_test.go deleted file mode 100644 index a98861c8..00000000 --- a/geojson/poly/raycast_test.go +++ /dev/null @@ -1,66 +0,0 @@ -package poly - -import "testing" - -func testRayRay(t *testing.T, p, a, b Point, expect rayres) { - res := raycast(p, a, b) - if res != expect { - t.Fatalf("1) %v,%v,%v = %s, expect %s", p, a, b, res, expect) - } - res = raycast(p, a, b) - if res != expect { - t.Fatalf("1) %v,%v,%v = %s, expect %s", p, b, a, res, expect) - } -} - -func TestRayHorizontal(t *testing.T) { - for x := float64(-1); x <= 4+1; x++ { - expect := on - if x < 0 { - expect = left - } else if x > 4 { - expect = out - } - testRayRay(t, P(x, 0), P(0, 0), P(4, 0), expect) - } - for x := float64(-1); x <= 4+1; x++ { - expect := out - testRayRay(t, P(x, -1), P(0, 0), P(4, 0), expect) - } - for x := float64(-1); x <= 4+1; x++ { - expect := out - testRayRay(t, P(x, +1), P(0, 0), P(4, 0), expect) - } -} - -func TestRayVertical(t *testing.T) { - for y := float64(-1); y <= 4+1; y++ { - expect := on - if y < 0 || y > 4 { - expect = out - } - testRayRay(t, P(0, y), P(0, 0), P(0, 4), expect) - } - for y := float64(-1); y <= 4+1; y++ { - expect := left - if y < 0 || y > 4 { - expect = out - } - testRayRay(t, P(-1, y), P(0, 0), P(0, 4), expect) - } - for y := float64(-1); y <= 4+1; y++ { - expect := out - testRayRay(t, P(+1, y), P(0, 0), P(0, 4), expect) - } -} - -func TestRayAngled(t *testing.T) { - testRayRay(t, P(1, 3), P(0, 4), P(4, 0), on) - testRayRay(t, P(0, 4), P(0, 4), P(4, 0), on) - testRayRay(t, P(4, 0), P(0, 4), P(4, 0), on) - testRayRay(t, P(1, 4), P(0, 4), P(4, 0), out) - testRayRay(t, P(5, 0), P(0, 4), P(4, 0), out) - testRayRay(t, P(-1, 4), P(0, 4), P(4, 0), left) - testRayRay(t, P(3, 0), P(0, 4), P(4, 0), left) - testRayRay(t, P(2, 2), P(0, 4), P(4, 0), on) -}