gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

edge_distances.go (17676B)


      1 // Copyright 2017 Google Inc. All rights reserved.
      2 //
      3 // Licensed under the Apache License, Version 2.0 (the "License");
      4 // you may not use this file except in compliance with the License.
      5 // You may obtain a copy of the License at
      6 //
      7 //     http://www.apache.org/licenses/LICENSE-2.0
      8 //
      9 // Unless required by applicable law or agreed to in writing, software
     10 // distributed under the License is distributed on an "AS IS" BASIS,
     11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     12 // See the License for the specific language governing permissions and
     13 // limitations under the License.
     14 
     15 package s2
     16 
     17 // This file defines a collection of methods for computing the distance to an edge,
     18 // interpolating along an edge, projecting points onto edges, etc.
     19 
     20 import (
     21 	"math"
     22 
     23 	"github.com/golang/geo/s1"
     24 )
     25 
     26 // DistanceFromSegment returns the distance of point X from line segment AB.
     27 // The points are expected to be normalized. The result is very accurate for small
     28 // distances but may have some numerical error if the distance is large
     29 // (approximately pi/2 or greater). The case A == B is handled correctly.
     30 func DistanceFromSegment(x, a, b Point) s1.Angle {
     31 	var minDist s1.ChordAngle
     32 	minDist, _ = updateMinDistance(x, a, b, minDist, true)
     33 	return minDist.Angle()
     34 }
     35 
     36 // IsDistanceLess reports whether the distance from X to the edge AB is less
     37 // than limit. (For less than or equal to, specify limit.Successor()).
     38 // This method is faster than DistanceFromSegment(). If you want to
     39 // compare against a fixed s1.Angle, you should convert it to an s1.ChordAngle
     40 // once and save the value, since this conversion is relatively expensive.
     41 func IsDistanceLess(x, a, b Point, limit s1.ChordAngle) bool {
     42 	_, less := UpdateMinDistance(x, a, b, limit)
     43 	return less
     44 }
     45 
     46 // UpdateMinDistance checks if the distance from X to the edge AB is less
     47 // than minDist, and if so, returns the updated value and true.
     48 // The case A == B is handled correctly.
     49 //
     50 // Use this method when you want to compute many distances and keep track of
     51 // the minimum. It is significantly faster than using DistanceFromSegment
     52 // because (1) using s1.ChordAngle is much faster than s1.Angle, and (2) it
     53 // can save a lot of work by not actually computing the distance when it is
     54 // obviously larger than the current minimum.
     55 func UpdateMinDistance(x, a, b Point, minDist s1.ChordAngle) (s1.ChordAngle, bool) {
     56 	return updateMinDistance(x, a, b, minDist, false)
     57 }
     58 
     59 // UpdateMaxDistance checks if the distance from X to the edge AB is greater
     60 // than maxDist, and if so, returns the updated value and true.
     61 // Otherwise it returns false. The case A == B is handled correctly.
     62 func UpdateMaxDistance(x, a, b Point, maxDist s1.ChordAngle) (s1.ChordAngle, bool) {
     63 	dist := maxChordAngle(ChordAngleBetweenPoints(x, a), ChordAngleBetweenPoints(x, b))
     64 	if dist > s1.RightChordAngle {
     65 		dist, _ = updateMinDistance(Point{x.Mul(-1)}, a, b, dist, true)
     66 		dist = s1.StraightChordAngle - dist
     67 	}
     68 	if maxDist < dist {
     69 		return dist, true
     70 	}
     71 
     72 	return maxDist, false
     73 }
     74 
     75 // IsInteriorDistanceLess reports whether the minimum distance from X to the edge
     76 // AB is attained at an interior point of AB (i.e., not an endpoint), and that
     77 // distance is less than limit. (Specify limit.Successor() for less than or equal to).
     78 func IsInteriorDistanceLess(x, a, b Point, limit s1.ChordAngle) bool {
     79 	_, less := UpdateMinInteriorDistance(x, a, b, limit)
     80 	return less
     81 }
     82 
     83 // UpdateMinInteriorDistance reports whether the minimum distance from X to AB
     84 // is attained at an interior point of AB (i.e., not an endpoint), and that distance
     85 // is less than minDist. If so, the value of minDist is updated and true is returned.
     86 // Otherwise it is unchanged and returns false.
     87 func UpdateMinInteriorDistance(x, a, b Point, minDist s1.ChordAngle) (s1.ChordAngle, bool) {
     88 	return interiorDist(x, a, b, minDist, false)
     89 }
     90 
     91 // Project returns the point along the edge AB that is closest to the point X.
     92 // The fractional distance of this point along the edge AB can be obtained
     93 // using DistanceFraction.
     94 //
     95 // This requires that all points are unit length.
     96 func Project(x, a, b Point) Point {
     97 	aXb := a.PointCross(b)
     98 	// Find the closest point to X along the great circle through AB.
     99 	p := x.Sub(aXb.Mul(x.Dot(aXb.Vector) / aXb.Vector.Norm2()))
    100 
    101 	// If this point is on the edge AB, then it's the closest point.
    102 	if Sign(aXb, a, Point{p}) && Sign(Point{p}, b, aXb) {
    103 		return Point{p.Normalize()}
    104 	}
    105 
    106 	// Otherwise, the closest point is either A or B.
    107 	if x.Sub(a.Vector).Norm2() <= x.Sub(b.Vector).Norm2() {
    108 		return a
    109 	}
    110 	return b
    111 }
    112 
    113 // DistanceFraction returns the distance ratio of the point X along an edge AB.
    114 // If X is on the line segment AB, this is the fraction T such
    115 // that X == Interpolate(T, A, B).
    116 //
    117 // This requires that A and B are distinct.
    118 func DistanceFraction(x, a, b Point) float64 {
    119 	d0 := x.Angle(a.Vector)
    120 	d1 := x.Angle(b.Vector)
    121 	return float64(d0 / (d0 + d1))
    122 }
    123 
    124 // Interpolate returns the point X along the line segment AB whose distance from A
    125 // is the given fraction "t" of the distance AB. Does NOT require that "t" be
    126 // between 0 and 1. Note that all distances are measured on the surface of
    127 // the sphere, so this is more complicated than just computing (1-t)*a + t*b
    128 // and normalizing the result.
    129 func Interpolate(t float64, a, b Point) Point {
    130 	if t == 0 {
    131 		return a
    132 	}
    133 	if t == 1 {
    134 		return b
    135 	}
    136 	ab := a.Angle(b.Vector)
    137 	return InterpolateAtDistance(s1.Angle(t)*ab, a, b)
    138 }
    139 
    140 // InterpolateAtDistance returns the point X along the line segment AB whose
    141 // distance from A is the angle ax.
    142 func InterpolateAtDistance(ax s1.Angle, a, b Point) Point {
    143 	aRad := ax.Radians()
    144 
    145 	// Use PointCross to compute the tangent vector at A towards B. The
    146 	// result is always perpendicular to A, even if A=B or A=-B, but it is not
    147 	// necessarily unit length. (We effectively normalize it below.)
    148 	normal := a.PointCross(b)
    149 	tangent := normal.Vector.Cross(a.Vector)
    150 
    151 	// Now compute the appropriate linear combination of A and "tangent". With
    152 	// infinite precision the result would always be unit length, but we
    153 	// normalize it anyway to ensure that the error is within acceptable bounds.
    154 	// (Otherwise errors can build up when the result of one interpolation is
    155 	// fed into another interpolation.)
    156 	return Point{(a.Mul(math.Cos(aRad)).Add(tangent.Mul(math.Sin(aRad) / tangent.Norm()))).Normalize()}
    157 }
    158 
    159 // minUpdateDistanceMaxError returns the maximum error in the result of
    160 // UpdateMinDistance (and the associated functions such as
    161 // UpdateMinInteriorDistance, IsDistanceLess, etc), assuming that all
    162 // input points are normalized to within the bounds guaranteed by r3.Vector's
    163 // Normalize. The error can be added or subtracted from an s1.ChordAngle
    164 // using its Expanded method.
    165 func minUpdateDistanceMaxError(dist s1.ChordAngle) float64 {
    166 	// There are two cases for the maximum error in UpdateMinDistance(),
    167 	// depending on whether the closest point is interior to the edge.
    168 	return math.Max(minUpdateInteriorDistanceMaxError(dist), dist.MaxPointError())
    169 }
    170 
    171 // minUpdateInteriorDistanceMaxError returns the maximum error in the result of
    172 // UpdateMinInteriorDistance, assuming that all input points are normalized
    173 // to within the bounds guaranteed by Point's Normalize. The error can be added
    174 // or subtracted from an s1.ChordAngle using its Expanded method.
    175 //
    176 // Note that accuracy goes down as the distance approaches 0 degrees or 180
    177 // degrees (for different reasons). Near 0 degrees the error is acceptable
    178 // for all practical purposes (about 1.2e-15 radians ~= 8 nanometers).  For
    179 // exactly antipodal points the maximum error is quite high (0.5 meters),
    180 // but this error drops rapidly as the points move away from antipodality
    181 // (approximately 1 millimeter for points that are 50 meters from antipodal,
    182 // and 1 micrometer for points that are 50km from antipodal).
    183 //
    184 // TODO(roberts): Currently the error bound does not hold for edges whose endpoints
    185 // are antipodal to within about 1e-15 radians (less than 1 micron). This could
    186 // be fixed by extending PointCross to use higher precision when necessary.
    187 func minUpdateInteriorDistanceMaxError(dist s1.ChordAngle) float64 {
    188 	// If a point is more than 90 degrees from an edge, then the minimum
    189 	// distance is always to one of the endpoints, not to the edge interior.
    190 	if dist >= s1.RightChordAngle {
    191 		return 0.0
    192 	}
    193 
    194 	// This bound includes all source of error, assuming that the input points
    195 	// are normalized. a and b are components of chord length that are
    196 	// perpendicular and parallel to a plane containing the edge respectively.
    197 	b := math.Min(1.0, 0.5*float64(dist))
    198 	a := math.Sqrt(b * (2 - b))
    199 	return ((2.5+2*math.Sqrt(3)+8.5*a)*a +
    200 		(2+2*math.Sqrt(3)/3+6.5*(1-b))*b +
    201 		(23+16/math.Sqrt(3))*dblEpsilon) * dblEpsilon
    202 }
    203 
    204 // updateMinDistance computes the distance from a point X to a line segment AB,
    205 // and if either the distance was less than the given minDist, or alwaysUpdate is
    206 // true, the value and whether it was updated are returned.
    207 func updateMinDistance(x, a, b Point, minDist s1.ChordAngle, alwaysUpdate bool) (s1.ChordAngle, bool) {
    208 	if d, ok := interiorDist(x, a, b, minDist, alwaysUpdate); ok {
    209 		// Minimum distance is attained along the edge interior.
    210 		return d, true
    211 	}
    212 
    213 	// Otherwise the minimum distance is to one of the endpoints.
    214 	xa2, xb2 := (x.Sub(a.Vector)).Norm2(), x.Sub(b.Vector).Norm2()
    215 	dist := s1.ChordAngle(math.Min(xa2, xb2))
    216 	if !alwaysUpdate && dist >= minDist {
    217 		return minDist, false
    218 	}
    219 	return dist, true
    220 }
    221 
    222 // interiorDist returns the shortest distance from point x to edge ab, assuming
    223 // that the closest point to X is interior to AB. If the closest point is not
    224 // interior to AB, interiorDist returns (minDist, false). If alwaysUpdate is set to
    225 // false, the distance is only updated when the value exceeds certain the given minDist.
    226 func interiorDist(x, a, b Point, minDist s1.ChordAngle, alwaysUpdate bool) (s1.ChordAngle, bool) {
    227 	// Chord distance of x to both end points a and b.
    228 	xa2, xb2 := (x.Sub(a.Vector)).Norm2(), x.Sub(b.Vector).Norm2()
    229 
    230 	// The closest point on AB could either be one of the two vertices (the
    231 	// vertex case) or in the interior (the interior case). Let C = A x B.
    232 	// If X is in the spherical wedge extending from A to B around the axis
    233 	// through C, then we are in the interior case. Otherwise we are in the
    234 	// vertex case.
    235 	//
    236 	// Check whether we might be in the interior case. For this to be true, XAB
    237 	// and XBA must both be acute angles. Checking this condition exactly is
    238 	// expensive, so instead we consider the planar triangle ABX (which passes
    239 	// through the sphere's interior). The planar angles XAB and XBA are always
    240 	// less than the corresponding spherical angles, so if we are in the
    241 	// interior case then both of these angles must be acute.
    242 	//
    243 	// We check this by computing the squared edge lengths of the planar
    244 	// triangle ABX, and testing whether angles XAB and XBA are both acute using
    245 	// the law of cosines:
    246 	//
    247 	//            | XA^2 - XB^2 | < AB^2      (*)
    248 	//
    249 	// This test must be done conservatively (taking numerical errors into
    250 	// account) since otherwise we might miss a situation where the true minimum
    251 	// distance is achieved by a point on the edge interior.
    252 	//
    253 	// There are two sources of error in the expression above (*).  The first is
    254 	// that points are not normalized exactly; they are only guaranteed to be
    255 	// within 2 * dblEpsilon of unit length.  Under the assumption that the two
    256 	// sides of (*) are nearly equal, the total error due to normalization errors
    257 	// can be shown to be at most
    258 	//
    259 	//        2 * dblEpsilon * (XA^2 + XB^2 + AB^2) + 8 * dblEpsilon ^ 2 .
    260 	//
    261 	// The other source of error is rounding of results in the calculation of (*).
    262 	// Each of XA^2, XB^2, AB^2 has a maximum relative error of 2.5 * dblEpsilon,
    263 	// plus an additional relative error of 0.5 * dblEpsilon in the final
    264 	// subtraction which we further bound as 0.25 * dblEpsilon * (XA^2 + XB^2 +
    265 	// AB^2) for convenience.  This yields a final error bound of
    266 	//
    267 	//        4.75 * dblEpsilon * (XA^2 + XB^2 + AB^2) + 8 * dblEpsilon ^ 2 .
    268 	ab2 := a.Sub(b.Vector).Norm2()
    269 	maxError := (4.75*dblEpsilon*(xa2+xb2+ab2) + 8*dblEpsilon*dblEpsilon)
    270 	if math.Abs(xa2-xb2) >= ab2+maxError {
    271 		return minDist, false
    272 	}
    273 
    274 	// The minimum distance might be to a point on the edge interior. Let R
    275 	// be closest point to X that lies on the great circle through AB. Rather
    276 	// than computing the geodesic distance along the surface of the sphere,
    277 	// instead we compute the "chord length" through the sphere's interior.
    278 	//
    279 	// The squared chord length XR^2 can be expressed as XQ^2 + QR^2, where Q
    280 	// is the point X projected onto the plane through the great circle AB.
    281 	// The distance XQ^2 can be written as (X.C)^2 / |C|^2 where C = A x B.
    282 	// We ignore the QR^2 term and instead use XQ^2 as a lower bound, since it
    283 	// is faster and the corresponding distance on the Earth's surface is
    284 	// accurate to within 1% for distances up to about 1800km.
    285 	c := a.PointCross(b)
    286 	c2 := c.Norm2()
    287 	xDotC := x.Dot(c.Vector)
    288 	xDotC2 := xDotC * xDotC
    289 	if !alwaysUpdate && xDotC2 > c2*float64(minDist) {
    290 		// The closest point on the great circle AB is too far away.  We need to
    291 		// test this using ">" rather than ">=" because the actual minimum bound
    292 		// on the distance is (xDotC2 / c2), which can be rounded differently
    293 		// than the (more efficient) multiplicative test above.
    294 		return minDist, false
    295 	}
    296 
    297 	// Otherwise we do the exact, more expensive test for the interior case.
    298 	// This test is very likely to succeed because of the conservative planar
    299 	// test we did initially.
    300 	//
    301 	// TODO(roberts): Ensure that the errors in test are accurately reflected in the
    302 	// minUpdateInteriorDistanceMaxError.
    303 	cx := c.Cross(x.Vector)
    304 	if a.Sub(x.Vector).Dot(cx) >= 0 || b.Sub(x.Vector).Dot(cx) <= 0 {
    305 		return minDist, false
    306 	}
    307 
    308 	// Compute the squared chord length XR^2 = XQ^2 + QR^2 (see above).
    309 	// This calculation has good accuracy for all chord lengths since it
    310 	// is based on both the dot product and cross product (rather than
    311 	// deriving one from the other). However, note that the chord length
    312 	// representation itself loses accuracy as the angle approaches π.
    313 	qr := 1 - math.Sqrt(cx.Norm2()/c2)
    314 	dist := s1.ChordAngle((xDotC2 / c2) + (qr * qr))
    315 
    316 	if !alwaysUpdate && dist >= minDist {
    317 		return minDist, false
    318 	}
    319 
    320 	return dist, true
    321 }
    322 
    323 // updateEdgePairMinDistance computes the minimum distance between the given
    324 // pair of edges. If the two edges cross, the distance is zero. The cases
    325 // a0 == a1 and b0 == b1 are handled correctly.
    326 func updateEdgePairMinDistance(a0, a1, b0, b1 Point, minDist s1.ChordAngle) (s1.ChordAngle, bool) {
    327 	if minDist == 0 {
    328 		return 0, false
    329 	}
    330 	if CrossingSign(a0, a1, b0, b1) == Cross {
    331 		minDist = 0
    332 		return 0, true
    333 	}
    334 
    335 	// Otherwise, the minimum distance is achieved at an endpoint of at least
    336 	// one of the two edges. We ensure that all four possibilities are always checked.
    337 	//
    338 	// The calculation below computes each of the six vertex-vertex distances
    339 	// twice (this could be optimized).
    340 	var ok1, ok2, ok3, ok4 bool
    341 	minDist, ok1 = UpdateMinDistance(a0, b0, b1, minDist)
    342 	minDist, ok2 = UpdateMinDistance(a1, b0, b1, minDist)
    343 	minDist, ok3 = UpdateMinDistance(b0, a0, a1, minDist)
    344 	minDist, ok4 = UpdateMinDistance(b1, a0, a1, minDist)
    345 	return minDist, ok1 || ok2 || ok3 || ok4
    346 }
    347 
    348 // updateEdgePairMaxDistance reports the minimum distance between the given pair of edges.
    349 // If one edge crosses the antipodal reflection of the other, the distance is pi.
    350 func updateEdgePairMaxDistance(a0, a1, b0, b1 Point, maxDist s1.ChordAngle) (s1.ChordAngle, bool) {
    351 	if maxDist == s1.StraightChordAngle {
    352 		return s1.StraightChordAngle, false
    353 	}
    354 	if CrossingSign(a0, a1, Point{b0.Mul(-1)}, Point{b1.Mul(-1)}) == Cross {
    355 		return s1.StraightChordAngle, true
    356 	}
    357 
    358 	// Otherwise, the maximum distance is achieved at an endpoint of at least
    359 	// one of the two edges. We ensure that all four possibilities are always checked.
    360 	//
    361 	// The calculation below computes each of the six vertex-vertex distances
    362 	// twice (this could be optimized).
    363 	var ok1, ok2, ok3, ok4 bool
    364 	maxDist, ok1 = UpdateMaxDistance(a0, b0, b1, maxDist)
    365 	maxDist, ok2 = UpdateMaxDistance(a1, b0, b1, maxDist)
    366 	maxDist, ok3 = UpdateMaxDistance(b0, a0, a1, maxDist)
    367 	maxDist, ok4 = UpdateMaxDistance(b1, a0, a1, maxDist)
    368 	return maxDist, ok1 || ok2 || ok3 || ok4
    369 }
    370 
    371 // EdgePairClosestPoints returns the pair of points (a, b) that achieves the
    372 // minimum distance between edges a0a1 and b0b1, where a is a point on a0a1 and
    373 // b is a point on b0b1. If the two edges intersect, a and b are both equal to
    374 // the intersection point. Handles a0 == a1 and b0 == b1 correctly.
    375 func EdgePairClosestPoints(a0, a1, b0, b1 Point) (Point, Point) {
    376 	if CrossingSign(a0, a1, b0, b1) == Cross {
    377 		x := Intersection(a0, a1, b0, b1)
    378 		return x, x
    379 	}
    380 	// We save some work by first determining which vertex/edge pair achieves
    381 	// the minimum distance, and then computing the closest point on that edge.
    382 	var minDist s1.ChordAngle
    383 	var ok bool
    384 
    385 	minDist, ok = updateMinDistance(a0, b0, b1, minDist, true)
    386 	closestVertex := 0
    387 	if minDist, ok = UpdateMinDistance(a1, b0, b1, minDist); ok {
    388 		closestVertex = 1
    389 	}
    390 	if minDist, ok = UpdateMinDistance(b0, a0, a1, minDist); ok {
    391 		closestVertex = 2
    392 	}
    393 	if minDist, ok = UpdateMinDistance(b1, a0, a1, minDist); ok {
    394 		closestVertex = 3
    395 	}
    396 	switch closestVertex {
    397 	case 0:
    398 		return a0, Project(a0, b0, b1)
    399 	case 1:
    400 		return a1, Project(a1, b0, b1)
    401 	case 2:
    402 		return Project(b0, a0, a1), b0
    403 	case 3:
    404 		return Project(b1, a0, a1), b1
    405 	default:
    406 		panic("illegal case reached")
    407 	}
    408 }