github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/geo/geogfn/distance.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 "math" 15 16 "github.com/cockroachdb/cockroach/pkg/geo" 17 "github.com/cockroachdb/cockroach/pkg/geo/geodist" 18 "github.com/cockroachdb/cockroach/pkg/geo/geographiclib" 19 "github.com/cockroachdb/errors" 20 "github.com/golang/geo/s1" 21 "github.com/golang/geo/s2" 22 ) 23 24 // Distance returns the distance between geographies a and b on a sphere or spheroid. 25 // Returns a geo.EmptyGeometryError if any of the Geographies are EMPTY. 26 func Distance( 27 a *geo.Geography, b *geo.Geography, useSphereOrSpheroid UseSphereOrSpheroid, 28 ) (float64, error) { 29 if a.SRID() != b.SRID() { 30 return 0, geo.NewMismatchingSRIDsError(a, b) 31 } 32 33 aRegions, err := a.AsS2(geo.EmptyBehaviorError) 34 if err != nil { 35 return 0, err 36 } 37 bRegions, err := b.AsS2(geo.EmptyBehaviorError) 38 if err != nil { 39 return 0, err 40 } 41 spheroid := geographiclib.WGS84Spheroid 42 return distanceGeographyRegions(spheroid, useSphereOrSpheroid, aRegions, bRegions, 0) 43 } 44 45 // 46 // Spheroids 47 // 48 49 // s2GeodistPoint implements geodist.Point. 50 type s2GeodistPoint struct { 51 s2.Point 52 } 53 54 var _ geodist.Point = (*s2GeodistPoint)(nil) 55 56 // IsShape implements the geodist.Point interface. 57 func (*s2GeodistPoint) IsShape() {} 58 59 // Point implements the geodist.Point interface. 60 func (*s2GeodistPoint) IsPoint() {} 61 62 // s2GeodistLineString implements geodist.LineString. 63 type s2GeodistLineString struct { 64 *s2.Polyline 65 } 66 67 var _ geodist.LineString = (*s2GeodistLineString)(nil) 68 69 // IsShape implements the geodist.LineString interface. 70 func (*s2GeodistLineString) IsShape() {} 71 72 // LineString implements the geodist.LineString interface. 73 func (*s2GeodistLineString) IsLineString() {} 74 75 // Edge implements the geodist.LineString interface. 76 func (g *s2GeodistLineString) Edge(i int) geodist.Edge { 77 return geodist.Edge{V0: &s2GeodistPoint{Point: (*g.Polyline)[i]}, V1: &s2GeodistPoint{Point: (*g.Polyline)[i+1]}} 78 } 79 80 // NumEdges implements the geodist.LineString interface. 81 func (g *s2GeodistLineString) NumEdges() int { 82 return len(*g.Polyline) - 1 83 } 84 85 // Vertex implements the geodist.LineString interface. 86 func (g *s2GeodistLineString) Vertex(i int) geodist.Point { 87 return &s2GeodistPoint{Point: (*g.Polyline)[i]} 88 } 89 90 // NumVertexes implements the geodist.LineString interface. 91 func (g *s2GeodistLineString) NumVertexes() int { 92 return len(*g.Polyline) 93 } 94 95 // s2GeodistLinearRing implements geodist.LinearRing. 96 type s2GeodistLinearRing struct { 97 *s2.Loop 98 } 99 100 var _ geodist.LinearRing = (*s2GeodistLinearRing)(nil) 101 102 // IsShape implements the geodist.LinearRing interface. 103 func (*s2GeodistLinearRing) IsShape() {} 104 105 // LinearRing implements the geodist.LinearRing interface. 106 func (*s2GeodistLinearRing) IsLinearRing() {} 107 108 // Edge implements the geodist.LinearRing interface. 109 func (g *s2GeodistLinearRing) Edge(i int) geodist.Edge { 110 return geodist.Edge{V0: &s2GeodistPoint{Point: g.Loop.Vertex(i)}, V1: &s2GeodistPoint{Point: g.Loop.Vertex(i + 1)}} 111 } 112 113 // NumEdges implements the geodist.LinearRing interface. 114 func (g *s2GeodistLinearRing) NumEdges() int { 115 return g.Loop.NumEdges() 116 } 117 118 // Vertex implements the geodist.LinearRing interface. 119 func (g *s2GeodistLinearRing) Vertex(i int) geodist.Point { 120 return &s2GeodistPoint{Point: g.Loop.Vertex(i)} 121 } 122 123 // NumVertexes implements the geodist.LinearRing interface. 124 func (g *s2GeodistLinearRing) NumVertexes() int { 125 return g.Loop.NumVertices() 126 } 127 128 // s2GeodistPolygon implements geodist.Polygon. 129 type s2GeodistPolygon struct { 130 *s2.Polygon 131 } 132 133 var _ geodist.Polygon = (*s2GeodistPolygon)(nil) 134 135 // IsShape implements the geodist.Polygon interface. 136 func (*s2GeodistPolygon) IsShape() {} 137 138 // Polygon implements the geodist.Polygon interface. 139 func (*s2GeodistPolygon) IsPolygon() {} 140 141 // LinearRing implements the geodist.Polygon interface. 142 func (g *s2GeodistPolygon) LinearRing(i int) geodist.LinearRing { 143 return &s2GeodistLinearRing{Loop: g.Polygon.Loop(i)} 144 } 145 146 // NumLinearRings implements the geodist.Polygon interface. 147 func (g *s2GeodistPolygon) NumLinearRings() int { 148 return g.Polygon.NumLoops() 149 } 150 151 // s2GeodistEdgeCrosser implements geodist.EdgeCrosser. 152 type s2GeodistEdgeCrosser struct { 153 *s2.EdgeCrosser 154 } 155 156 var _ geodist.EdgeCrosser = (*s2GeodistEdgeCrosser)(nil) 157 158 // ChainCrossing implements geodist.EdgeCrosser. 159 func (c *s2GeodistEdgeCrosser) ChainCrossing(p geodist.Point) bool { 160 return c.EdgeCrosser.ChainCrossingSign(p.(*s2GeodistPoint).Point) != s2.DoNotCross 161 } 162 163 // distanceGeographyRegions calculates the distance between two sets of regions. 164 // It will quit if it finds a distance that is less than stopAfterLE. 165 // It is not guaranteed to find the absolute minimum distance if stopAfterLE > 0. 166 // 167 // !!! SURPRISING BEHAVIOR WARNING FOR SPHEROIDS !!! 168 // PostGIS evaluates the distance between spheroid regions by computing the min of 169 // the pair-wise distance between the cross-product of the regions in A and the regions 170 // in B, where the pair-wise distance is computed as: 171 // * Find the two closest points between the pairs of regions using the sphere 172 // for distance calculations. 173 // * Compute the spheroid distance between the two closest points. 174 // 175 // This is technically incorrect, since it is possible that the two closest points on 176 // the spheroid are different than the two closest points on the sphere. 177 // See distance_test.go for examples of the "truer" distance values. 178 // Since we aim to be compatible with PostGIS, we adopt the same approach. 179 func distanceGeographyRegions( 180 spheroid geographiclib.Spheroid, 181 useSphereOrSpheroid UseSphereOrSpheroid, 182 aRegions []s2.Region, 183 bRegions []s2.Region, 184 stopAfterLE float64, 185 ) (float64, error) { 186 minDistance := math.MaxFloat64 187 for _, aRegion := range aRegions { 188 aGeodist, err := regionToGeodistShape(aRegion) 189 if err != nil { 190 return 0, err 191 } 192 for _, bRegion := range bRegions { 193 minDistanceUpdater := newGeographyMinDistanceUpdater(spheroid, useSphereOrSpheroid, stopAfterLE) 194 bGeodist, err := regionToGeodistShape(bRegion) 195 if err != nil { 196 return 0, err 197 } 198 earlyExit, err := geodist.ShapeDistance( 199 &geographyDistanceCalculator{updater: minDistanceUpdater}, 200 aGeodist, 201 bGeodist, 202 ) 203 if err != nil { 204 return 0, err 205 } 206 minDistance = math.Min(minDistance, minDistanceUpdater.Distance()) 207 if earlyExit { 208 return minDistance, nil 209 } 210 } 211 } 212 return minDistance, nil 213 } 214 215 // geographyMinDistanceUpdater finds the minimum distance using a sphere. 216 // Methods will return early if it finds a minimum distance <= stopAfterLE. 217 type geographyMinDistanceUpdater struct { 218 spheroid geographiclib.Spheroid 219 useSphereOrSpheroid UseSphereOrSpheroid 220 minEdge s2.Edge 221 minD s1.ChordAngle 222 stopAfterLE s1.ChordAngle 223 } 224 225 var _ geodist.DistanceUpdater = (*geographyMinDistanceUpdater)(nil) 226 227 // newGeographyMinDistanceUpdater returns a new geographyMinDistanceUpdater with the 228 // correct arguments set up. 229 func newGeographyMinDistanceUpdater( 230 spheroid geographiclib.Spheroid, useSphereOrSpheroid UseSphereOrSpheroid, stopAfterLE float64, 231 ) *geographyMinDistanceUpdater { 232 multiplier := 1.0 233 if useSphereOrSpheroid == UseSpheroid { 234 // Modify the stopAfterLE distance to be 95% of the proposed stopAfterLE, since 235 // we use the sphere to calculate the distance and we want to leave a buffer 236 // for spheroid distances being slightly off. 237 // Distances should differ by a maximum of (2 * spheroid.Flattening)%, 238 // so the 5% margin is pretty safe. 239 multiplier = 0.95 240 } 241 stopAfterLEChordAngle := s1.ChordAngleFromAngle(s1.Angle(stopAfterLE * multiplier / spheroid.SphereRadius)) 242 return &geographyMinDistanceUpdater{ 243 spheroid: spheroid, 244 minD: math.MaxFloat64, 245 useSphereOrSpheroid: useSphereOrSpheroid, 246 stopAfterLE: stopAfterLEChordAngle, 247 } 248 } 249 250 // Distance implements the DistanceUpdater interface. 251 func (u *geographyMinDistanceUpdater) Distance() float64 { 252 // If the distance is zero, avoid the call to spheroidDistance and return early. 253 if u.minD == 0 { 254 return 0 255 } 256 if u.useSphereOrSpheroid == UseSpheroid { 257 return spheroidDistance(u.spheroid, u.minEdge.V0, u.minEdge.V1) 258 } 259 return u.minD.Angle().Radians() * u.spheroid.SphereRadius 260 } 261 262 // Update implements the geodist.DistanceUpdater interface. 263 func (u *geographyMinDistanceUpdater) Update( 264 aInterface geodist.Point, bInterface geodist.Point, 265 ) bool { 266 a := aInterface.(*s2GeodistPoint).Point 267 b := bInterface.(*s2GeodistPoint).Point 268 269 sphereDistance := s2.ChordAngleBetweenPoints(a, b) 270 if sphereDistance < u.minD { 271 u.minD = sphereDistance 272 u.minEdge = s2.Edge{V0: a, V1: b} 273 // If we have a threshold, determine if we can stop early. 274 // If the sphere distance is within range of the stopAfter, we can 275 // definitively say we've reach the close enough point. 276 if u.minD <= u.stopAfterLE { 277 return true 278 } 279 } 280 return false 281 } 282 283 // OnIntersects implements the geodist.DistanceUpdater interface. 284 func (u *geographyMinDistanceUpdater) OnIntersects() bool { 285 u.minD = 0 286 return true 287 } 288 289 // IsMaxDistance implements the geodist.DistanceUpdater interface. 290 func (u *geographyMinDistanceUpdater) IsMaxDistance() bool { 291 return false 292 } 293 294 // geographyDistanceCalculator implements geodist.DistanceCalculator 295 type geographyDistanceCalculator struct { 296 updater *geographyMinDistanceUpdater 297 } 298 299 var _ geodist.DistanceCalculator = (*geographyDistanceCalculator)(nil) 300 301 // DistanceUpdater implements geodist.DistanceCalculator. 302 func (c *geographyDistanceCalculator) DistanceUpdater() geodist.DistanceUpdater { 303 return c.updater 304 } 305 306 // BoundingBoxIntersects implements geodist.DistanceCalculator. 307 func (c *geographyDistanceCalculator) BoundingBoxIntersects() bool { 308 // Return true, as it does the safer thing beneath. 309 // TODO(otan): update bounding box intersects. 310 return true 311 } 312 313 // NewEdgeCrosser implements geodist.DistanceCalculator. 314 func (c *geographyDistanceCalculator) NewEdgeCrosser( 315 edge geodist.Edge, startPoint geodist.Point, 316 ) geodist.EdgeCrosser { 317 return &s2GeodistEdgeCrosser{ 318 EdgeCrosser: s2.NewChainEdgeCrosser( 319 edge.V0.(*s2GeodistPoint).Point, 320 edge.V1.(*s2GeodistPoint).Point, 321 startPoint.(*s2GeodistPoint).Point, 322 ), 323 } 324 } 325 326 // PointInLinearRing implements geodist.DistanceCalculator. 327 func (c *geographyDistanceCalculator) PointInLinearRing( 328 point geodist.Point, polygon geodist.LinearRing, 329 ) bool { 330 return polygon.(*s2GeodistLinearRing).ContainsPoint(point.(*s2GeodistPoint).Point) 331 } 332 333 // ClosestPointToEdge implements geodist.DistanceCalculator. 334 // 335 // ClosestPointToEdge projects the point onto the infinite line represented 336 // by the edge. This will return the point on the line closest to the edge. 337 // It will return the closest point on the line, as well as a bool representing 338 // whether the point that is projected lies directly on the edge as a segment. 339 // 340 // For visualization and more, see: Section 6 / Figure 4 of 341 // "Projective configuration theorems: old wine into new wineskins", Tabachnikov, Serge, 2016/07/16 342 func (c *geographyDistanceCalculator) ClosestPointToEdge( 343 edge geodist.Edge, pointInterface geodist.Point, 344 ) (geodist.Point, bool) { 345 eV0 := edge.V0.(*s2GeodistPoint).Point 346 eV1 := edge.V1.(*s2GeodistPoint).Point 347 point := pointInterface.(*s2GeodistPoint).Point 348 349 // Project the point onto the normal of the edge. A great circle passing through 350 // the normal and the point will intersect with the great circle represented 351 // by the given edge. 352 normal := eV0.Vector.Cross(eV1.Vector).Normalize() 353 // To find the point where the great circle represented by the edge and the 354 // great circle represented by (normal, point), we project the point 355 // onto the normal. 356 normalScaledToPoint := normal.Mul(normal.Dot(point.Vector)) 357 // The difference between the point and the projection of the normal when normalized 358 // should give us a point on the great circle which contains the vertexes of the edge. 359 closestPoint := s2.Point{Vector: point.Vector.Sub(normalScaledToPoint).Normalize()} 360 // We then check whether the given point lies on the geodesic of the edge, 361 // as the above algorithm only generates a point on the great circle 362 // represented by the edge. 363 return &s2GeodistPoint{Point: closestPoint}, (&s2.Polyline{eV0, eV1}).IntersectsCell(s2.CellFromPoint(closestPoint)) 364 } 365 366 // regionToGeodistShape converts the s2 Region to a geodist object. 367 func regionToGeodistShape(r s2.Region) (geodist.Shape, error) { 368 switch r := r.(type) { 369 case s2.Point: 370 return &s2GeodistPoint{Point: r}, nil 371 case *s2.Polyline: 372 return &s2GeodistLineString{Polyline: r}, nil 373 case *s2.Polygon: 374 return &s2GeodistPolygon{Polygon: r}, nil 375 } 376 return nil, errors.Newf("unknown region: %T", r) 377 }