diff --git a/contains.go b/contains.go new file mode 100644 index 00000000..8ada4efb --- /dev/null +++ b/contains.go @@ -0,0 +1,47 @@ +package geom + +// https://wrfranklin.org/Research/Short_Notes/pnpoly.html +func pnPoly(coords []Coord, pt Coord) bool { + var in bool + n := len(coords) + for i, j := 0, n-1; i < n; j, i = i, i+1 { + if (coords[i][1] > pt[1]) != (coords[j][1] > pt[1]) && (pt[0] < (coords[j][0]-coords[i][0])*(pt[1]-coords[i][1])/(coords[j][1]-coords[i][1])+coords[i][0]) { + in = !in + } + } + return in +} + +// ContainsPoint reports whether a geometry T contains the given point. +func ContainsPoint(geo T, point *Point) bool { + if geo.Bounds().OverlapsPoint(point.Layout(), point.Coords()) { + zero := NewPoint(point.Layout()).Coords() + switch g := geo.(type) { + case *Point: + return g == point + case *LinearRing: + pnPoly(g.Coords(), point.Coords()) + case *Polygon: + var coords []Coord + for r := 0; r < g.NumLinearRings(); r++ { + ring := g.LinearRing(r) + c := ring.Coords() + coords = append(append(append(coords, zero), c...), zero) + } + return pnPoly(coords, point.Coords()) + case *MultiPolygon: + for pl := 0; pl < g.NumPolygons(); pl++ { + if ContainsPoint(g.Polygon(pl), point) { + return true + } + } + case *GeometryCollection: + for i := 0; i < g.NumGeoms(); i++ { + if ContainsPoint(g.Geom(i), point) { + return true + } + } + } + } + return false +} diff --git a/contains_test.go b/contains_test.go new file mode 100644 index 00000000..42a2979b --- /dev/null +++ b/contains_test.go @@ -0,0 +1,60 @@ +package geom + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +var testGeo = []struct { + geom T + pts []*Point + in []bool +}{ + { + NewPolygon(XY).MustSetCoords([][]Coord{ + { + {24.950899, 60.169158}, + {24.953492, 60.169158}, + {24.953510, 60.170104}, + {24.950958, 60.169990}, + {24.950899, 60.169158}, + }, + }), + []*Point{ + NewPoint(XY).MustSetCoords(Coord{24.952242, 60.1696017}), + NewPoint(XY).MustSetCoords(Coord{24.976567, 60.1612500}), + }, + []bool{ + true, + false, + }, + }, + { + NewPolygon(XY).MustSetCoords([][]Coord{ + { + {40.7711, -73.9345}, + {40.7710, -73.9342}, + {40.7704, -73.9344}, + {40.7702, -73.9345}, + {40.7711, -73.9345}, + }, + }), + []*Point{ + NewPoint(XY).MustSetCoords(Coord{40.7705, -73.9394}), + NewPoint(XY).MustSetCoords(Coord{40.7707, -73.9344}), + }, + []bool{ + false, + true, + }, + }, +} + +func TestContain(t *testing.T) { + for i, test := range testGeo { + for j, pt := range test.pts { + assert.Equal(t, test.in[j], ContainsPoint(test.geom, pt), "not match in geom[%d] and pt[%d]", i, j) + } + } +} diff --git a/go.mod b/go.mod index 34b9a346..a4802023 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/alecthomas/assert/v2 v2.4.0 github.com/lib/pq v1.10.9 github.com/ory/dockertest/v3 v3.9.1 + github.com/stretchr/testify v1.8.1 github.com/twpayne/go-kml/v3 v3.1.0 ) @@ -17,6 +18,7 @@ require ( github.com/alecthomas/repr v0.3.0 // indirect github.com/cenkalti/backoff/v4 v4.1.3 // indirect github.com/containerd/continuity v0.3.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/docker/cli v20.10.17+incompatible // indirect github.com/docker/docker v24.0.7+incompatible // indirect github.com/docker/go-connections v0.4.0 // indirect @@ -31,8 +33,8 @@ require ( github.com/opencontainers/image-spec v1.0.2 // indirect github.com/opencontainers/runc v1.1.12 // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/sirupsen/logrus v1.9.0 // indirect - github.com/stretchr/testify v1.8.1 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xeipuuv/gojsonschema v1.2.0 // indirect