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 }