From b02fe35457d7475ac5a63817ddf2700548acf5e3 Mon Sep 17 00:00:00 2001 From: Rener Castro Date: Mon, 19 Feb 2024 20:34:09 -0300 Subject: [PATCH 1/2] [feature] add contains point function --- contains.go | 47 ++++++++++++++++++++++++++++++++++++ contains_test.go | 62 ++++++++++++++++++++++++++++++++++++++++++++++++ go.mod | 4 +++- 3 files changed, 112 insertions(+), 1 deletion(-) create mode 100644 contains.go create mode 100644 contains_test.go diff --git a/contains.go b/contains.go new file mode 100644 index 00000000..0a8378e5 --- /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..7bf4b808 --- /dev/null +++ b/contains_test.go @@ -0,0 +1,62 @@ +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 From dafdf7c7dad45e50316da071d526babad684fb36 Mon Sep 17 00:00:00 2001 From: Rener Castro Date: Mon, 4 Mar 2024 15:45:23 -0300 Subject: [PATCH 2/2] format gofumpt --- contains.go | 2 +- contains_test.go | 82 +++++++++++++++++++++++------------------------- 2 files changed, 41 insertions(+), 43 deletions(-) diff --git a/contains.go b/contains.go index 0a8378e5..8ada4efb 100644 --- a/contains.go +++ b/contains.go @@ -12,7 +12,7 @@ func pnPoly(coords []Coord, pt Coord) bool { return in } -// ContainsPoint reports whether a geometry T contains the given point +// 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() diff --git a/contains_test.go b/contains_test.go index 7bf4b808..42a2979b 100644 --- a/contains_test.go +++ b/contains_test.go @@ -6,52 +6,50 @@ import ( "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, +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}), }, - { - 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, + []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 {