gtsocial-umbx

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

convex_hull_query.go (9900B)


      1 // Copyright 2018 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 	"sort"
     19 
     20 	"github.com/golang/geo/r3"
     21 )
     22 
     23 // ConvexHullQuery builds the convex hull of any collection of points,
     24 // polylines, loops, and polygons. It returns a single convex loop.
     25 //
     26 // The convex hull is defined as the smallest convex region on the sphere that
     27 // contains all of your input geometry. Recall that a region is "convex" if
     28 // for every pair of points inside the region, the straight edge between them
     29 // is also inside the region. In our case, a "straight" edge is a geodesic,
     30 // i.e. the shortest path on the sphere between two points.
     31 //
     32 // Containment of input geometry is defined as follows:
     33 //
     34 //  - Each input loop and polygon is contained by the convex hull exactly
     35 //    (i.e., according to Polygon's Contains(Polygon)).
     36 //
     37 //  - Each input point is either contained by the convex hull or is a vertex
     38 //    of the convex hull. (Recall that S2Loops do not necessarily contain their
     39 //    vertices.)
     40 //
     41 //  - For each input polyline, the convex hull contains all of its vertices
     42 //    according to the rule for points above. (The definition of convexity
     43 //    then ensures that the convex hull also contains the polyline edges.)
     44 //
     45 // To use this type, call the various Add... methods to add your input geometry, and
     46 // then call ConvexHull. Note that ConvexHull does *not* reset the
     47 // state; you can continue adding geometry if desired and compute the convex
     48 // hull again. If you want to start from scratch, simply create a new
     49 // ConvexHullQuery value.
     50 //
     51 // This implement Andrew's monotone chain algorithm, which is a variant of the
     52 // Graham scan (see https://en.wikipedia.org/wiki/Graham_scan). The time
     53 // complexity is O(n log n), and the space required is O(n). In fact only the
     54 // call to "sort" takes O(n log n) time; the rest of the algorithm is linear.
     55 //
     56 // Demonstration of the algorithm and code:
     57 // en.wikibooks.org/wiki/Algorithm_Implementation/Geometry/Convex_hull/Monotone_chain
     58 //
     59 // This type is not safe for concurrent use.
     60 type ConvexHullQuery struct {
     61 	bound  Rect
     62 	points []Point
     63 }
     64 
     65 // NewConvexHullQuery creates a new ConvexHullQuery.
     66 func NewConvexHullQuery() *ConvexHullQuery {
     67 	return &ConvexHullQuery{
     68 		bound: EmptyRect(),
     69 	}
     70 }
     71 
     72 // AddPoint adds the given point to the input geometry.
     73 func (q *ConvexHullQuery) AddPoint(p Point) {
     74 	q.bound = q.bound.AddPoint(LatLngFromPoint(p))
     75 	q.points = append(q.points, p)
     76 }
     77 
     78 // AddPolyline adds the given polyline to the input geometry.
     79 func (q *ConvexHullQuery) AddPolyline(p *Polyline) {
     80 	q.bound = q.bound.Union(p.RectBound())
     81 	q.points = append(q.points, (*p)...)
     82 }
     83 
     84 // AddLoop adds the given loop to the input geometry.
     85 func (q *ConvexHullQuery) AddLoop(l *Loop) {
     86 	q.bound = q.bound.Union(l.RectBound())
     87 	if l.isEmptyOrFull() {
     88 		return
     89 	}
     90 	q.points = append(q.points, l.vertices...)
     91 }
     92 
     93 // AddPolygon adds the given polygon to the input geometry.
     94 func (q *ConvexHullQuery) AddPolygon(p *Polygon) {
     95 	q.bound = q.bound.Union(p.RectBound())
     96 	for _, l := range p.loops {
     97 		// Only loops at depth 0 can contribute to the convex hull.
     98 		if l.depth == 0 {
     99 			q.AddLoop(l)
    100 		}
    101 	}
    102 }
    103 
    104 // CapBound returns a bounding cap for the input geometry provided.
    105 //
    106 // Note that this method does not clear the geometry; you can continue
    107 // adding to it and call this method again if desired.
    108 func (q *ConvexHullQuery) CapBound() Cap {
    109 	// We keep track of a rectangular bound rather than a spherical cap because
    110 	// it is easy to compute a tight bound for a union of rectangles, whereas it
    111 	// is quite difficult to compute a tight bound around a union of caps.
    112 	// Also, polygons and polylines implement CapBound() in terms of
    113 	// RectBound() for this same reason, so it is much better to keep track
    114 	// of a rectangular bound as we go along and convert it at the end.
    115 	//
    116 	// TODO(roberts): We could compute an optimal bound by implementing Welzl's
    117 	// algorithm. However we would still need to have special handling of loops
    118 	// and polygons, since if a loop spans more than 180 degrees in any
    119 	// direction (i.e., if it contains two antipodal points), then it is not
    120 	// enough just to bound its vertices. In this case the only convex bounding
    121 	// cap is FullCap(), and the only convex bounding loop is the full loop.
    122 	return q.bound.CapBound()
    123 }
    124 
    125 // ConvexHull returns a Loop representing the convex hull of the input geometry provided.
    126 //
    127 // If there is no geometry, this method returns an empty loop containing no
    128 // points.
    129 //
    130 // If the geometry spans more than half of the sphere, this method returns a
    131 // full loop containing the entire sphere.
    132 //
    133 // If the geometry contains 1 or 2 points, or a single edge, this method
    134 // returns a very small loop consisting of three vertices (which are a
    135 // superset of the input vertices).
    136 //
    137 // Note that this method does not clear the geometry; you can continue
    138 // adding to the query and call this method again.
    139 func (q *ConvexHullQuery) ConvexHull() *Loop {
    140 	c := q.CapBound()
    141 	if c.Height() >= 1 {
    142 		// The bounding cap is not convex. The current bounding cap
    143 		// implementation is not optimal, but nevertheless it is likely that the
    144 		// input geometry itself is not contained by any convex polygon. In any
    145 		// case, we need a convex bounding cap to proceed with the algorithm below
    146 		// (in order to construct a point "origin" that is definitely outside the
    147 		// convex hull).
    148 		return FullLoop()
    149 	}
    150 
    151 	// Remove duplicates. We need to do this before checking whether there are
    152 	// fewer than 3 points.
    153 	x := make(map[Point]bool)
    154 	r, w := 0, 0 // read/write indexes
    155 	for ; r < len(q.points); r++ {
    156 		if x[q.points[r]] {
    157 			continue
    158 		}
    159 		q.points[w] = q.points[r]
    160 		x[q.points[r]] = true
    161 		w++
    162 	}
    163 	q.points = q.points[:w]
    164 
    165 	// This code implements Andrew's monotone chain algorithm, which is a simple
    166 	// variant of the Graham scan. Rather than sorting by x-coordinate, instead
    167 	// we sort the points in CCW order around an origin O such that all points
    168 	// are guaranteed to be on one side of some geodesic through O. This
    169 	// ensures that as we scan through the points, each new point can only
    170 	// belong at the end of the chain (i.e., the chain is monotone in terms of
    171 	// the angle around O from the starting point).
    172 	origin := Point{c.Center().Ortho()}
    173 	sort.Slice(q.points, func(i, j int) bool {
    174 		return RobustSign(origin, q.points[i], q.points[j]) == CounterClockwise
    175 	})
    176 
    177 	// Special cases for fewer than 3 points.
    178 	switch len(q.points) {
    179 	case 0:
    180 		return EmptyLoop()
    181 	case 1:
    182 		return singlePointLoop(q.points[0])
    183 	case 2:
    184 		return singleEdgeLoop(q.points[0], q.points[1])
    185 	}
    186 
    187 	// Generate the lower and upper halves of the convex hull. Each half
    188 	// consists of the maximal subset of vertices such that the edge chain
    189 	// makes only left (CCW) turns.
    190 	lower := q.monotoneChain()
    191 
    192 	// reverse the points
    193 	for left, right := 0, len(q.points)-1; left < right; left, right = left+1, right-1 {
    194 		q.points[left], q.points[right] = q.points[right], q.points[left]
    195 	}
    196 	upper := q.monotoneChain()
    197 
    198 	// Remove the duplicate vertices and combine the chains.
    199 	lower = lower[:len(lower)-1]
    200 	upper = upper[:len(upper)-1]
    201 	lower = append(lower, upper...)
    202 
    203 	return LoopFromPoints(lower)
    204 }
    205 
    206 // monotoneChain iterates through the points, selecting the maximal subset of points
    207 // such that the edge chain makes only left (CCW) turns.
    208 func (q *ConvexHullQuery) monotoneChain() []Point {
    209 	var output []Point
    210 	for _, p := range q.points {
    211 		// Remove any points that would cause the chain to make a clockwise turn.
    212 		for len(output) >= 2 && RobustSign(output[len(output)-2], output[len(output)-1], p) != CounterClockwise {
    213 			output = output[:len(output)-1]
    214 		}
    215 		output = append(output, p)
    216 	}
    217 	return output
    218 }
    219 
    220 // singlePointLoop constructs a 3-vertex polygon consisting of "p" and two nearby
    221 // vertices. Note that ContainsPoint(p) may be false for the resulting loop.
    222 func singlePointLoop(p Point) *Loop {
    223 	const offset = 1e-15
    224 	d0 := p.Ortho()
    225 	d1 := p.Cross(d0)
    226 	vertices := []Point{
    227 		p,
    228 		{p.Add(d0.Mul(offset)).Normalize()},
    229 		{p.Add(d1.Mul(offset)).Normalize()},
    230 	}
    231 	return LoopFromPoints(vertices)
    232 }
    233 
    234 // singleEdgeLoop constructs a loop consisting of the two vertices and their midpoint.
    235 func singleEdgeLoop(a, b Point) *Loop {
    236 	// If the points are exactly antipodal we return the full loop.
    237 	//
    238 	// Note that we could use the code below even in this case (which would
    239 	// return a zero-area loop that follows the edge AB), except that (1) the
    240 	// direction of AB is defined using symbolic perturbations and therefore is
    241 	// not predictable by ordinary users, and (2) Loop disallows anitpodal
    242 	// adjacent vertices and so we would need to use 4 vertices to define the
    243 	// degenerate loop. (Note that the Loop antipodal vertex restriction is
    244 	// historical and now could easily be removed, however it would still have
    245 	// the problem that the edge direction is not easily predictable.)
    246 	if a.Add(b.Vector) == (r3.Vector{}) {
    247 		return FullLoop()
    248 	}
    249 
    250 	// Construct a loop consisting of the two vertices and their midpoint.  We
    251 	// use Interpolate() to ensure that the midpoint is very close to
    252 	// the edge even when its endpoints nearly antipodal.
    253 	vertices := []Point{a, b, Interpolate(0.5, a, b)}
    254 	loop := LoopFromPoints(vertices)
    255 	// The resulting loop may be clockwise, so invert it if necessary.
    256 	loop.Normalize()
    257 	return loop
    258 }