gtsocial-umbx

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

rect_bounder.go (16038B)


      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 import (
     18 	"math"
     19 
     20 	"github.com/golang/geo/r1"
     21 	"github.com/golang/geo/r3"
     22 	"github.com/golang/geo/s1"
     23 )
     24 
     25 // RectBounder is used to compute a bounding rectangle that contains all edges
     26 // defined by a vertex chain (v0, v1, v2, ...). All vertices must be unit length.
     27 // Note that the bounding rectangle of an edge can be larger than the bounding
     28 // rectangle of its endpoints, e.g. consider an edge that passes through the North Pole.
     29 //
     30 // The bounds are calculated conservatively to account for numerical errors
     31 // when points are converted to LatLngs. More precisely, this function
     32 // guarantees the following:
     33 // Let L be a closed edge chain (Loop) such that the interior of the loop does
     34 // not contain either pole. Now if P is any point such that L.ContainsPoint(P),
     35 // then RectBound(L).ContainsPoint(LatLngFromPoint(P)).
     36 type RectBounder struct {
     37 	// The previous vertex in the chain.
     38 	a Point
     39 	// The previous vertex latitude longitude.
     40 	aLL   LatLng
     41 	bound Rect
     42 }
     43 
     44 // NewRectBounder returns a new instance of a RectBounder.
     45 func NewRectBounder() *RectBounder {
     46 	return &RectBounder{
     47 		bound: EmptyRect(),
     48 	}
     49 }
     50 
     51 // maxErrorForTests returns the maximum error in RectBound provided that the
     52 // result does not include either pole. It is only used for testing purposes
     53 func (r *RectBounder) maxErrorForTests() LatLng {
     54 	// The maximum error in the latitude calculation is
     55 	//    3.84 * dblEpsilon   for the PointCross calculation
     56 	//    0.96 * dblEpsilon   for the Latitude calculation
     57 	//    5    * dblEpsilon   added by AddPoint/RectBound to compensate for error
     58 	//    -----------------
     59 	//    9.80 * dblEpsilon   maximum error in result
     60 	//
     61 	// The maximum error in the longitude calculation is dblEpsilon. RectBound
     62 	// does not do any expansion because this isn't necessary in order to
     63 	// bound the *rounded* longitudes of contained points.
     64 	return LatLng{10 * dblEpsilon * s1.Radian, 1 * dblEpsilon * s1.Radian}
     65 }
     66 
     67 // AddPoint adds the given point to the chain. The Point must be unit length.
     68 func (r *RectBounder) AddPoint(b Point) {
     69 	bLL := LatLngFromPoint(b)
     70 
     71 	if r.bound.IsEmpty() {
     72 		r.a = b
     73 		r.aLL = bLL
     74 		r.bound = r.bound.AddPoint(bLL)
     75 		return
     76 	}
     77 
     78 	// First compute the cross product N = A x B robustly. This is the normal
     79 	// to the great circle through A and B. We don't use RobustSign
     80 	// since that method returns an arbitrary vector orthogonal to A if the two
     81 	// vectors are proportional, and we want the zero vector in that case.
     82 	n := r.a.Sub(b.Vector).Cross(r.a.Add(b.Vector)) // N = 2 * (A x B)
     83 
     84 	// The relative error in N gets large as its norm gets very small (i.e.,
     85 	// when the two points are nearly identical or antipodal). We handle this
     86 	// by choosing a maximum allowable error, and if the error is greater than
     87 	// this we fall back to a different technique. Since it turns out that
     88 	// the other sources of error in converting the normal to a maximum
     89 	// latitude add up to at most 1.16 * dblEpsilon, and it is desirable to
     90 	// have the total error be a multiple of dblEpsilon, we have chosen to
     91 	// limit the maximum error in the normal to be 3.84 * dblEpsilon.
     92 	// It is possible to show that the error is less than this when
     93 	//
     94 	// n.Norm() >= 8 * sqrt(3) / (3.84 - 0.5 - sqrt(3)) * dblEpsilon
     95 	//          = 1.91346e-15 (about 8.618 * dblEpsilon)
     96 	nNorm := n.Norm()
     97 	if nNorm < 1.91346e-15 {
     98 		// A and B are either nearly identical or nearly antipodal (to within
     99 		// 4.309 * dblEpsilon, or about 6 nanometers on the earth's surface).
    100 		if r.a.Dot(b.Vector) < 0 {
    101 			// The two points are nearly antipodal. The easiest solution is to
    102 			// assume that the edge between A and B could go in any direction
    103 			// around the sphere.
    104 			r.bound = FullRect()
    105 		} else {
    106 			// The two points are nearly identical (to within 4.309 * dblEpsilon).
    107 			// In this case we can just use the bounding rectangle of the points,
    108 			// since after the expansion done by GetBound this Rect is
    109 			// guaranteed to include the (lat,lng) values of all points along AB.
    110 			r.bound = r.bound.Union(RectFromLatLng(r.aLL).AddPoint(bLL))
    111 		}
    112 		r.a = b
    113 		r.aLL = bLL
    114 		return
    115 	}
    116 
    117 	// Compute the longitude range spanned by AB.
    118 	lngAB := s1.EmptyInterval().AddPoint(r.aLL.Lng.Radians()).AddPoint(bLL.Lng.Radians())
    119 	if lngAB.Length() >= math.Pi-2*dblEpsilon {
    120 		// The points lie on nearly opposite lines of longitude to within the
    121 		// maximum error of the calculation. The easiest solution is to assume
    122 		// that AB could go on either side of the pole.
    123 		lngAB = s1.FullInterval()
    124 	}
    125 
    126 	// Next we compute the latitude range spanned by the edge AB. We start
    127 	// with the range spanning the two endpoints of the edge:
    128 	latAB := r1.IntervalFromPoint(r.aLL.Lat.Radians()).AddPoint(bLL.Lat.Radians())
    129 
    130 	// This is the desired range unless the edge AB crosses the plane
    131 	// through N and the Z-axis (which is where the great circle through A
    132 	// and B attains its minimum and maximum latitudes). To test whether AB
    133 	// crosses this plane, we compute a vector M perpendicular to this
    134 	// plane and then project A and B onto it.
    135 	m := n.Cross(r3.Vector{0, 0, 1})
    136 	mA := m.Dot(r.a.Vector)
    137 	mB := m.Dot(b.Vector)
    138 
    139 	// We want to test the signs of "mA" and "mB", so we need to bound
    140 	// the error in these calculations. It is possible to show that the
    141 	// total error is bounded by
    142 	//
    143 	// (1 + sqrt(3)) * dblEpsilon * nNorm + 8 * sqrt(3) * (dblEpsilon**2)
    144 	//   = 6.06638e-16 * nNorm + 6.83174e-31
    145 
    146 	mError := 6.06638e-16*nNorm + 6.83174e-31
    147 	if mA*mB < 0 || math.Abs(mA) <= mError || math.Abs(mB) <= mError {
    148 		// Minimum/maximum latitude *may* occur in the edge interior.
    149 		//
    150 		// The maximum latitude is 90 degrees minus the latitude of N. We
    151 		// compute this directly using atan2 in order to get maximum accuracy
    152 		// near the poles.
    153 		//
    154 		// Our goal is compute a bound that contains the computed latitudes of
    155 		// all S2Points P that pass the point-in-polygon containment test.
    156 		// There are three sources of error we need to consider:
    157 		// - the directional error in N (at most 3.84 * dblEpsilon)
    158 		// - converting N to a maximum latitude
    159 		// - computing the latitude of the test point P
    160 		// The latter two sources of error are at most 0.955 * dblEpsilon
    161 		// individually, but it is possible to show by a more complex analysis
    162 		// that together they can add up to at most 1.16 * dblEpsilon, for a
    163 		// total error of 5 * dblEpsilon.
    164 		//
    165 		// We add 3 * dblEpsilon to the bound here, and GetBound() will pad
    166 		// the bound by another 2 * dblEpsilon.
    167 		maxLat := math.Min(
    168 			math.Atan2(math.Sqrt(n.X*n.X+n.Y*n.Y), math.Abs(n.Z))+3*dblEpsilon,
    169 			math.Pi/2)
    170 
    171 		// In order to get tight bounds when the two points are close together,
    172 		// we also bound the min/max latitude relative to the latitudes of the
    173 		// endpoints A and B. First we compute the distance between A and B,
    174 		// and then we compute the maximum change in latitude between any two
    175 		// points along the great circle that are separated by this distance.
    176 		// This gives us a latitude change "budget". Some of this budget must
    177 		// be spent getting from A to B; the remainder bounds the round-trip
    178 		// distance (in latitude) from A or B to the min or max latitude
    179 		// attained along the edge AB.
    180 		latBudget := 2 * math.Asin(0.5*(r.a.Sub(b.Vector)).Norm()*math.Sin(maxLat))
    181 		maxDelta := 0.5*(latBudget-latAB.Length()) + dblEpsilon
    182 
    183 		// Test whether AB passes through the point of maximum latitude or
    184 		// minimum latitude. If the dot product(s) are small enough then the
    185 		// result may be ambiguous.
    186 		if mA <= mError && mB >= -mError {
    187 			latAB.Hi = math.Min(maxLat, latAB.Hi+maxDelta)
    188 		}
    189 		if mB <= mError && mA >= -mError {
    190 			latAB.Lo = math.Max(-maxLat, latAB.Lo-maxDelta)
    191 		}
    192 	}
    193 	r.a = b
    194 	r.aLL = bLL
    195 	r.bound = r.bound.Union(Rect{latAB, lngAB})
    196 }
    197 
    198 // RectBound returns the bounding rectangle of the edge chain that connects the
    199 // vertices defined so far. This bound satisfies the guarantee made
    200 // above, i.e. if the edge chain defines a Loop, then the bound contains
    201 // the LatLng coordinates of all Points contained by the loop.
    202 func (r *RectBounder) RectBound() Rect {
    203 	return r.bound.expanded(LatLng{s1.Angle(2 * dblEpsilon), 0}).PolarClosure()
    204 }
    205 
    206 // ExpandForSubregions expands a bounding Rect so that it is guaranteed to
    207 // contain the bounds of any subregion whose bounds are computed using
    208 // ComputeRectBound. For example, consider a loop L that defines a square.
    209 // GetBound ensures that if a point P is contained by this square, then
    210 // LatLngFromPoint(P) is contained by the bound. But now consider a diamond
    211 // shaped loop S contained by L. It is possible that GetBound returns a
    212 // *larger* bound for S than it does for L, due to rounding errors. This
    213 // method expands the bound for L so that it is guaranteed to contain the
    214 // bounds of any subregion S.
    215 //
    216 // More precisely, if L is a loop that does not contain either pole, and S
    217 // is a loop such that L.Contains(S), then
    218 //
    219 //   ExpandForSubregions(L.RectBound).Contains(S.RectBound).
    220 //
    221 func ExpandForSubregions(bound Rect) Rect {
    222 	// Empty bounds don't need expansion.
    223 	if bound.IsEmpty() {
    224 		return bound
    225 	}
    226 
    227 	// First we need to check whether the bound B contains any nearly-antipodal
    228 	// points (to within 4.309 * dblEpsilon). If so then we need to return
    229 	// FullRect, since the subregion might have an edge between two
    230 	// such points, and AddPoint returns Full for such edges. Note that
    231 	// this can happen even if B is not Full for example, consider a loop
    232 	// that defines a 10km strip straddling the equator extending from
    233 	// longitudes -100 to +100 degrees.
    234 	//
    235 	// It is easy to check whether B contains any antipodal points, but checking
    236 	// for nearly-antipodal points is trickier. Essentially we consider the
    237 	// original bound B and its reflection through the origin B', and then test
    238 	// whether the minimum distance between B and B' is less than 4.309 * dblEpsilon.
    239 
    240 	// lngGap is a lower bound on the longitudinal distance between B and its
    241 	// reflection B'. (2.5 * dblEpsilon is the maximum combined error of the
    242 	// endpoint longitude calculations and the Length call.)
    243 	lngGap := math.Max(0, math.Pi-bound.Lng.Length()-2.5*dblEpsilon)
    244 
    245 	// minAbsLat is the minimum distance from B to the equator (if zero or
    246 	// negative, then B straddles the equator).
    247 	minAbsLat := math.Max(bound.Lat.Lo, -bound.Lat.Hi)
    248 
    249 	// latGapSouth and latGapNorth measure the minimum distance from B to the
    250 	// south and north poles respectively.
    251 	latGapSouth := math.Pi/2 + bound.Lat.Lo
    252 	latGapNorth := math.Pi/2 - bound.Lat.Hi
    253 
    254 	if minAbsLat >= 0 {
    255 		// The bound B does not straddle the equator. In this case the minimum
    256 		// distance is between one endpoint of the latitude edge in B closest to
    257 		// the equator and the other endpoint of that edge in B'. The latitude
    258 		// distance between these two points is 2*minAbsLat, and the longitude
    259 		// distance is lngGap. We could compute the distance exactly using the
    260 		// Haversine formula, but then we would need to bound the errors in that
    261 		// calculation. Since we only need accuracy when the distance is very
    262 		// small (close to 4.309 * dblEpsilon), we substitute the Euclidean
    263 		// distance instead. This gives us a right triangle XYZ with two edges of
    264 		// length x = 2*minAbsLat and y ~= lngGap. The desired distance is the
    265 		// length of the third edge z, and we have
    266 		//
    267 		//         z  ~=  sqrt(x^2 + y^2)  >=  (x + y) / sqrt(2)
    268 		//
    269 		// Therefore the region may contain nearly antipodal points only if
    270 		//
    271 		//  2*minAbsLat + lngGap  <  sqrt(2) * 4.309 * dblEpsilon
    272 		//                        ~= 1.354e-15
    273 		//
    274 		// Note that because the given bound B is conservative, minAbsLat and
    275 		// lngGap are both lower bounds on their true values so we do not need
    276 		// to make any adjustments for their errors.
    277 		if 2*minAbsLat+lngGap < 1.354e-15 {
    278 			return FullRect()
    279 		}
    280 	} else if lngGap >= math.Pi/2 {
    281 		// B spans at most Pi/2 in longitude. The minimum distance is always
    282 		// between one corner of B and the diagonally opposite corner of B'. We
    283 		// use the same distance approximation that we used above; in this case
    284 		// we have an obtuse triangle XYZ with two edges of length x = latGapSouth
    285 		// and y = latGapNorth, and angle Z >= Pi/2 between them. We then have
    286 		//
    287 		//         z  >=  sqrt(x^2 + y^2)  >=  (x + y) / sqrt(2)
    288 		//
    289 		// Unlike the case above, latGapSouth and latGapNorth are not lower bounds
    290 		// (because of the extra addition operation, and because math.Pi/2 is not
    291 		// exactly equal to Pi/2); they can exceed their true values by up to
    292 		// 0.75 * dblEpsilon. Putting this all together, the region may contain
    293 		// nearly antipodal points only if
    294 		//
    295 		//   latGapSouth + latGapNorth  <  (sqrt(2) * 4.309 + 1.5) * dblEpsilon
    296 		//                              ~= 1.687e-15
    297 		if latGapSouth+latGapNorth < 1.687e-15 {
    298 			return FullRect()
    299 		}
    300 	} else {
    301 		// Otherwise we know that (1) the bound straddles the equator and (2) its
    302 		// width in longitude is at least Pi/2. In this case the minimum
    303 		// distance can occur either between a corner of B and the diagonally
    304 		// opposite corner of B' (as in the case above), or between a corner of B
    305 		// and the opposite longitudinal edge reflected in B'. It is sufficient
    306 		// to only consider the corner-edge case, since this distance is also a
    307 		// lower bound on the corner-corner distance when that case applies.
    308 
    309 		// Consider the spherical triangle XYZ where X is a corner of B with
    310 		// minimum absolute latitude, Y is the closest pole to X, and Z is the
    311 		// point closest to X on the opposite longitudinal edge of B'. This is a
    312 		// right triangle (Z = Pi/2), and from the spherical law of sines we have
    313 		//
    314 		//     sin(z) / sin(Z)  =  sin(y) / sin(Y)
    315 		//     sin(maxLatGap) / 1  =  sin(dMin) / sin(lngGap)
    316 		//     sin(dMin)  =  sin(maxLatGap) * sin(lngGap)
    317 		//
    318 		// where "maxLatGap" = max(latGapSouth, latGapNorth) and "dMin" is the
    319 		// desired minimum distance. Now using the facts that sin(t) >= (2/Pi)*t
    320 		// for 0 <= t <= Pi/2, that we only need an accurate approximation when
    321 		// at least one of "maxLatGap" or lngGap is extremely small (in which
    322 		// case sin(t) ~= t), and recalling that "maxLatGap" has an error of up
    323 		// to 0.75 * dblEpsilon, we want to test whether
    324 		//
    325 		//   maxLatGap * lngGap  <  (4.309 + 0.75) * (Pi/2) * dblEpsilon
    326 		//                       ~= 1.765e-15
    327 		if math.Max(latGapSouth, latGapNorth)*lngGap < 1.765e-15 {
    328 			return FullRect()
    329 		}
    330 	}
    331 	// Next we need to check whether the subregion might contain any edges that
    332 	// span (math.Pi - 2 * dblEpsilon) radians or more in longitude, since AddPoint
    333 	// sets the longitude bound to Full in that case. This corresponds to
    334 	// testing whether (lngGap <= 0) in lngExpansion below.
    335 
    336 	// Otherwise, the maximum latitude error in AddPoint is 4.8 * dblEpsilon.
    337 	// In the worst case, the errors when computing the latitude bound for a
    338 	// subregion could go in the opposite direction as the errors when computing
    339 	// the bound for the original region, so we need to double this value.
    340 	// (More analysis shows that it's okay to round down to a multiple of
    341 	// dblEpsilon.)
    342 	//
    343 	// For longitude, we rely on the fact that atan2 is correctly rounded and
    344 	// therefore no additional bounds expansion is necessary.
    345 
    346 	latExpansion := 9 * dblEpsilon
    347 	lngExpansion := 0.0
    348 	if lngGap <= 0 {
    349 		lngExpansion = math.Pi
    350 	}
    351 	return bound.expanded(LatLng{s1.Angle(latExpansion), s1.Angle(lngExpansion)}).PolarClosure()
    352 }