...

Source file src/github.com/golang/geo/s2/convex_hull_query.go

Documentation: github.com/golang/geo/s2

     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  }
   259  

View as plain text