github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/geo/geogfn/intersects.go (about)

     1  // Copyright 2020 The Cockroach Authors.
     2  //
     3  // Use of this software is governed by the Business Source License
     4  // included in the file licenses/BSL.txt.
     5  //
     6  // As of the Change Date specified in that file, in accordance with
     7  // the Business Source License, use of this software will be governed
     8  // by the Apache License, Version 2.0, included in the file
     9  // licenses/APL.txt.
    10  
    11  package geogfn
    12  
    13  import (
    14  	"fmt"
    15  
    16  	"github.com/cockroachdb/cockroach/pkg/geo"
    17  	"github.com/golang/geo/s2"
    18  )
    19  
    20  // Intersects returns whether geography A intersects geography B.
    21  // This calculation is done on the sphere.
    22  // Precision of intersect measurements is up to 1cm.
    23  func Intersects(a *geo.Geography, b *geo.Geography) (bool, error) {
    24  	if a.SRID() != b.SRID() {
    25  		return false, geo.NewMismatchingSRIDsError(a, b)
    26  	}
    27  
    28  	aRegions, err := a.AsS2(geo.EmptyBehaviorOmit)
    29  	if err != nil {
    30  		return false, err
    31  	}
    32  	bRegions, err := b.AsS2(geo.EmptyBehaviorOmit)
    33  	if err != nil {
    34  		return false, err
    35  	}
    36  	// If any of aRegions intersects any of bRegions, return true.
    37  	for _, aRegion := range aRegions {
    38  		for _, bRegion := range bRegions {
    39  			intersects, err := singleRegionIntersects(aRegion, bRegion)
    40  			if err != nil {
    41  				return false, err
    42  			}
    43  			if intersects {
    44  				return true, nil
    45  			}
    46  		}
    47  	}
    48  	return false, nil
    49  }
    50  
    51  // singleRegionIntersects returns true if aRegion intersects bRegion.
    52  func singleRegionIntersects(aRegion s2.Region, bRegion s2.Region) (bool, error) {
    53  	switch aRegion := aRegion.(type) {
    54  	case s2.Point:
    55  		switch bRegion := bRegion.(type) {
    56  		case s2.Point:
    57  			return aRegion.IntersectsCell(s2.CellFromPoint(bRegion)), nil
    58  		case *s2.Polyline:
    59  			return bRegion.IntersectsCell(s2.CellFromPoint(aRegion)), nil
    60  		case *s2.Polygon:
    61  			return bRegion.IntersectsCell(s2.CellFromPoint(aRegion)), nil
    62  		default:
    63  			return false, fmt.Errorf("unknown s2 type of b: %#v", bRegion)
    64  		}
    65  	case *s2.Polyline:
    66  		switch bRegion := bRegion.(type) {
    67  		case s2.Point:
    68  			return aRegion.IntersectsCell(s2.CellFromPoint(bRegion)), nil
    69  		case *s2.Polyline:
    70  			return polylineIntersectsPolyline(aRegion, bRegion), nil
    71  		case *s2.Polygon:
    72  			return polygonIntersectsPolyline(bRegion, aRegion), nil
    73  		default:
    74  			return false, fmt.Errorf("unknown s2 type of b: %#v", bRegion)
    75  		}
    76  	case *s2.Polygon:
    77  		switch bRegion := bRegion.(type) {
    78  		case s2.Point:
    79  			return aRegion.IntersectsCell(s2.CellFromPoint(bRegion)), nil
    80  		case *s2.Polyline:
    81  			return polygonIntersectsPolyline(aRegion, bRegion), nil
    82  		case *s2.Polygon:
    83  			return aRegion.Intersects(bRegion), nil
    84  		default:
    85  			return false, fmt.Errorf("unknown s2 type of b: %#v", bRegion)
    86  		}
    87  	}
    88  	return false, fmt.Errorf("unknown s2 type of a: %#v", aRegion)
    89  }
    90  
    91  // polylineIntersectsPolyline returns whether polyline a intersects with
    92  // polyline b.
    93  func polylineIntersectsPolyline(a *s2.Polyline, b *s2.Polyline) bool {
    94  	for aEdgeIdx := 0; aEdgeIdx < a.NumEdges(); aEdgeIdx++ {
    95  		edge := a.Edge(aEdgeIdx)
    96  		crosser := s2.NewChainEdgeCrosser(edge.V0, edge.V1, (*b)[0])
    97  		for _, nextVertex := range (*b)[1:] {
    98  			crossing := crosser.ChainCrossingSign(nextVertex)
    99  			if crossing != s2.DoNotCross {
   100  				return true
   101  			}
   102  		}
   103  	}
   104  	return false
   105  }
   106  
   107  // polygonIntersectsPolyline returns whether polygon a intersects with
   108  // polyline b.
   109  func polygonIntersectsPolyline(a *s2.Polygon, b *s2.Polyline) bool {
   110  	// Check if the polygon contains any vertex of the line b.
   111  	for _, vertex := range *b {
   112  		if a.IntersectsCell(s2.CellFromPoint(vertex)) {
   113  			return true
   114  		}
   115  	}
   116  	// Here the polygon does not contain any vertex of the polyline.
   117  	// The polyline can intersect the polygon if a line goes through the polygon
   118  	// with both vertexes that are not in the interior of the polygon.
   119  	// This technique works for holes touching, or holes touching the exterior
   120  	// as the point in which the holes touch is considered an intersection.
   121  	for _, loop := range a.Loops() {
   122  		for loopEdgeIdx := 0; loopEdgeIdx < loop.NumEdges(); loopEdgeIdx++ {
   123  			loopEdge := loop.Edge(loopEdgeIdx)
   124  			crosser := s2.NewChainEdgeCrosser(loopEdge.V0, loopEdge.V1, (*b)[0])
   125  			for _, nextVertex := range (*b)[1:] {
   126  				crossing := crosser.ChainCrossingSign(nextVertex)
   127  				if crossing != s2.DoNotCross {
   128  					return true
   129  				}
   130  			}
   131  		}
   132  	}
   133  	return false
   134  }