1 // Copyright 2016 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 module s2.builder.util.snap_functions; 20 21 import s2.s1angle; 22 import s2.s2builder; 23 import s2.s2builder; 24 import s2.s2cell_id; 25 import s2.s2latlng; 26 import s2.s2metrics; 27 import s2.s2point; 28 29 import std.algorithm; 30 import std.math; 31 32 /** 33 * A SnapFunction that snaps every vertex to itself. It should be used when 34 * vertices do not need to be snapped to a discrete set of locations (such as 35 * E7 lat/lngs), or when maximum accuracy is desired. 36 * 37 * If the given "snap_radius" is zero, then all input vertices are preserved 38 * exactly. Otherwise, S2Builder merges nearby vertices to ensure that no 39 * vertex pair is closer than "snap_radius". Furthermore, vertices are 40 * separated from non-incident edges by at least "min_edge_vertex_separation", 41 * equal to (0.5 * snap_radius). For example, if the snap_radius is 1km, then 42 * vertices will be separated from non-incident edges by at least 500m. 43 */ 44 class IdentitySnapFunction : S2Builder.SnapFunction { 45 public: 46 /// The default constructor uses a snap_radius of zero (i.e., no snapping). 47 this() { 48 _snapRadius = S1Angle.zero(); 49 } 50 51 /// Convenience constructor that calls set_snap_radius(). 52 this(S1Angle snap_radius) { 53 setSnapRadius(snap_radius); 54 } 55 56 this(in IdentitySnapFunction other) { 57 _snapRadius = other._snapRadius; 58 } 59 60 /// REQUIRES: snap_radius <= SnapFunction::kMaxSnapRadius() 61 void setSnapRadius(S1Angle snap_radius) 62 in { 63 assert(snap_radius <= kMaxSnapRadius()); 64 } do { 65 _snapRadius = snap_radius; 66 } 67 68 override 69 S1Angle snapRadius() const { 70 return _snapRadius; 71 } 72 73 /// For the identity snap function, all vertex pairs are separated by at 74 /// least snap_radius(). 75 override 76 S1Angle minVertexSeparation() const { 77 // Since SnapFunction does not move the input point, output vertices are 78 // separated by the full snap_radius(). 79 return _snapRadius; 80 } 81 82 // For the identity snap function, edges are separated from all non-incident 83 // vertices by at least 0.5 * snap_radius(). 84 override 85 S1Angle minEdgeVertexSeparation() const { 86 // In the worst case configuration, the edge separation is half of the 87 // vertex separation. 88 return 0.5 * _snapRadius; 89 } 90 91 override 92 S2Point snapPoint(in S2Point point) const { 93 return point; 94 } 95 96 override 97 S2Builder.SnapFunction clone() const { 98 return new IdentitySnapFunction(this); 99 } 100 101 override 102 string toString() const { 103 import std.conv; 104 return "IdentitySnapFunction[_snapRadius=" ~ _snapRadius.to!string ~ "]"; 105 } 106 107 private: 108 // Copying and assignment are allowed. 109 S1Angle _snapRadius; 110 } 111 112 /** 113 * A SnapFunction that snaps vertices to S2CellId centers. This can be useful 114 * if you want to encode your geometry compactly using S2Polygon::Encode(), 115 * for example. You can snap to the centers of cells at any level. 116 * 117 * Every snap level has a corresponding minimum snap radius, which is simply 118 * the maximum distance that a vertex can move when snapped. It is 119 * approximately equal to half of the maximum diagonal length for cells at the 120 * chosen level. You can also set the snap radius to a larger value; for 121 * example, you could snap to the centers of leaf cells (1cm resolution) but 122 * set the snap_radius() to 10m. This would result in significant extra 123 * simplification, without moving vertices unnecessarily (i.e., vertices that 124 * are at least 10m away from all other vertices will move by less than 1cm). 125 */ 126 class S2CellIdSnapFunction : S2Builder.SnapFunction { 127 public: 128 /// The default constructor snaps to S2CellId::kMaxLevel (i.e., the centers 129 /// of leaf cells), and uses the minimum allowable snap radius at that level. 130 this() { 131 setLevel(S2CellId.MAX_LEVEL); 132 } 133 134 /// Convenience constructor equivalent to calling set_level(level). 135 this(int level) { 136 setLevel(level); 137 } 138 139 this(in S2CellIdSnapFunction other) { 140 _level = other._level; 141 _snapRadius = other._snapRadius; 142 } 143 144 /** 145 * Snaps vertices to S2Cell centers at the given level. As a side effect, 146 * this method also resets "snap_radius" to the minimum value allowed at 147 * this level: 148 * 149 * set_snap_radius(MinSnapRadiusForLevel(level)) 150 * 151 * This means that if you want to use a larger snap radius than the minimum, 152 * you must call set_snap_radius() *after* calling set_level(). 153 */ 154 void setLevel(int level) 155 in { 156 assert(level >= 0); 157 assert(level <= S2CellId.MAX_LEVEL); 158 } do { 159 _level = level; 160 setSnapRadius(minSnapRadiusForLevel(level)); 161 } 162 163 int level() const { 164 return _level; 165 } 166 167 /** 168 * Defines the snap radius to be used (see s2builder.h). The snap radius 169 * must be at least the minimum value for the current level(), but larger 170 * values can also be used (e.g., to simplify the geometry). 171 * 172 * REQUIRES: snap_radius >= MinSnapRadiusForLevel(level()) 173 * REQUIRES: snap_radius <= SnapFunction::kMaxSnapRadius() 174 */ 175 void setSnapRadius(S1Angle snap_radius) 176 in { 177 assert(snap_radius >= minSnapRadiusForLevel(level())); 178 assert(snap_radius <= kMaxSnapRadius()); 179 } do { 180 _snapRadius = snap_radius; 181 } 182 183 override 184 S1Angle snapRadius() const { 185 return _snapRadius; 186 } 187 188 /// Returns the minimum allowable snap radius for the given S2Cell level 189 /// (approximately equal to half of the maximum cell diagonal length). 190 static S1Angle minSnapRadiusForLevel(int level) { 191 // snap_radius() needs to be an upper bound on the true distance that a 192 // point can move when snapped, taking into account numerical errors. 193 // 194 // The maximum error when converting from an S2Point to an S2CellId is 195 // S2::kMaxDiag.deriv() * DBL_EPSILON. The maximum error when converting an 196 // S2CellId center back to an S2Point is 1.5 * DBL_EPSILON. These add up to 197 // just slightly less than 4 * DBL_EPSILON. 198 return S1Angle.fromRadians(0.5 * MAX_DIAG.getValue(level) + 4 * double.epsilon); 199 } 200 201 /** 202 * Returns the minimum S2Cell level (i.e., largest S2Cells) such that 203 * vertices will not move by more than "snap_radius". This can be useful 204 * when choosing an appropriate level to snap to. The return value is 205 * always a valid level (out of range values are silently clamped). 206 * 207 * If you want to choose the snap level based on a distance, and then use 208 * the minimum possible snap radius for the chosen level, do this: 209 * 210 * S2CellIdSnapFunction f( 211 * S2CellIdSnapFunction::LevelForMaxSnapRadius(distance)); 212 */ 213 static int levelForMaxSnapRadius(S1Angle snap_radius) { 214 // When choosing a level, we need to acount for the error bound of 215 // 4 * DBL_EPSILON that is added by MinSnapRadiusForLevel(). 216 return MAX_DIAG.getLevelForMaxValue(2 * (snap_radius.radians() - 4 * double.epsilon)); 217 } 218 219 /** 220 * For S2CellId snapping, the minimum separation between vertices depends on 221 * level() and snap_radius(). It can vary between 0.5 * snap_radius() 222 * and snap_radius(). 223 */ 224 override 225 S1Angle minVertexSeparation() const { 226 // We have three different bounds for the minimum vertex separation: one is 227 // a constant bound, one is proportional to snap_radius, and one is equal to 228 // snap_radius minus a constant. These bounds give the best results for 229 // small, medium, and large snap radii respectively. We return the maximum 230 // of the three bounds. 231 // 232 // 1. Constant bound: Vertices are always separated by at least 233 // kMinEdge(level), the minimum edge length for the chosen snap level. 234 // 235 // 2. Proportional bound: It can be shown that in the plane, the worst-case 236 // configuration has a vertex separation of 2 / sqrt(13) * snap_radius. 237 // This is verified in the unit test, except that on the sphere the ratio 238 // is slightly smaller at cell level 2 (0.54849 vs. 0.55470). We reduce 239 // that value a bit more below to be conservative. 240 // 241 // 3. Best asymptotic bound: This bound bound is derived by observing we 242 // only select a new site when it is at least snap_radius() away from all 243 // existing sites, and the site can move by at most 0.5 * kMaxDiag(level) 244 // when snapped. 245 S1Angle min_edge = S1Angle.fromRadians(MIN_EDGE.getValue(_level)); 246 S1Angle max_diag = S1Angle.fromRadians(MAX_DIAG.getValue(_level)); 247 return max(min_edge, 248 max(0.548 * _snapRadius, // 2 / sqrt(13) in the plane 249 _snapRadius - 0.5 * max_diag)); 250 } 251 252 /** 253 * For S2CellId snapping, the minimum separation between edges and 254 * non-incident vertices depends on level() and snap_radius(). It can 255 * be as low as 0.219 * snap_radius(), but is typically 0.5 * snap_radius() 256 * or more. 257 */ 258 override 259 S1Angle minEdgeVertexSeparation() const { 260 // Similar to min_vertex_separation(), in this case we have four bounds: a 261 // constant bound that holds only at the minimum snap radius, a constant 262 // bound that holds for any snap radius, a bound that is proportional to 263 // snap_radius, and a bound that is equal to snap_radius minus a constant. 264 // 265 // 1. Constant bounds: 266 // 267 // (a) At the minimum snap radius for a given level, it can be shown that 268 // vertices are separated from edges by at least 0.5 * kMinDiag(level) in 269 // the plane. The unit test verifies this, except that on the sphere the 270 // worst case is slightly better: 0.5652980068 * kMinDiag(level). 271 // 272 // (b) Otherwise, for arbitrary snap radii the worst-case configuration 273 // in the plane has an edge-vertex separation of sqrt(3/19) * 274 // kMinDiag(level), where sqrt(3/19) is about 0.3973597071. The unit 275 // test verifies that the bound is slighty better on the sphere: 276 // 0.3973595687 * kMinDiag(level). 277 // 278 // 2. Proportional bound: In the plane, the worst-case configuration has an 279 // edge-vertex separation of 2 * sqrt(3/247) * snap_radius, which is 280 // about 0.2204155075. The unit test verifies this, except that on the 281 // sphere the bound is slightly worse for certain large S2Cells: the 282 // minimum ratio occurs at cell level 6, and is about 0.2196666953. 283 // 284 // 3. Best asymptotic bound: If snap_radius() is large compared to the 285 // minimum snap radius, then the best bound is achieved by 3 sites on a 286 // circular arc of radius "snap_radius", spaced "min_vertex_separation" 287 // apart. An input edge passing just to one side of the center of the 288 // circle intersects the Voronoi regions of the two end sites but not the 289 // Voronoi region of the center site, and gives an edge separation of 290 // (min_vertex_separation ** 2) / (2 * snap_radius). This bound 291 // approaches 0.5 * snap_radius for large snap radii, i.e. the minimum 292 // edge-vertex separation approaches half of the minimum vertex 293 // separation as the snap radius becomes large compared to the cell size. 294 295 S1Angle min_diag = S1Angle.fromRadians(MIN_DIAG.getValue(_level)); 296 if (snapRadius() == minSnapRadiusForLevel(_level)) { 297 // This bound only holds when the minimum snap radius is being used. 298 return 0.565 * min_diag; // 0.500 in the plane 299 } 300 // Otherwise, these bounds hold for any snap_radius(). 301 S1Angle vertex_sep = minVertexSeparation(); 302 return max(0.397 * min_diag, // sqrt(3 / 19) in the plane 303 max(0.219 * _snapRadius, // 2 * sqrt(3 / 247) in the plane 304 0.5 * (vertex_sep / _snapRadius) * vertex_sep)); 305 } 306 307 override 308 S2Point snapPoint(in S2Point point) const { 309 return S2CellId(point).parent(_level).toS2Point(); 310 } 311 312 override 313 S2Builder.SnapFunction clone() const { 314 return new S2CellIdSnapFunction(this); 315 } 316 317 private: 318 // Copying and assignment are allowed. 319 int _level; 320 S1Angle _snapRadius; 321 } 322 323 /** 324 * A SnapFunction that snaps vertices to S2LatLng E5, E6, or E7 coordinates. 325 * These coordinates are expressed in degrees multiplied by a power of 10 and 326 * then rounded to the nearest integer. For example, in E6 coordinates the 327 * point (23.12345651, -45.65432149) would become (23123457, -45654321). 328 * 329 * The main argument of the SnapFunction is the exponent for the power of 10 330 * that coordinates should be multipled by before rounding. For example, 331 * IntLatLngSnapFunction(7) is a function that snaps to E7 coordinates. The 332 * exponent can range from 0 to 10. 333 * 334 * Each exponent has a corresponding minimum snap radius, which is simply the 335 * maximum distance that a vertex can move when snapped. It is approximately 336 * equal to 1/sqrt(2) times the nominal point spacing; for example, for 337 * snapping to E7 the minimum snap radius is (1e-7 / sqrt(2)) degrees. 338 * You can also set the snap radius to any value larger than this; this can 339 * result in significant extra simplification (similar to using a larger 340 * exponent) but does not move vertices unnecessarily. 341 */ 342 class IntLatLngSnapFunction : S2Builder.SnapFunction { 343 public: 344 /// The default constructor yields an invalid snap function. You must set 345 /// the exponent explicitly before using it. 346 this() { 347 _exponent = -1; 348 _snapRadius = S1Angle(); 349 _fromDegrees = 0; 350 _toDegrees = 0; 351 } 352 353 /// Convenience constructor equivalent to calling set_exponent(exponent). 354 this(int exponent) { 355 setExponent(exponent); 356 } 357 358 this(in IntLatLngSnapFunction other) { 359 _exponent = other._exponent; 360 _snapRadius = other._snapRadius; 361 _fromDegrees = other._fromDegrees; 362 _toDegrees = other._toDegrees; 363 } 364 365 /** 366 * Snaps vertices to points whose (lat, lng) coordinates are integers after 367 * converting to degrees and multiplying by 10 raised to the given exponent. 368 * For example, (exponent == 7) yields E7 coordinates. As a side effect, 369 * this method also resets "snap_radius" to the minimum value allowed for 370 * this exponent: 371 * 372 * set_snap_radius(MinSnapRadiusForExponent(exponent)) 373 * 374 * This means that if you want to use a larger snap radius than the minimum, 375 * you must call set_snap_radius() *after* calling set_exponent(). 376 * 377 * REQUIRES: kMinExponent <= exponent <= kMaxExponent 378 */ 379 void setExponent(int exponent) 380 in { 381 assert(exponent >= MIN_EXPONENT); 382 assert(exponent <= MAX_EXPONENT); 383 } do { 384 _exponent = exponent; 385 setSnapRadius(minSnapRadiusForExponent(exponent)); 386 387 // Precompute the scale factors needed for snapping. Note that these 388 // calculations need to exactly match the ones in s1angle.h to ensure 389 // that the same S2Points are generated. 390 double power = 1; 391 for (int i = 0; i < exponent; ++i) power *= 10; 392 _fromDegrees = power; 393 _toDegrees = 1 / power; 394 } 395 396 int exponent() const { 397 return _exponent; 398 } 399 400 /// The minum exponent supported for snapping. 401 enum int MIN_EXPONENT = 0; 402 403 /// The maximum exponent supported for snapping. 404 enum int MAX_EXPONENT = 10; 405 406 /** 407 * Defines the snap radius to be used (see s2builder.h). The snap radius 408 * must be at least the minimum value for the current exponent(), but larger 409 * values can also be used (e.g., to simplify the geometry). 410 * 411 * REQUIRES: snap_radius >= MinSnapRadiusForExponent(exponent()) 412 * REQUIRES: snap_radius <= SnapFunction::kMaxSnapRadius() 413 */ 414 void setSnapRadius(S1Angle snap_radius) 415 in { 416 assert(snap_radius >= minSnapRadiusForExponent(exponent())); 417 assert(snap_radius <= kMaxSnapRadius()); 418 } do { 419 _snapRadius = snap_radius; 420 } 421 422 override 423 S1Angle snapRadius() const { 424 return _snapRadius; 425 } 426 427 // Returns the minimum allowable snap radius for the given exponent 428 // (approximately equal to (pow(10, -exponent) / sqrt(2)) degrees). 429 static S1Angle minSnapRadiusForExponent(int exponent) { 430 // snap_radius() needs to be an upper bound on the true distance that a 431 // point can move when snapped, taking into account numerical errors. 432 // 433 // The maximum errors in latitude and longitude can be bounded as 434 // follows (as absolute errors in terms of DBL_EPSILON): 435 // 436 // Latitude Longitude 437 // Convert to S2LatLng: 1.000 1.000 438 // Convert to degrees: 1.032 2.063 439 // Scale by 10**exp: 0.786 1.571 440 // Round to integer: 0.5 * S1Angle::Degrees(to_degrees_) 441 // Scale by 10**(-exp): 1.375 2.749 442 // Convert to radians: 1.252 1.503 443 // ------------------------------------------------------------ 444 // Total (except for rounding) 5.445 8.886 445 // 446 // The maximum error when converting the S2LatLng back to an S2Point is 447 // 448 // sqrt(2) * (maximum error in latitude or longitude) + 1.5 * DBL_EPSILON 449 // 450 // which works out to (9 * sqrt(2) + 1.5) * DBL_EPSILON radians. Finally 451 // we need to consider the effect of rounding to integer coordinates 452 // (much larger than the errors above), which can change the position by 453 // up to (sqrt(2) * 0.5 * to_degrees_) radians. 454 double power = 1; 455 for (int i = 0; i < exponent; ++i) power *= 10; 456 return (S1Angle.fromDegrees(M_SQRT1_2 / power) + 457 S1Angle.fromRadians((9 * M_SQRT2 + 1.5) * double.epsilon)); 458 } 459 460 /** 461 * Returns the minimum exponent such that vertices will not move by more 462 * than "snap_radius". This can be useful when choosing an appropriate 463 * exponent for snapping. The return value is always a valid exponent 464 * (out of range values are silently clamped). 465 * 466 * If you want to choose the exponent based on a distance, and then use 467 * the minimum possible snap radius for that exponent, do this: 468 * 469 * IntLatLngSnapFunction f( 470 * IntLatLngSnapFunction::ExponentForMaxSnapRadius(distance)); 471 */ 472 static int exponentForMaxSnapRadius(S1Angle snap_radius) { 473 // When choosing an exponent, we need to acount for the error bound of 474 // (9 * sqrt(2) + 1.5) * DBL_EPSILON added by MinSnapRadiusForExponent(). 475 snap_radius -= S1Angle.fromRadians((9 * M_SQRT2 + 1.5) * double.epsilon); 476 snap_radius = max(snap_radius, S1Angle.fromRadians(1e-30)); 477 double exponent = log10(M_SQRT1_2 / snap_radius.degrees()); 478 479 // There can be small errors in the calculation above, so to ensure that 480 // this function is the inverse of MinSnapRadiusForExponent() we subtract a 481 // small error tolerance. 482 return max(MIN_EXPONENT, 483 min(MAX_EXPONENT, cast(int) ceil(exponent - 2 * double.epsilon))); 484 } 485 486 /** 487 * For IntLatLng snapping, the minimum separation between vertices depends on 488 * exponent() and snap_radius(). It can vary between snap_radius() 489 * and snap_radius(). 490 */ 491 override 492 S1Angle minVertexSeparation() const { 493 // We have two bounds for the minimum vertex separation: one is proportional 494 // to snap_radius, and one is equal to snap_radius minus a constant. These 495 // bounds give the best results for small and large snap radii respectively. 496 // We return the maximum of the two bounds. 497 // 498 // 1. Proportional bound: It can be shown that in the plane, the worst-case 499 // configuration has a vertex separation of (sqrt(2) / 3) * snap_radius. 500 // This is verified in the unit test, except that on the sphere the ratio 501 // is slightly smaller (0.471337 vs. 0.471404). We reduce that value a 502 // bit more below to be conservative. 503 // 504 // 2. Best asymptotic bound: This bound bound is derived by observing we 505 // only select a new site when it is at least snap_radius() away from all 506 // existing sites, and snapping a vertex can move it by up to 507 // ((1 / sqrt(2)) * to_degrees_) degrees. 508 return max(0.471 * _snapRadius, // sqrt(2) / 3 in the plane 509 _snapRadius - S1Angle.fromDegrees(M_SQRT1_2 * _toDegrees)); 510 } 511 512 /** 513 * For IntLatLng snapping, the minimum separation between edges and 514 * non-incident vertices depends on level() and snap_radius(). It can 515 * be as low as 0.222 * snap_radius(), but is typically 0.39 * snap_radius() 516 * or more. 517 */ 518 override 519 S1Angle minEdgeVertexSeparation() const { 520 // Similar to min_vertex_separation(), in this case we have three bounds: 521 // one is a constant bound, one is proportional to snap_radius, and one is 522 // equal to snap_radius minus a constant. 523 // 524 // 1. Constant bound: In the plane, the worst-case configuration has an 525 // edge-vertex separation of ((1 / sqrt(13)) * to_degrees_) degrees. 526 // The unit test verifies this, except that on the sphere the ratio is 527 // slightly lower when small exponents such as E1 are used 528 // (0.2772589 vs 0.2773501). 529 // 530 // 2. Proportional bound: In the plane, the worst-case configuration has an 531 // edge-vertex separation of (2 / 9) * snap_radius (0.222222222222). The 532 // unit test verifies this, except that on the sphere the bound can be 533 // slightly worse with large exponents (e.g., E9) due to small numerical 534 // errors (0.222222126756717). 535 // 536 // 3. Best asymptotic bound: If snap_radius() is large compared to the 537 // minimum snap radius, then the best bound is achieved by 3 sites on a 538 // circular arc of radius "snap_radius", spaced "min_vertex_separation" 539 // apart (see S2CellIdSnapFunction::min_edge_vertex_separation). This 540 // bound approaches 0.5 * snap_radius as the snap radius becomes large 541 // relative to the grid spacing. 542 543 S1Angle vertex_sep = minVertexSeparation(); 544 return max(0.277 * S1Angle.fromDegrees(_toDegrees), // 1/sqrt(13) in the plane 545 max(0.222 * _snapRadius, // 2/9 in the plane 546 0.5 * (vertex_sep / _snapRadius) * vertex_sep)); 547 } 548 549 override 550 S2Point snapPoint(in S2Point point) const 551 in { 552 assert(_exponent >= 0); // Make sure the snap function was initialized. 553 } do { 554 auto input = S2LatLng(point); 555 long lat = lround(input.lat().degrees() * _fromDegrees); 556 long lng = lround(input.lng().degrees() * _fromDegrees); 557 return S2LatLng.fromDegrees(lat * _toDegrees, lng * _toDegrees).toS2Point(); 558 } 559 560 override 561 S2Builder.SnapFunction clone() const { 562 return new IntLatLngSnapFunction(this); 563 } 564 565 private: 566 // Copying and assignment are allowed. 567 int _exponent; 568 S1Angle _snapRadius; 569 double _fromDegrees; 570 double _toDegrees; 571 }