Skip to content

Commit

Permalink
Support polygons in near, within and contains. Fix a bug in intersects
Browse files Browse the repository at this point in the history
  • Loading branch information
ashwin95r committed Dec 26, 2016
1 parent e03a584 commit 67c5430
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 23 deletions.
73 changes: 55 additions & 18 deletions types/geofilter.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,12 +150,9 @@ func queryTokens(qt QueryType, data string, maxDistance float64) ([]string, *Geo
return toks, &GeoQueryData{pt: pt, loop: l, qtype: qt}, nil

case QueryTypeContains:
if l != nil {
return nil, nil, x.Errorf("Cannot use a polygon in a contains query")
}
// For a contains query, we only need to look at the objects whose cover matches our
// parents. So we take our parents and prefix with the coverPrefix to look in the index.
return createTokens(parents, coverPrefix), &GeoQueryData{pt: pt, qtype: qt}, nil
return createTokens(parents, coverPrefix), &GeoQueryData{pt: pt, loop: l, qtype: qt}, nil

case QueryTypeNear:
if l != nil {
Expand Down Expand Up @@ -206,33 +203,68 @@ func (q GeoQueryData) MatchesFilter(g geom.T) bool {
return false
}

// WithinPolygon returns true if g1 is within g2 approximaltely.
// Note that this is very far from accurate within function and is
// a temporary fix.
// TODO(Ashwin): Improve this to make it more accurate.
func WithinPolygon(g1 *s2.Loop, g2 *s2.Loop) bool {
for _, point := range g1.Vertices() {
if !g2.ContainsPoint(point) {
return false
}
}
return true
}

// TODO(Ashwin): Improve this to make it more accurate.
func WithinCapPolygon(g1 *s2.Loop, g2 *s2.Cap) bool {
for _, point := range g1.Vertices() {
if !g2.ContainsPoint(point) {
return false
}
}
return true
}

// returns true if the geometry represented by g is within the given loop or cap
func (q GeoQueryData) isWithin(g geom.T) bool {
x.AssertTruef(q.pt != nil || q.loop != nil || q.cap != nil, "At least a point, loop or cap should be defined.")
gpt, ok := g.(*geom.Point)
if !ok {
gpoly, ok := g.(*geom.Polygon)
if ok {
// We will only consider points for within queries.
return false
if !ok {
return false
}
s2loop, err := loopFromPolygon(gpoly)
if err != nil {
return false
}
if q.loop != nil {
return WithinPolygon(s2loop, q.loop)
}
if q.cap != nil {
return WithinCapPolygon(s2loop, q.cap)
}
}

s2pt := pointFromPoint(gpt)
if q.pt != nil {
return q.pt.ApproxEqual(s2pt)
}
gpt, ok := g.(*geom.Point)
if ok {
s2pt := pointFromPoint(gpt)
if q.pt != nil {
return q.pt.ApproxEqual(s2pt)
}

if q.loop != nil {
return q.loop.ContainsPoint(s2pt)
if q.loop != nil {
return q.loop.ContainsPoint(s2pt)
}
return q.cap.ContainsPoint(s2pt)
}
return q.cap.ContainsPoint(s2pt)
return false
}

// returns true if the geometry represented by uid/attr contains the given point
func (q GeoQueryData) contains(g geom.T) bool {
x.AssertTruef(q.pt != nil || q.loop != nil, "At least a point or loop should be defined.")
if q.loop != nil {
// We don't support polygons containing polygons yet.
return false
}

poly, ok := g.(*geom.Polygon)
if !ok {
Expand All @@ -244,6 +276,11 @@ func (q GeoQueryData) contains(g geom.T) bool {
if err != nil {
return false
}
// If its a loop check if it lies within other loop. Else Check the point.
if q.loop != nil {
// We don't support polygons containing polygons yet.
return WithinPolygon(q.loop, s2loop)
}
return s2loop.ContainsPoint(*q.pt)
}

Expand Down
17 changes: 14 additions & 3 deletions types/geofilter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ func TestQueryTokensPolygon(t *testing.T) {
if qt == QueryTypeWithin {
require.Len(t, toks, 18)
} else {
require.Len(t, toks, 65)
require.Len(t, toks, 66)
}
require.NotNil(t, qd)
require.Equal(t, qd.qtype, qt)
Expand All @@ -91,7 +91,7 @@ func TestQueryTokensPolygon(t *testing.T) {

func TestQueryTokensPolygonError(t *testing.T) {
data := formData(t, "testdata/zip.json")
qtypes := []QueryType{QueryTypeNear, QueryTypeContains}
qtypes := []QueryType{QueryTypeNear}
for _, qt := range qtypes {
_, _, err := queryTokens(qt, data, 0.0)
require.Error(t, err)
Expand Down Expand Up @@ -257,8 +257,19 @@ func TestMatchesFilterIntersectsPolygon(t *testing.T) {
{{-120, 35}, {-121, 35}, {-121, 36}, {-120, 36}, {-120, 35}},
})
require.False(t, qd.MatchesFilter(poly))
}

// These two polygons don't intersect.
polyOut := geom.NewPolygon(geom.XY).MustSetCoords([][]geom.Coord{
{{-122.4989104270935, 37.736953437345356}, {-122.50504732131958, 37.729096212099975}, {-122.49515533447264, 37.732049133202324}, {-122.4989104270935, 37.736953437345356}},
})

poly2 := geom.NewPolygon(geom.XY).MustSetCoords([][]geom.Coord{
{{-122.5033039, 37.7334601}, {-122.503128, 37.7335189}, {-122.5031222, 37.7335205}, {-122.5030813, 37.7335868}, {-122.5031511, 37.73359}, {-122.5031933, 37.7335916}, {-122.5032228, 37.7336022}, {-122.5032697, 37.7335937}, {-122.5033194, 37.7335874}, {-122.5033723, 37.7335518}, {-122.503369, 37.7335068}, {-122.5033462, 37.7334474}, {-122.5033039, 37.7334601}},
})
data = formDataPolygon(t, polyOut)
_, qd, err = queryTokens(QueryTypeIntersects, data, 0.0)
require.False(t, qd.MatchesFilter(poly2))
}
func TestMatchesFilterNearPoint(t *testing.T) {
p := geom.NewPoint(geom.XY).MustSetCoords(geom.Coord{-122.082506, 37.4249518})
data := formDataPoint(t, p)
Expand Down
2 changes: 1 addition & 1 deletion types/s2.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ func (l loopRegion) edgesCross(c s2.Cell) bool {
func (l loopRegion) edgesCrossPoints(pts []s2.Point) bool {
n := len(pts)
for i := 0; i < n; i++ {
crosser := s2.NewChainEdgeCrosser(pts[i], pts[(i+1)%n], pts[0])
crosser := s2.NewChainEdgeCrosser(pts[i], pts[(i+1)%n], l.Vertex(0))
for i := 1; i <= l.NumEdges(); i++ { // add vertex 0 twice as it is a closed loop
if crosser.EdgeOrVertexChainCrossing(l.Vertex(i)) {
return true
Expand Down
2 changes: 1 addition & 1 deletion types/s2index_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ func TestKeyGeneratorPolygon(t *testing.T) {

keys, err := IndexGeoTokens(g)
require.NoError(t, err)
require.Len(t, keys, 65)
require.Len(t, keys, 66)
}

func testCover(file string, max int) {
Expand Down

0 comments on commit 67c5430

Please sign in to comment.