1 /* 2 * Copyright 2005 Google Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.google.common.geometry; 17 18 import com.google.common.base.Preconditions; 19 20 import java.util.ArrayList; 21 import java.util.Comparator; 22 import java.util.HashSet; 23 import java.util.PriorityQueue; 24 25 /** 26 * An S2RegionCoverer is a class that allows arbitrary regions to be 27 * approximated as unions of cells (S2CellUnion). This is useful for 28 * implementing various sorts of search and precomputation operations. 29 * 30 * Typical usage: {@code S2RegionCoverer coverer; coverer.setMaxCells(5); S2Cap 31 * cap = S2Cap.fromAxisAngle(...); S2CellUnion covering; 32 * coverer.getCovering(cap, covering); * } 33 * 34 * This yields a cell union of at most 5 cells that is guaranteed to cover the 35 * given cap (a disc-shaped region on the sphere). 36 * 37 * The approximation algorithm is not optimal but does a pretty good job in 38 * practice. The output does not always use the maximum number of cells allowed, 39 * both because this would not always yield a better approximation, and because 40 * max_cells() is a limit on how much work is done exploring the possible 41 * covering as well as a limit on the final output size. 42 * 43 * One can also generate interior coverings, which are sets of cells which are 44 * entirely contained within a region. Interior coverings can be empty, even for 45 * non-empty regions, if there are no cells that satisfy the provided 46 * constraints and are contained by the region. Note that for performance 47 * reasons, it is wise to specify a max_level when computing interior coverings 48 * - otherwise for regions with small or zero area, the algorithm may spend a 49 * lot of time subdividing cells all the way to leaf level to try to find 50 * contained cells. 51 * 52 * This class is thread-unsafe. Simultaneous calls to any of the getCovering 53 * methods will conflict and produce unpredictable results. 54 * 55 */ 56 public final strictfp class S2RegionCoverer { 57 58 /** 59 * By default, the covering uses at most 8 cells at any level. This gives a 60 * reasonable tradeoff between the number of cells used and the accuracy of 61 * the approximation (see table below). 62 */ 63 public static final int DEFAULT_MAX_CELLS = 8; 64 65 private static final S2Cell[] FACE_CELLS = new S2Cell[6]; 66 static { 67 for (int face = 0; face < 6; ++face) { 68 FACE_CELLS[face] = S2Cell.fromFacePosLevel(face, (byte) 0, 0); 69 } 70 } 71 72 73 private int minLevel; 74 private int maxLevel; 75 private int levelMod; 76 private int maxCells; 77 78 // True if we're computing an interior covering. 79 private boolean interiorCovering; 80 81 // Counter of number of candidates created, for performance evaluation. 82 private int candidatesCreatedCounter; 83 84 /** 85 * We save a temporary copy of the pointer passed to GetCovering() in order to 86 * avoid passing this parameter around internally. It is only used (and only 87 * valid) for the duration of a single GetCovering() call. 88 */ 89 S2Region region; 90 91 /** 92 * A temporary variable used by GetCovering() that holds the cell ids that 93 * have been added to the covering so far. 94 */ 95 ArrayList<S2CellId> result; 96 97 98 static class Candidate { 99 private S2Cell cell; 100 private boolean isTerminal; // Cell should not be expanded further. 101 private int numChildren; // Number of children that intersect the region. 102 private Candidate[] children; // Actual size may be 0, 4, 16, or 64 103 // elements. 104 } 105 106 static class QueueEntry { 107 private int id; 108 private Candidate candidate; 109 QueueEntry(int id, Candidate candidate)110 public QueueEntry(int id, Candidate candidate) { 111 this.id = id; 112 this.candidate = candidate; 113 } 114 } 115 116 /** 117 * We define our own comparison function on QueueEntries in order to make the 118 * results deterministic. Using the default less<QueueEntry>, entries of equal 119 * priority would be sorted according to the memory address of the candidate. 120 */ 121 static class QueueEntriesComparator implements Comparator<QueueEntry> { 122 @Override compare(S2RegionCoverer.QueueEntry x, S2RegionCoverer.QueueEntry y)123 public int compare(S2RegionCoverer.QueueEntry x, S2RegionCoverer.QueueEntry y) { 124 return x.id < y.id ? 1 : (x.id > y.id ? -1 : 0); 125 } 126 } 127 128 129 /** 130 * We keep the candidates in a priority queue. We specify a vector to hold the 131 * queue entries since for some reason priority_queue<> uses a deque by 132 * default. 133 */ 134 private PriorityQueue<QueueEntry> candidateQueue; 135 136 /** 137 * Default constructor, sets all fields to default values. 138 */ S2RegionCoverer()139 public S2RegionCoverer() { 140 minLevel = 0; 141 maxLevel = S2CellId.MAX_LEVEL; 142 levelMod = 1; 143 maxCells = DEFAULT_MAX_CELLS; 144 this.region = null; 145 result = new ArrayList<S2CellId>(); 146 // TODO(kirilll?): 10 is a completely random number, work out a better 147 // estimate 148 candidateQueue = new PriorityQueue<QueueEntry>(10, new QueueEntriesComparator()); 149 } 150 151 // Set the minimum and maximum cell level to be used. The default is to use 152 // all cell levels. Requires: max_level() >= min_level(). 153 // 154 // To find the cell level corresponding to a given physical distance, use 155 // the S2Cell metrics defined in s2.h. For example, to find the cell 156 // level that corresponds to an average edge length of 10km, use: 157 // 158 // int level = S2::kAvgEdge.GetClosestLevel( 159 // geostore::S2Earth::KmToRadians(length_km)); 160 // 161 // Note: min_level() takes priority over max_cells(), i.e. cells below the 162 // given level will never be used even if this causes a large number of 163 // cells to be returned. 164 165 /** 166 * Sets the minimum level to be used. 167 */ setMinLevel(int minLevel)168 public void setMinLevel(int minLevel) { 169 // assert (minLevel >= 0 && minLevel <= S2CellId.MAX_LEVEL); 170 this.minLevel = Math.max(0, Math.min(S2CellId.MAX_LEVEL, minLevel)); 171 } 172 173 /** 174 * Sets the maximum level to be used. 175 */ setMaxLevel(int maxLevel)176 public void setMaxLevel(int maxLevel) { 177 // assert (maxLevel >= 0 && maxLevel <= S2CellId.MAX_LEVEL); 178 this.maxLevel = Math.max(0, Math.min(S2CellId.MAX_LEVEL, maxLevel)); 179 } 180 minLevel()181 public int minLevel() { 182 return minLevel; 183 } 184 maxLevel()185 public int maxLevel() { 186 return maxLevel; 187 } 188 maxCells()189 public int maxCells() { 190 return maxCells; 191 } 192 193 /** 194 * If specified, then only cells where (level - min_level) is a multiple of 195 * "level_mod" will be used (default 1). This effectively allows the branching 196 * factor of the S2CellId hierarchy to be increased. Currently the only 197 * parameter values allowed are 1, 2, or 3, corresponding to branching factors 198 * of 4, 16, and 64 respectively. 199 */ setLevelMod(int levelMod)200 public void setLevelMod(int levelMod) { 201 // assert (levelMod >= 1 && levelMod <= 3); 202 this.levelMod = Math.max(1, Math.min(3, levelMod)); 203 } 204 levelMod()205 public int levelMod() { 206 return levelMod; 207 } 208 209 210 /** 211 * Sets the maximum desired number of cells in the approximation (defaults to 212 * kDefaultMaxCells). Note the following: 213 * 214 * <ul> 215 * <li>For any setting of max_cells(), up to 6 cells may be returned if that 216 * is the minimum number of cells required (e.g. if the region intersects all 217 * six face cells). Up to 3 cells may be returned even for very tiny convex 218 * regions if they happen to be located at the intersection of three cube 219 * faces. 220 * 221 * <li>For any setting of max_cells(), an arbitrary number of cells may be 222 * returned if min_level() is too high for the region being approximated. 223 * 224 * <li>If max_cells() is less than 4, the area of the covering may be 225 * arbitrarily large compared to the area of the original region even if the 226 * region is convex (e.g. an S2Cap or S2LatLngRect). 227 * </ul> 228 * 229 * Accuracy is measured by dividing the area of the covering by the area of 230 * the original region. The following table shows the median and worst case 231 * values for this area ratio on a test case consisting of 100,000 spherical 232 * caps of random size (generated using s2regioncoverer_unittest): 233 * 234 * <pre> 235 * max_cells: 3 4 5 6 8 12 20 100 1000 236 * median ratio: 5.33 3.32 2.73 2.34 1.98 1.66 1.42 1.11 1.01 237 * worst case: 215518 14.41 9.72 5.26 3.91 2.75 1.92 1.20 1.02 238 * </pre> 239 */ setMaxCells(int maxCells)240 public void setMaxCells(int maxCells) { 241 this.maxCells = maxCells; 242 } 243 244 /** 245 * Computes a list of cell ids that covers the given region and satisfies the 246 * various restrictions specified above. 247 * 248 * @param region The region to cover 249 * @param covering The list filled in by this method 250 */ getCovering(S2Region region, ArrayList<S2CellId> covering)251 public void getCovering(S2Region region, ArrayList<S2CellId> covering) { 252 // Rather than just returning the raw list of cell ids generated by 253 // GetCoveringInternal(), we construct a cell union and then denormalize it. 254 // This has the effect of replacing four child cells with their parent 255 // whenever this does not violate the covering parameters specified 256 // (min_level, level_mod, etc). This strategy significantly reduces the 257 // number of cells returned in many cases, and it is cheap compared to 258 // computing the covering in the first place. 259 260 S2CellUnion tmp = getCovering(region); 261 tmp.denormalize(minLevel(), levelMod(), covering); 262 } 263 264 /** 265 * Computes a list of cell ids that is contained within the given region and 266 * satisfies the various restrictions specified above. 267 * 268 * @param region The region to fill 269 * @param interior The list filled in by this method 270 */ getInteriorCovering(S2Region region, ArrayList<S2CellId> interior)271 public void getInteriorCovering(S2Region region, ArrayList<S2CellId> interior) { 272 S2CellUnion tmp = getInteriorCovering(region); 273 tmp.denormalize(minLevel(), levelMod(), interior); 274 } 275 276 /** 277 * Return a normalized cell union that covers the given region and satisfies 278 * the restrictions *EXCEPT* for min_level() and level_mod(). These criteria 279 * cannot be satisfied using a cell union because cell unions are 280 * automatically normalized by replacing four child cells with their parent 281 * whenever possible. (Note that the list of cell ids passed to the cell union 282 * constructor does in fact satisfy all the given restrictions.) 283 */ getCovering(S2Region region)284 public S2CellUnion getCovering(S2Region region) { 285 S2CellUnion covering = new S2CellUnion(); 286 getCovering(region, covering); 287 return covering; 288 } 289 getCovering(S2Region region, S2CellUnion covering)290 public void getCovering(S2Region region, S2CellUnion covering) { 291 interiorCovering = false; 292 getCoveringInternal(region); 293 covering.initSwap(result); 294 } 295 296 /** 297 * Return a normalized cell union that is contained within the given region 298 * and satisfies the restrictions *EXCEPT* for min_level() and level_mod(). 299 */ getInteriorCovering(S2Region region)300 public S2CellUnion getInteriorCovering(S2Region region) { 301 S2CellUnion covering = new S2CellUnion(); 302 getInteriorCovering(region, covering); 303 return covering; 304 } 305 getInteriorCovering(S2Region region, S2CellUnion covering)306 public void getInteriorCovering(S2Region region, S2CellUnion covering) { 307 interiorCovering = true; 308 getCoveringInternal(region); 309 covering.initSwap(result); 310 } 311 312 /** 313 * Given a connected region and a starting point, return a set of cells at the 314 * given level that cover the region. 315 */ getSimpleCovering( S2Region region, S2Point start, int level, ArrayList<S2CellId> output)316 public static void getSimpleCovering( 317 S2Region region, S2Point start, int level, ArrayList<S2CellId> output) { 318 floodFill(region, S2CellId.fromPoint(start).parent(level), output); 319 } 320 321 /** 322 * If the cell intersects the given region, return a new candidate with no 323 * children, otherwise return null. Also marks the candidate as "terminal" if 324 * it should not be expanded further. 325 */ newCandidate(S2Cell cell)326 private Candidate newCandidate(S2Cell cell) { 327 if (!region.mayIntersect(cell)) { 328 return null; 329 } 330 331 boolean isTerminal = false; 332 if (cell.level() >= minLevel) { 333 if (interiorCovering) { 334 if (region.contains(cell)) { 335 isTerminal = true; 336 } else if (cell.level() + levelMod > maxLevel) { 337 return null; 338 } 339 } else { 340 if (cell.level() + levelMod > maxLevel || region.contains(cell)) { 341 isTerminal = true; 342 } 343 } 344 } 345 Candidate candidate = new Candidate(); 346 candidate.cell = cell; 347 candidate.isTerminal = isTerminal; 348 if (!isTerminal) { 349 candidate.children = new Candidate[1 << maxChildrenShift()]; 350 } 351 candidatesCreatedCounter++; 352 return candidate; 353 } 354 355 /** Return the log base 2 of the maximum number of children of a candidate. */ maxChildrenShift()356 private int maxChildrenShift() { 357 return 2 * levelMod; 358 } 359 360 /** 361 * Process a candidate by either adding it to the result list or expanding its 362 * children and inserting it into the priority queue. Passing an argument of 363 * NULL does nothing. 364 */ addCandidate(Candidate candidate)365 private void addCandidate(Candidate candidate) { 366 if (candidate == null) { 367 return; 368 } 369 370 if (candidate.isTerminal) { 371 result.add(candidate.cell.id()); 372 return; 373 } 374 // assert (candidate.numChildren == 0); 375 376 // Expand one level at a time until we hit min_level_ to ensure that 377 // we don't skip over it. 378 int numLevels = (candidate.cell.level() < minLevel) ? 1 : levelMod; 379 int numTerminals = expandChildren(candidate, candidate.cell, numLevels); 380 381 if (candidate.numChildren == 0) { 382 // Do nothing 383 } else if (!interiorCovering && numTerminals == 1 << maxChildrenShift() 384 && candidate.cell.level() >= minLevel) { 385 // Optimization: add the parent cell rather than all of its children. 386 // We can't do this for interior coverings, since the children just 387 // intersect the region, but may not be contained by it - we need to 388 // subdivide them further. 389 candidate.isTerminal = true; 390 addCandidate(candidate); 391 392 } else { 393 // We negate the priority so that smaller absolute priorities are returned 394 // first. The heuristic is designed to refine the largest cells first, 395 // since those are where we have the largest potential gain. Among cells 396 // at the same level, we prefer the cells with the smallest number of 397 // intersecting children. Finally, we prefer cells that have the smallest 398 // number of children that cannot be refined any further. 399 int priority = -((((candidate.cell.level() << maxChildrenShift()) + candidate.numChildren) 400 << maxChildrenShift()) + numTerminals); 401 candidateQueue.add(new QueueEntry(priority, candidate)); 402 // logger.info("Push: " + candidate.cell.id() + " (" + priority + ") "); 403 } 404 } 405 406 /** 407 * Populate the children of "candidate" by expanding the given number of 408 * levels from the given cell. Returns the number of children that were marked 409 * "terminal". 410 */ expandChildren(Candidate candidate, S2Cell cell, int numLevels)411 private int expandChildren(Candidate candidate, S2Cell cell, int numLevels) { 412 numLevels--; 413 S2Cell[] childCells = new S2Cell[4]; 414 for (int i = 0; i < 4; ++i) { 415 childCells[i] = new S2Cell(); 416 } 417 cell.subdivide(childCells); 418 int numTerminals = 0; 419 for (int i = 0; i < 4; ++i) { 420 if (numLevels > 0) { 421 if (region.mayIntersect(childCells[i])) { 422 numTerminals += expandChildren(candidate, childCells[i], numLevels); 423 } 424 continue; 425 } 426 Candidate child = newCandidate(childCells[i]); 427 if (child != null) { 428 candidate.children[candidate.numChildren++] = child; 429 if (child.isTerminal) { 430 ++numTerminals; 431 } 432 } 433 } 434 return numTerminals; 435 } 436 437 /** Computes a set of initial candidates that cover the given region. */ getInitialCandidates()438 private void getInitialCandidates() { 439 // Optimization: if at least 4 cells are desired (the normal case), 440 // start with a 4-cell covering of the region's bounding cap. This 441 // lets us skip quite a few levels of refinement when the region to 442 // be covered is relatively small. 443 if (maxCells >= 4) { 444 // Find the maximum level such that the bounding cap contains at most one 445 // cell vertex at that level. 446 S2Cap cap = region.getCapBound(); 447 int level = Math.min(S2Projections.MIN_WIDTH.getMaxLevel(2 * cap.angle().radians()), 448 Math.min(maxLevel(), S2CellId.MAX_LEVEL - 1)); 449 if (levelMod() > 1 && level > minLevel()) { 450 level -= (level - minLevel()) % levelMod(); 451 } 452 // We don't bother trying to optimize the level == 0 case, since more than 453 // four face cells may be required. 454 if (level > 0) { 455 // Find the leaf cell containing the cap axis, and determine which 456 // subcell of the parent cell contains it. 457 ArrayList<S2CellId> base = new ArrayList<S2CellId>(4); 458 S2CellId id = S2CellId.fromPoint(cap.axis()); 459 id.getVertexNeighbors(level, base); 460 for (int i = 0; i < base.size(); ++i) { 461 addCandidate(newCandidate(new S2Cell(base.get(i)))); 462 } 463 return; 464 } 465 } 466 // Default: start with all six cube faces. 467 for (int face = 0; face < 6; ++face) { 468 addCandidate(newCandidate(FACE_CELLS[face])); 469 } 470 } 471 472 /** Generates a covering and stores it in result. */ getCoveringInternal(S2Region region)473 private void getCoveringInternal(S2Region region) { 474 // Strategy: Start with the 6 faces of the cube. Discard any 475 // that do not intersect the shape. Then repeatedly choose the 476 // largest cell that intersects the shape and subdivide it. 477 // 478 // result contains the cells that will be part of the output, while the 479 // priority queue contains cells that we may still subdivide further. Cells 480 // that are entirely contained within the region are immediately added to 481 // the output, while cells that do not intersect the region are immediately 482 // discarded. 483 // Therefore pq_ only contains cells that partially intersect the region. 484 // Candidates are prioritized first according to cell size (larger cells 485 // first), then by the number of intersecting children they have (fewest 486 // children first), and then by the number of fully contained children 487 // (fewest children first). 488 489 Preconditions.checkState(candidateQueue.isEmpty() && result.isEmpty()); 490 491 this.region = region; 492 candidatesCreatedCounter = 0; 493 494 getInitialCandidates(); 495 while (!candidateQueue.isEmpty() && (!interiorCovering || result.size() < maxCells)) { 496 Candidate candidate = candidateQueue.poll().candidate; 497 // logger.info("Pop: " + candidate.cell.id()); 498 if (candidate.cell.level() < minLevel || candidate.numChildren == 1 499 || result.size() + (interiorCovering ? 0 : candidateQueue.size()) + candidate.numChildren 500 <= maxCells) { 501 // Expand this candidate into its children. 502 for (int i = 0; i < candidate.numChildren; ++i) { 503 addCandidate(candidate.children[i]); 504 } 505 } else if (interiorCovering) { 506 // Do nothing 507 } else { 508 candidate.isTerminal = true; 509 addCandidate(candidate); 510 } 511 } 512 513 candidateQueue.clear(); 514 this.region = null; 515 } 516 517 /** 518 * Given a region and a starting cell, return the set of all the 519 * edge-connected cells at the same level that intersect "region". The output 520 * cells are returned in arbitrary order. 521 */ floodFill(S2Region region, S2CellId start, ArrayList<S2CellId> output)522 private static void floodFill(S2Region region, S2CellId start, ArrayList<S2CellId> output) { 523 HashSet<S2CellId> all = new HashSet<S2CellId>(); 524 ArrayList<S2CellId> frontier = new ArrayList<S2CellId>(); 525 output.clear(); 526 all.add(start); 527 frontier.add(start); 528 while (!frontier.isEmpty()) { 529 S2CellId id = frontier.get(frontier.size() - 1); 530 frontier.remove(frontier.size() - 1); 531 if (!region.mayIntersect(new S2Cell(id))) { 532 continue; 533 } 534 output.add(id); 535 536 S2CellId[] neighbors = new S2CellId[4]; 537 id.getEdgeNeighbors(neighbors); 538 for (int edge = 0; edge < 4; ++edge) { 539 S2CellId nbr = neighbors[edge]; 540 boolean hasNbr = all.contains(nbr); 541 if (!all.contains(nbr)) { 542 frontier.add(nbr); 543 all.add(nbr); 544 } 545 } 546 } 547 } 548 } 549