diff --git a/geojson/geojson_s2_util.go b/geojson/geojson_s2_util.go index 5c580643..91ad975e 100644 --- a/geojson/geojson_s2_util.go +++ b/geojson/geojson_s2_util.go @@ -25,19 +25,6 @@ import ( // ------------------------------------------------------------------------ -// creates a shape index with all of the given polygons -// and queries it with vertex model closed which considers -// polygon edges and vertices to be part of the polygon. -func polygonsContainsPoint(s2pgns []*s2.Polygon, - point *s2.Point) bool { - idx := s2.NewShapeIndex() - for _, s2pgn := range s2pgns { - idx.Add(s2pgn) - } - - return s2.NewContainsPointQuery(idx, s2.VertexModelClosed).Contains(*point) -} - // project the point to all of the linestrings and check if // any of the projections are equal to the point. func polylineIntersectsPoint(pls []*s2.Polyline, @@ -66,6 +53,21 @@ func polylineIntersectsPolygons(pls []*s2.Polyline, containsQuery := s2.NewContainsPointQuery(idx, s2.VertexModelClosed) for _, pl := range pls { for _, point := range *pl { + + // Precheck points within the bounds of the polygon + // and for small polygons, check if the point is contained + for _, s2pgn := range s2pgns { + if !s2pgn.PointWithinBound(point) { + continue + } + + if small, inside := s2pgn.SmallPolygonContainsPoint(point); small { + if inside { + return true + } + } + } + if containsQuery.Contains(point) { return true } @@ -95,12 +97,29 @@ func polylineIntersectsPolygons(pls []*s2.Polyline, // so we create a shape index and query it instead // s2.VertexModelClosed will not consider points on the edges, so // behaviour there is arbitrary -func polygonIntersectsPoint(s2pgns []*s2.Polygon, +func polygonsIntersectsPoint(s2pgns []*s2.Polygon, point *s2.Point) bool { idx := s2.NewShapeIndex() for _, pgn := range s2pgns { + if !pgn.PointWithinBound(*point) { + continue + } + + // We don't early exit here because the point may be contained + // on the vertices of the polygon, which is not considered + if small, inside := pgn.SmallPolygonContainsPoint(*point); small { + if inside { + return true + } + } + idx.Add(pgn) } + + if idx.Len() == 0 { + return false + } + return s2.NewContainsPointQuery(idx, s2.VertexModelClosed).Contains(*point) } diff --git a/geojson/geojson_shapes_impl.go b/geojson/geojson_shapes_impl.go index 38751abf..7d8e0967 100644 --- a/geojson/geojson_shapes_impl.go +++ b/geojson/geojson_shapes_impl.go @@ -933,7 +933,7 @@ func checkPointIntersectsShape(point *s2.Point, shapeIn, other index.GeoJSON) (b // check if the other shape is a polygon. if p2, ok := other.(*Polygon); ok { // check if the point is contained within the polygon. - if polygonsContainsPoint([]*s2.Polygon{p2.s2pgn}, point) { + if polygonsIntersectsPoint([]*s2.Polygon{p2.s2pgn}, point) { return true, nil } @@ -943,7 +943,7 @@ func checkPointIntersectsShape(point *s2.Point, shapeIn, other index.GeoJSON) (b // check if the other shape is a multipolygon. if p2, ok := other.(*MultiPolygon); ok { // check if the point is contained within any of the polygons - if polygonsContainsPoint(p2.s2pgns, point) { + if polygonsIntersectsPoint(p2.s2pgns, point) { return true, nil } @@ -1195,7 +1195,7 @@ func checkPolygonIntersectsShape(s2pgn *s2.Polygon, shapeIn, other index.GeoJSON) (bool, error) { // check if the other shape is a point. if p2, ok := other.(*Point); ok { - if polygonIntersectsPoint([]*s2.Polygon{s2pgn}, p2.s2point) { + if polygonsIntersectsPoint([]*s2.Polygon{s2pgn}, p2.s2point) { return true, nil } @@ -1205,7 +1205,7 @@ func checkPolygonIntersectsShape(s2pgn *s2.Polygon, shapeIn, // check if the other shape is a multipoint. if p2, ok := other.(*MultiPoint); ok { for _, s2point := range p2.s2points { - if polygonIntersectsPoint([]*s2.Polygon{s2pgn}, s2point) { + if polygonsIntersectsPoint([]*s2.Polygon{s2pgn}, s2point) { return true, nil } } @@ -1293,10 +1293,8 @@ func checkMultiPolygonContainsShape(s2pgns []*s2.Polygon, shapeIn, other index.GeoJSON) (bool, error) { // check if the other shape is a point. if p2, ok := other.(*Point); ok { - for _, s2pgn := range s2pgns { - if polygonIntersectsPoint([]*s2.Polygon{s2pgn}, p2.s2point) { - return true, nil - } + if polygonsIntersectsPoint(s2pgns, p2.s2point) { + return true, nil } return false, nil diff --git a/s2/polygon.go b/s2/polygon.go index 7c24805d..94771253 100644 --- a/s2/polygon.go +++ b/s2/polygon.go @@ -613,6 +613,27 @@ func (p *Polygon) ContainsPoint(point Point) bool { return NewContainsPointQuery(p.index, VertexModelSemiOpen).Contains(point) } +// Check whether the point is within the bounds of the polygon. +func (p *Polygon) PointWithinBound(point Point) bool { + return p.bound.ContainsPoint(point) +} + +// SmallPolygonContainsPoint checks if the polygon is small enough to use brute force +// returns whether the check is possible and whether the point is contained +// Does not consider vertices of the polygon +func (p *Polygon) SmallPolygonContainsPoint(point Point) (bool, bool) { + const maxBruteForceVertices = 32 + if p.numVertices < maxBruteForceVertices || p.index == nil { + inside := false + for _, l := range p.loops { + inside = inside != l.bruteForceContainsPoint(point) + } + return true, inside + } + + return false, false +} + // ContainsCell reports whether the polygon contains the given cell. func (p *Polygon) ContainsCell(cell Cell) bool { it := p.index.Iterator()