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 16 // Original Author: ericv@google.com (Eric Veach) 17 // Converted to D: madric@gmail.com (Vijay Nayar) 18 // 19 // This file defines a collection of classes that are useful for computing 20 // minimum distances on the sphere. Their purpose is to allow code to be 21 // shared among the various query classes that find nearby geometry, such as 22 // S2ClosestPointQuery and S2ClosestEdgeQuery. 23 24 module s2.s2min_distance_targets; 25 26 import s2.s1angle; 27 import s2.s1chord_angle; 28 import s2.s2cap; 29 import s2.s2cell; 30 import s2.s2closest_edge_query; 31 import s2.s2contains_point_query; 32 import s2.s2distance_target; 33 import s2.s2edge_distances : updateMinDistance, updateEdgePairMinDistance; 34 import s2.s2point; 35 import s2.s2shape; 36 import s2.s2shape_index; 37 import s2.s2shape_index_region : makeS2ShapeIndexRegion; 38 39 import std.math : sqrt; 40 41 /** 42 * S2MinDistance is a thin wrapper around S1ChordAngle that is used by classes 43 * such as S2ClosestEdgeQuery to compute minimum distances on the sphere (as 44 * opposed to maximum distances, ellipsoidal distances, etc). 45 * 46 * It implements the Distance concept defined by S2DistanceTarget, which is 47 * used by query classes such as S2ClosestPointQuery and S2ClosestEdgeQuery. 48 * (See s2distance_target.h for details.) 49 */ 50 struct S2MinDistance { 51 public: 52 S1ChordAngle s1ChordAngle; 53 54 alias s1ChordAngle this; 55 56 alias Delta = S1ChordAngle; 57 58 static S2MinDistance zero() { 59 return S2MinDistance(S1ChordAngle.zero()); 60 } 61 62 static S2MinDistance infinity() { 63 return S2MinDistance(S1ChordAngle.infinity()); 64 } 65 66 static S2MinDistance negative() { 67 return S2MinDistance(S1ChordAngle.negative()); 68 } 69 70 S1ChordAngle getChordAngleBound() const { 71 return plusError(getS1AngleConstructorMaxError()); 72 } 73 74 // If (dist < *this), updates *this and returns true (used internally). 75 bool updateMin(in S2MinDistance dist) { 76 if (dist < this) { 77 this = dist; 78 return true; 79 } 80 return false; 81 } 82 83 int opCmp(in S2MinDistance other) const { 84 return s1ChordAngle.opCmp(other.s1ChordAngle); 85 } 86 } 87 88 // S2MinDistanceTarget represents a geometric object to which distances are 89 // measured. Specifically, it is used to compute minimum distances on the 90 // sphere (as opposed to maximum distances, ellipsoidal distances, etc). 91 // 92 // Subtypes are defined below for measuring the distance to a point, an edge, 93 // an S2Cell, or an S2ShapeIndex (an arbitrary collection of geometry). 94 alias S2MinDistanceTarget = S2DistanceTarget!S2MinDistance; 95 96 // An S2DistanceTarget subtype for computing the minimum distance to a point. 97 class S2MinDistancePointTarget : S2MinDistanceTarget { 98 public: 99 this(in S2Point point) { 100 _point = point; 101 } 102 103 final override 104 S2Cap getCapBound() { 105 return new S2Cap(_point, S1ChordAngle.zero()); 106 } 107 108 final override 109 bool updateMinDistance(in S2Point p, ref S2MinDistance min_dist) { 110 return min_dist.updateMin(S2MinDistance(S1ChordAngle(p, _point))); 111 } 112 113 final override 114 bool updateMinDistance(in S2Point v0, in S2Point v1, ref S2MinDistance min_dist) { 115 return .updateMinDistance(_point, v0, v1, min_dist); 116 } 117 118 final override 119 bool updateMinDistance(in S2Cell cell, ref S2MinDistance min_dist) { 120 return min_dist.updateMin(S2MinDistance(cell.getDistance(_point))); 121 } 122 123 final override 124 bool visitContainingShapes(S2ShapeIndex index, ShapeVisitor visitor) { 125 return makeS2ContainsPointQuery(index).visitContainingShapes( 126 _point, (in S2Shape shape) { 127 return visitor(shape, _point); 128 }); 129 } 130 131 private: 132 S2Point _point; 133 } 134 135 // An S2DistanceTarget subtype for computing the minimum distance to a edge. 136 class S2MinDistanceEdgeTarget : S2MinDistanceTarget { 137 public: 138 this(in S2Point a, in S2Point b) { 139 _a = a; 140 _b = b; 141 } 142 143 final override 144 S2Cap getCapBound() { 145 // The following computes a radius equal to half the edge length in an 146 // efficient and numerically stable way. 147 double d2 = S1ChordAngle(_a, _b).length2(); 148 double r2 = (0.5 * d2) / (1 + sqrt(1 - 0.25 * d2)); 149 return new S2Cap((_a + _b).normalize(), S1ChordAngle.fromLength2(r2)); 150 } 151 152 final override 153 bool updateMinDistance(in S2Point p, ref S2MinDistance min_dist) { 154 return .updateMinDistance(p, _a, _b, min_dist); 155 } 156 157 final override 158 bool updateMinDistance(in S2Point v0, in S2Point v1, ref S2MinDistance min_dist) { 159 return updateEdgePairMinDistance(_a, _b, v0, v1, min_dist); 160 } 161 162 final override 163 bool updateMinDistance(in S2Cell cell, ref S2MinDistance min_dist) { 164 return min_dist.updateMin(S2MinDistance(cell.getDistance(_a, _b))); 165 } 166 167 final override 168 bool visitContainingShapes(S2ShapeIndex index, ShapeVisitor visitor) { 169 // We test the center of the edge in order to ensure that edge targets AB 170 // and BA yield identical results (which is not guaranteed by the API but 171 // users might expect). Other options would be to test both endpoints, or 172 // return different results for AB and BA in some cases. 173 scope target = new S2MinDistancePointTarget((_a + _b).normalize()); 174 return target.visitContainingShapes(index, visitor); 175 } 176 177 private: 178 S2Point _a; 179 S2Point _b; 180 } 181 182 // An S2DistanceTarget subtype for computing the minimum distance to an S2Cell 183 // (including the interior of the cell). 184 class S2MinDistanceCellTarget : S2MinDistanceTarget { 185 public: 186 this(in S2Cell cell) { 187 _cell = cell; 188 } 189 190 final override 191 S2Cap getCapBound() { 192 return _cell.getCapBound(); 193 } 194 195 final override 196 bool updateMinDistance(in S2Point p, ref S2MinDistance min_dist) { 197 return min_dist.updateMin(S2MinDistance(_cell.getDistance(p))); 198 } 199 200 final override 201 bool updateMinDistance(in S2Point v0, in S2Point v1, ref S2MinDistance min_dist) { 202 return min_dist.updateMin(S2MinDistance(_cell.getDistance(v0, v1))); 203 } 204 205 final override 206 bool updateMinDistance(in S2Cell cell, ref S2MinDistance min_dist) { 207 return min_dist.updateMin(S2MinDistance(_cell.getDistance(cell))); 208 } 209 210 final override 211 bool visitContainingShapes(S2ShapeIndex index, ShapeVisitor visitor) { 212 // The simplest approach is simply to return the polygons that contain the 213 // cell center. Alternatively, if the index cell is smaller than the target 214 // cell then we could return all polygons that are present in the 215 // S2ShapeIndexCell, but since the index is built conservatively this may 216 // include some polygons that don't quite intersect the cell. So we would 217 // either need to recheck for intersection more accurately, or weaken the 218 // VisitContainingShapes contract so that it only guarantees approximate 219 // intersection, neither of which seems like a good tradeoff. 220 scope target = new S2MinDistancePointTarget(_cell.getCenter()); 221 return target.visitContainingShapes(index, visitor); 222 } 223 224 private: 225 const(S2Cell) _cell; 226 } 227 228 /** 229 * An S2DistanceTarget subtype for computing the minimum distance to an 230 * S2ShapeIndex \(a collection of points, polylines, and/or polygons\). 231 * 232 * Note that ShapeIndexTarget has its own options: 233 * 234 * include_interiors() 235 * - specifies that distances are measured to the boundary and interior 236 * of polygons in the S2ShapeIndex. \(If set to false, distance is 237 * measured to the polygon boundary only.\) 238 * DEFAULT: true. 239 * 240 * brute_force() 241 * - specifies that the distances should be computed by examining every 242 * edge in the S2ShapeIndex \(for testing and debugging purposes\). 243 * DEFAULT: false. 244 * 245 * These options are specified independently of the corresponding 246 * S2ClosestEdgeQuery options. For example, if include_interiors is true for 247 * an ShapeIndexTarget but false for the S2ClosestEdgeQuery where the target 248 * is used, then distances will be measured from the boundary of one 249 * S2ShapeIndex to the boundary and interior of the other. 250 * 251 * Note that when the distance to a ShapeIndexTarget is zero because the 252 * target intersects the interior of the query index, you can find a point 253 * that achieves this zero distance by calling the `visitContainingShapes()` 254 * method directly. For example: 255 * 256 * --- 257 * auto target = new S2ClosestEdgeQuery.ShapeIndexTarget(target_index); 258 * target.visitContainingShapes( 259 * query_index, (S2Shape containing_shape, const S2Point& target_point) { 260 * ... do something with "target_point" ... 261 * return false; // Terminate search 262 * }); 263 * --- 264 */ 265 class S2MinDistanceShapeIndexTarget : S2MinDistanceTarget { 266 public: 267 this(S2ShapeIndex index) { 268 _index = index; 269 _query = new S2ClosestEdgeQuery(index); 270 } 271 272 // Specifies that distance will be measured to the boundary and interior 273 // of polygons in the S2ShapeIndex rather than to polygon boundaries only. 274 // 275 // DEFAULT: true 276 bool includeInteriors() const { 277 return _query.options().includeInteriors(); 278 } 279 280 void setIncludeInteriors(bool include_interiors) { 281 _query.mutableOptions().setIncludeInteriors(include_interiors); 282 } 283 284 // Specifies that the distances should be computed by examining every edge 285 // in the S2ShapeIndex (for testing and debugging purposes). 286 // 287 // DEFAULT: false 288 bool useBruteForce() const { 289 return _query.options().useBruteForce(); 290 } 291 292 void setUseBruteForce(bool use_brute_force) { 293 _query.mutableOptions().setUseBruteForce(use_brute_force); 294 } 295 296 override 297 bool setMaxError(in S1ChordAngle max_error) { 298 _query.mutableOptions().setMaxError(max_error.toS1Angle()); 299 return true; // Indicates that we may return suboptimal results. 300 } 301 302 final override 303 S2Cap getCapBound() { 304 return makeS2ShapeIndexRegion(_index).getCapBound(); 305 } 306 307 final override 308 bool updateMinDistance(in S2Point p, ref S2MinDistance min_dist) { 309 _query.mutableOptions().setMaxDistance(min_dist); 310 auto target = new S2ClosestEdgeQuery.PointTarget(p); 311 S2ClosestEdgeQuery.Result r = _query.findClosestEdge(target); 312 if (r.shapeId < 0) return false; 313 min_dist = r.distance; 314 return true; 315 } 316 317 final override 318 bool updateMinDistance(in S2Point v0, in S2Point v1, ref S2MinDistance min_dist) { 319 _query.mutableOptions().setMaxDistance(min_dist); 320 auto target = new S2ClosestEdgeQuery.EdgeTarget(v0, v1); 321 S2ClosestEdgeQuery.Result r = _query.findClosestEdge(target); 322 if (r.shapeId < 0) return false; 323 min_dist = r.distance; 324 return true; 325 } 326 327 final override 328 bool updateMinDistance(in S2Cell cell, ref S2MinDistance min_dist) { 329 _query.mutableOptions().setMaxDistance(min_dist); 330 auto target = new S2ClosestEdgeQuery.CellTarget(cell); 331 S2ClosestEdgeQuery.Result r = _query.findClosestEdge(target); 332 if (r.shapeId < 0) return false; 333 min_dist = r.distance; 334 return true; 335 } 336 337 final override 338 bool visitContainingShapes(S2ShapeIndex query_index, in ShapeVisitor visitor) { 339 // It is sufficient to find the set of chain starts in the target index 340 // (i.e., one vertex per connected component of edges) that are contained by 341 // the query index, except for one special case to handle full polygons. 342 // 343 // TODO(ericv): Do this by merge-joining the two S2ShapeIndexes, and share 344 // the code with S2BooleanOperation. 345 346 int num_shape_ids = _index.numShapeIds(); 347 for (int s = 0; s < num_shape_ids; ++s) { 348 const(S2Shape) shape = _index.shape(s); 349 if (shape is null) continue; 350 int num_chains = shape.numChains(); 351 // Shapes that don't have any edges require a special case (below). 352 bool tested_point = false; 353 for (int c = 0; c < num_chains; ++c) { 354 S2Shape.Chain chain = shape.chain(c); 355 if (chain.length == 0) continue; 356 tested_point = true; 357 S2Point v0 = shape.chainEdge(c, 0).v0; 358 auto target = new S2MinDistancePointTarget(v0); 359 if (!target.visitContainingShapes(query_index, visitor)) { 360 return false; 361 } 362 } 363 if (!tested_point) { 364 // Special case to handle full polygons. 365 S2Shape.ReferencePoint refPoint = shape.getReferencePoint(); 366 if (!refPoint.contained) continue; 367 auto target = new S2MinDistancePointTarget(refPoint.point); 368 if (!target.visitContainingShapes(query_index, visitor)) { 369 return false; 370 } 371 } 372 } 373 return true; 374 } 375 376 private: 377 S2ShapeIndex _index; 378 S2ClosestEdgeQuery _query; 379 }