github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/geo/geodist/geodist.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 geodist finds distances between two geospatial shapes.
    12  package geodist
    13  
    14  import "github.com/cockroachdb/errors"
    15  
    16  // Point is an interface that represents a geospatial Point.
    17  type Point interface {
    18  	IsShape()
    19  	IsPoint()
    20  }
    21  
    22  // Edge is a struct that represents a connection between two points.
    23  type Edge struct {
    24  	V0, V1 Point
    25  }
    26  
    27  // LineString is an interface that represents a geospatial LineString.
    28  type LineString interface {
    29  	Edge(i int) Edge
    30  	NumEdges() int
    31  	Vertex(i int) Point
    32  	NumVertexes() int
    33  	IsShape()
    34  	IsLineString()
    35  }
    36  
    37  // LinearRing is an interface that represents a geospatial LinearRing.
    38  type LinearRing interface {
    39  	Edge(i int) Edge
    40  	NumEdges() int
    41  	Vertex(i int) Point
    42  	NumVertexes() int
    43  	IsShape()
    44  	IsLinearRing()
    45  }
    46  
    47  // shapeWithEdges represents any shape that contains edges.
    48  type shapeWithEdges interface {
    49  	Edge(i int) Edge
    50  	NumEdges() int
    51  }
    52  
    53  // Polygon is an interface that represents a geospatial Polygon.
    54  type Polygon interface {
    55  	LinearRing(i int) LinearRing
    56  	NumLinearRings() int
    57  	IsShape()
    58  	IsPolygon()
    59  }
    60  
    61  // Shape is an interface that represents any Geospatial shape.
    62  type Shape interface {
    63  	IsShape()
    64  }
    65  
    66  var _ Shape = (Point)(nil)
    67  var _ Shape = (LineString)(nil)
    68  var _ Shape = (LinearRing)(nil)
    69  var _ Shape = (Polygon)(nil)
    70  
    71  // DistanceUpdater is a provided hook that has a series of functions that allows
    72  // the caller to maintain the distance value desired.
    73  type DistanceUpdater interface {
    74  	// Update updates the distance based on two provided points,
    75  	// returning if the function should return early.
    76  	Update(a Point, b Point) bool
    77  	// OnIntersects is called when two shapes intersects.
    78  	OnIntersects() bool
    79  	// Distance returns the distance to return so far.
    80  	Distance() float64
    81  	// IsMaxDistance returns whether the updater is looking for maximum distance.
    82  	IsMaxDistance() bool
    83  }
    84  
    85  // EdgeCrosser is a provided hook that calculates whether edges intersect.
    86  type EdgeCrosser interface {
    87  	// ChainCrossing assumes there is an edge to compare against, and the previous
    88  	// point `p0` is the start of the next edge. It will then check whether (p0, p)
    89  	// intersects with the edge. When complete, point p will become p0.
    90  	// Desired usage examples:
    91  	//   crosser := NewEdgeCrosser(edge.V0, edge.V1, startingP0)
    92  	//   intersects := crosser.ChainCrossing(p1)
    93  	//   intersects |= crosser.ChainCrossing(p2) ....
    94  	ChainCrossing(p Point) bool
    95  }
    96  
    97  // DistanceCalculator contains calculations which allow ShapeDistance to calculate
    98  // the distance between two shapes.
    99  type DistanceCalculator interface {
   100  	// DistanceUpdater returns the DistanceUpdater for the current set of calculations.
   101  	DistanceUpdater() DistanceUpdater
   102  	// NewEdgeCrosser returns a new EdgeCrosser with the given edge initialized to be
   103  	// the edge to compare against, and the start point to be the start of the first
   104  	// edge to compare against.
   105  	NewEdgeCrosser(edge Edge, startPoint Point) EdgeCrosser
   106  	// PointInLinearRing returns whether the point is inside the given linearRing.
   107  	PointInLinearRing(point Point, linearRing LinearRing) bool
   108  	// ClosestPointToEdge returns the closest point to the infinite line denoted by
   109  	// the edge, and a bool on whether this point lies on the edge segment.
   110  	ClosestPointToEdge(edge Edge, point Point) (Point, bool)
   111  	// BoundingBoxIntersects returns whether the bounding boxes of the shapes in
   112  	// question intersect.
   113  	BoundingBoxIntersects() bool
   114  }
   115  
   116  // ShapeDistance returns the distance between two given shapes.
   117  // Distance is defined by the DistanceUpdater provided by the interface.
   118  // It returns whether the function above should return early.
   119  func ShapeDistance(c DistanceCalculator, a Shape, b Shape) (bool, error) {
   120  	switch a := a.(type) {
   121  	case Point:
   122  		switch b := b.(type) {
   123  		case Point:
   124  			return c.DistanceUpdater().Update(a, b), nil
   125  		case LineString:
   126  			return onPointToLineString(c, a, b), nil
   127  		case Polygon:
   128  			return onPointToPolygon(c, a, b), nil
   129  		default:
   130  			return false, errors.Newf("unknown shape: %T", b)
   131  		}
   132  	case LineString:
   133  		switch b := b.(type) {
   134  		case Point:
   135  			return onPointToLineString(c, b, a), nil
   136  		case LineString:
   137  			return onShapeEdgesToShapeEdges(c, a, b), nil
   138  		case Polygon:
   139  			return onLineStringToPolygon(c, a, b), nil
   140  		default:
   141  			return false, errors.Newf("unknown shape: %T", b)
   142  		}
   143  	case Polygon:
   144  		switch b := b.(type) {
   145  		case Point:
   146  			return onPointToPolygon(c, b, a), nil
   147  		case LineString:
   148  			return onLineStringToPolygon(c, b, a), nil
   149  		case Polygon:
   150  			return onPolygonToPolygon(c, a, b), nil
   151  		default:
   152  			return false, errors.Newf("unknown shape: %T", b)
   153  		}
   154  	}
   155  	return false, errors.Newf("unknown shape: %T", a)
   156  }
   157  
   158  // onPointToEdgesExceptFirstEdgeStart updates the distance against the edges of a shape and a point.
   159  // It will only check the V1 of each edge and assumes the first edge start does not need the distance
   160  // to be computed.
   161  func onPointToEdgesExceptFirstEdgeStart(c DistanceCalculator, a Point, b shapeWithEdges) bool {
   162  	for edgeIdx := 0; edgeIdx < b.NumEdges(); edgeIdx++ {
   163  		edge := b.Edge(edgeIdx)
   164  		// Check against all V1 of every edge.
   165  		if c.DistanceUpdater().Update(a, edge.V1) {
   166  			return true
   167  		}
   168  		// The max distance between a point and the set of points representing an edge is the
   169  		// maximum distance from the point and the pair of end-points of the edge, so we don't
   170  		// need to update the distance using the projected point.
   171  		if !c.DistanceUpdater().IsMaxDistance() {
   172  			// Also project the point to the infinite line of the edge, and compare if the closestPoint
   173  			// lies on the edge.
   174  			if closestPoint, ok := c.ClosestPointToEdge(edge, a); ok {
   175  				if c.DistanceUpdater().Update(a, closestPoint) {
   176  					return true
   177  				}
   178  			}
   179  		}
   180  	}
   181  	return false
   182  }
   183  
   184  // onPointToLineString updates the distance between a point and a polyline.
   185  // Returns true if the calling function should early exit.
   186  func onPointToLineString(c DistanceCalculator, a Point, b LineString) bool {
   187  	// Compare the first point, to avoid checking each V0 in the chain afterwards.
   188  	if c.DistanceUpdater().Update(a, b.Vertex(0)) {
   189  		return true
   190  	}
   191  	return onPointToEdgesExceptFirstEdgeStart(c, a, b)
   192  }
   193  
   194  // onPointToPolygon updates the distance between a point and a polygon.
   195  // Returns true if the calling function should early exit.
   196  func onPointToPolygon(c DistanceCalculator, a Point, b Polygon) bool {
   197  	// MaxDistance: When computing the maximum distance, the cases are:
   198  	//   - The point P is not contained in the exterior of the polygon G.
   199  	//     Say vertex V is the vertex of the exterior of the polygon that is
   200  	//     furthest away from point P (among all the exterior vertices).
   201  	//   - One can prove that any vertex of the holes will be closer to point P than vertex V.
   202  	//     Similarly we can prove that any point in the interior of the polygin is closer to P than vertex V.
   203  	//     Therefore we only need to compare with the exterior.
   204  	//   - The point P is contained in the exterior and inside a hole of polygon G.
   205  	//     One can again prove that the furthest point in the polygon from P is one of the vertices of the exterior.
   206  	//   - The point P is contained in the polygon. One can again prove the same property.
   207  	//   So we only need to compare with the exterior ring.
   208  	// MinDistance: If the exterior ring does not contain the point, we just need to calculate the distance to
   209  	//   the exterior ring.
   210  	// BoundingBoxIntersects: if the bounding box of the shape being calculated does not intersect,
   211  	//   then we only need to compare the outer loop.
   212  	if c.DistanceUpdater().IsMaxDistance() || !c.BoundingBoxIntersects() || !c.PointInLinearRing(a, b.LinearRing(0)) {
   213  		return onPointToEdgesExceptFirstEdgeStart(c, a, b.LinearRing(0))
   214  	}
   215  	// At this point it may be inside a hole.
   216  	// If it is in a hole, return the distance to the hole.
   217  	for ringIdx := 1; ringIdx < b.NumLinearRings(); ringIdx++ {
   218  		ring := b.LinearRing(ringIdx)
   219  		if c.PointInLinearRing(a, ring) {
   220  			return onPointToEdgesExceptFirstEdgeStart(c, a, ring)
   221  		}
   222  	}
   223  
   224  	// Otherwise, we are inside the polygon.
   225  	return c.DistanceUpdater().OnIntersects()
   226  }
   227  
   228  // onShapeEdgesToShapeEdges updates the distance between two shapes by
   229  // only looking at the edges.
   230  // Returns true if the calling function should early exit.
   231  func onShapeEdgesToShapeEdges(c DistanceCalculator, a shapeWithEdges, b shapeWithEdges) bool {
   232  	for aEdgeIdx := 0; aEdgeIdx < a.NumEdges(); aEdgeIdx++ {
   233  		aEdge := a.Edge(aEdgeIdx)
   234  		var crosser EdgeCrosser
   235  		// MaxDistance: the max distance between 2 edges is the maximum of the distance across
   236  		//   pairs of vertices chosen from each edge.
   237  		//   It does not matter whether the edges cross, so we skip this check.
   238  		// BoundingBoxIntersects: if the bounding box of the two shapes do not intersect,
   239  		//   then we don't need to check whether edges intersect either.
   240  		if !c.DistanceUpdater().IsMaxDistance() && c.BoundingBoxIntersects() {
   241  			crosser = c.NewEdgeCrosser(aEdge, b.Edge(0).V0)
   242  		}
   243  		for bEdgeIdx := 0; bEdgeIdx < b.NumEdges(); bEdgeIdx++ {
   244  			bEdge := b.Edge(bEdgeIdx)
   245  			if crosser != nil {
   246  				// If the edges cross, the distance is 0.
   247  				if crosser.ChainCrossing(bEdge.V1) {
   248  					return c.DistanceUpdater().OnIntersects()
   249  				}
   250  			}
   251  
   252  			// Check the vertex against the ends of the edges.
   253  			if c.DistanceUpdater().Update(aEdge.V0, bEdge.V0) ||
   254  				c.DistanceUpdater().Update(aEdge.V0, bEdge.V1) ||
   255  				c.DistanceUpdater().Update(aEdge.V1, bEdge.V0) ||
   256  				c.DistanceUpdater().Update(aEdge.V1, bEdge.V1) {
   257  				return true
   258  			}
   259  			// Only project vertexes to edges if we are looking at the edges.
   260  			if !c.DistanceUpdater().IsMaxDistance() {
   261  				if projectVertexToEdge(c, aEdge.V0, bEdge) ||
   262  					projectVertexToEdge(c, aEdge.V1, bEdge) ||
   263  					projectVertexToEdge(c, bEdge.V0, aEdge) ||
   264  					projectVertexToEdge(c, bEdge.V1, aEdge) {
   265  					return true
   266  				}
   267  			}
   268  		}
   269  	}
   270  	return false
   271  }
   272  
   273  // projectVertexToEdge attempts to project the point onto the given edge.
   274  // Returns true if the calling function should early exit.
   275  func projectVertexToEdge(c DistanceCalculator, vertex Point, edge Edge) bool {
   276  	// Also check the projection of the vertex onto the edge.
   277  	if closestPoint, ok := c.ClosestPointToEdge(edge, vertex); ok {
   278  		if c.DistanceUpdater().Update(vertex, closestPoint) {
   279  			return true
   280  		}
   281  	}
   282  	return false
   283  }
   284  
   285  // onLineStringToPolygon updates the distance between a polyline and a polygon.
   286  // Returns true if the calling function should early exit.
   287  func onLineStringToPolygon(c DistanceCalculator, a LineString, b Polygon) bool {
   288  	// MinDistance: If we know at least one point is outside the exterior ring, then there are two cases:
   289  	//   * the line is always outside the exterior ring. We only need to compare the line
   290  	//     against the exterior ring.
   291  	//   * the line intersects with the exterior ring.
   292  	//   In both these cases, we can defer to the edge to edge comparison between the line
   293  	//   and the exterior ring.
   294  	//   We use the first point of the linestring for this check.
   295  	// MaxDistance: the furthest distance from a LineString to a Polygon is always against the
   296  	//   exterior ring. This follows the reasoning under "onPointToPolygon", but we must now
   297  	//   check each point in the LineString.
   298  	// BoundingBoxIntersects: if the bounding box of the two shapes do not intersect,
   299  	//   then the distance is always from the LineString to the exterior ring.
   300  	if c.DistanceUpdater().IsMaxDistance() ||
   301  		!c.BoundingBoxIntersects() ||
   302  		!c.PointInLinearRing(a.Vertex(0), b.LinearRing(0)) {
   303  		return onShapeEdgesToShapeEdges(c, a, b.LinearRing(0))
   304  	}
   305  
   306  	// Now we are guaranteed that there is at least one point inside the exterior ring.
   307  	//
   308  	// For a polygon with no holes, the fact that there is a point inside the exterior
   309  	// ring would imply that the distance is zero.
   310  	//
   311  	// However, when there are holes, it is possible that the distance is non-zero if
   312  	// polyline A is completely contained inside a hole. We iterate over the holes and
   313  	// compute the distance between the hole and polyline A.
   314  	// * If polyline A is within the given distance, we can immediately return.
   315  	// * If polyline A does not intersect the hole but there is at least one point inside
   316  	//   the hole, must be inside that hole and so the distance of this polyline to this hole
   317  	//   is the distance of this polyline to this polygon.
   318  	for ringIdx := 1; ringIdx < b.NumLinearRings(); ringIdx++ {
   319  		hole := b.LinearRing(ringIdx)
   320  		if onShapeEdgesToShapeEdges(c, a, hole) {
   321  			return true
   322  		}
   323  		for pointIdx := 0; pointIdx < a.NumVertexes(); pointIdx++ {
   324  			if c.PointInLinearRing(a.Vertex(pointIdx), hole) {
   325  				return false
   326  			}
   327  		}
   328  	}
   329  
   330  	// This means we are inside the exterior ring, and no points are inside a hole.
   331  	// This means the point is inside the polygon.
   332  	return c.DistanceUpdater().OnIntersects()
   333  }
   334  
   335  // onPolygonToPolygon updates the distance between two polygons.
   336  // Returns true if the calling function should early exit.
   337  func onPolygonToPolygon(c DistanceCalculator, a Polygon, b Polygon) bool {
   338  	aFirstPoint := a.LinearRing(0).Vertex(0)
   339  	bFirstPoint := b.LinearRing(0).Vertex(0)
   340  
   341  	// MinDistance:
   342  	// If there is at least one point on the the exterior ring of B that is outside the exterior ring
   343  	// of A, then we have one of these two cases:
   344  	// * The exterior rings of A and B intersect. The distance can always be found by comparing
   345  	//   the exterior rings.
   346  	// * The exterior rings of A and B never meet. This distance can always be found
   347  	//   by only comparing the exterior rings.
   348  	// If we find the point is inside the exterior ring, A could contain B, so this reasoning
   349  	// does not apply.
   350  	//
   351  	// The same reasoning applies if there is at least one point on the exterior ring of A
   352  	// that is outside the exterior ring of B.
   353  	//
   354  	// As such, we only need to compare the exterior rings if we detect this.
   355  	//
   356  	// MaxDistance:
   357  	//   The furthest distance between two polygons is always against the exterior rings of each other.
   358  	//   This closely follows the reasoning pointed out in "onPointToPolygon". Holes are always located
   359  	//   inside the exterior ring of a polygon, so the exterior ring will always contain a point
   360  	//   with a larger max distance.
   361  	// BoundingBoxIntersects: if the bounding box of the two shapes do not intersect,
   362  	//   then the distance is always between the two exterior rings.
   363  	if c.DistanceUpdater().IsMaxDistance() ||
   364  		!c.BoundingBoxIntersects() ||
   365  		!c.PointInLinearRing(bFirstPoint, a.LinearRing(0)) && !c.PointInLinearRing(aFirstPoint, b.LinearRing(0)) {
   366  		return onShapeEdgesToShapeEdges(c, a.LinearRing(0), b.LinearRing(0))
   367  	}
   368  
   369  	// If any point of polygon A is inside a hole of polygon B, then either:
   370  	// * A is inside the hole and the closest point can be found by comparing A's outer
   371  	//   linearRing and the hole in B, or
   372  	// * A intersects this hole and the distance is zero, which can also be found by comparing
   373  	//   A's outer linearRing and the hole in B.
   374  	// In this case, we only need to compare the holes of B to contain a single point A.
   375  	for ringIdx := 1; ringIdx < b.NumLinearRings(); ringIdx++ {
   376  		bHole := b.LinearRing(ringIdx)
   377  		if c.PointInLinearRing(aFirstPoint, bHole) {
   378  			return onShapeEdgesToShapeEdges(c, a.LinearRing(0), bHole)
   379  		}
   380  	}
   381  
   382  	// Do the same check for the polygons the other way around.
   383  	for ringIdx := 1; ringIdx < a.NumLinearRings(); ringIdx++ {
   384  		aHole := a.LinearRing(ringIdx)
   385  		if c.PointInLinearRing(bFirstPoint, aHole) {
   386  			return onShapeEdgesToShapeEdges(c, b.LinearRing(0), aHole)
   387  		}
   388  	}
   389  
   390  	// Now we know either a point of the exterior ring A is definitely inside polygon B
   391  	// or vice versa. This is an intersection.
   392  	return c.DistanceUpdater().OnIntersects()
   393  }