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  }