Skip to content

Commit 3639c24

Browse files
authored
builder_snapper: Update constants and comments (#172)
Fix comments. Remove outdate maxEdgeDeviation methods. Add TODOs for a few more things to be done. Update some of the inline constant values to match the C++ values. (sqrt(2)/13 --> 0.548, etc. Remove removed methods from test.
1 parent 0e61285 commit 3639c24

File tree

2 files changed

+92
-108
lines changed

2 files changed

+92
-108
lines changed

s2/builder_snapper.go

Lines changed: 92 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -27,32 +27,26 @@ import (
2727
//
2828
// A Snapper defines the following methods:
2929
//
30-
// 1. The SnapPoint method, which snaps a point P to a nearby point (the
30+
// 1. The SnapPoint method, which snaps a point P to a nearby point (the
31+
// candidate snap site). Any point may be returned, including P
32+
// itself (the identity snap function).
3133
//
32-
// candidate snap site). Any point may be returned, including P
33-
// itself (the identity snap function).
34+
// 2. SnapRadius, the maximum distance that vertices can move when
35+
// snapped. The snapRadius must be at least as large as the maximum
36+
// distance between P and SnapPoint(P) for any point P.
3437
//
35-
// 2. SnapRadius, the maximum distance that vertices can move when
38+
// Note that the maximum distance that edge interiors can move when
39+
// snapped is slightly larger than "snapRadius", and is reported by
40+
// the Builder options maxEdgeDeviation (see there for details).
3641
//
37-
// snapped. The snapRadius must be at least as large as the maximum
38-
// distance between P and SnapPoint(P) for any point P.
42+
// 3. MinVertexSeparation, the guaranteed minimum distance between
43+
// vertices in the output. This is generally a fraction of
44+
// snapRadius where the fraction depends on the snap function.
3945
//
40-
// 3. MaxEdgeDeviation, the maximum distance that edges can move when
41-
//
42-
// snapped. It is slightly larger than snapRadius because when a
43-
// geodesic edge is snapped, the center of the edge moves further than
44-
// its endpoints. This value is computed automatically by Builder.
45-
//
46-
// 4. MinVertexSeparation, the guaranteed minimum distance between
47-
//
48-
// vertices in the output. This is generally a fraction of
49-
// snapRadius where the fraction depends on the snap function.
50-
//
51-
// 5. A MinEdgeVertexSeparation, the guaranteed minimum distance
52-
//
53-
// between edges and non-incident vertices in the output. This is
54-
// generally a fraction of snapRadius where the fraction depends on
55-
// the snap function.
46+
// 4. A MinEdgeVertexSeparation, the guaranteed minimum distance
47+
// between edges and non-incident vertices in the output. This is
48+
// generally a fraction of snapRadius where the fraction depends on
49+
// the snap function.
5650
//
5751
// It is important to note that SnapPoint does not define the actual
5852
// mapping from input vertices to output vertices, since the points it
@@ -65,41 +59,36 @@ import (
6559
//
6660
// Builder makes the following guarantees (within a small error margin):
6761
//
68-
// 1. Every vertex is at a location returned by SnapPoint.
69-
//
70-
// 2. Vertices are within snapRadius of the corresponding input vertex.
71-
//
72-
// 3. Edges are within maxEdgeDeviation of the corresponding input edge
62+
// 1. Every vertex is at a location returned by SnapPoint.
7363
//
74-
// (a distance slightly larger than snapRadius).
64+
// 2. Vertices are within snapRadius of the corresponding input vertex.
7565
//
76-
// 4. Vertices are separated by at least minVertexSeparation
66+
// 3. Edges are within maxEdgeDeviation of the corresponding input edge
67+
// (a distance slightly larger than snapRadius).
7768
//
78-
// (a fraction of snapRadius that depends on the snap function).
69+
// 4. Vertices are separated by at least minVertexSeparation
70+
// (a fraction of snapRadius that depends on the snap function).
7971
//
80-
// 5. Edges and non-incident vertices are separated by at least
72+
// 5. Edges and non-incident vertices are separated by at least
73+
// minEdgeVertexSeparation (a fraction of snapRadius).
8174
//
82-
// minEdgeVertexSeparation (a fraction of snapRadius).
75+
// 6. Vertex and edge locations do not change unless one of the conditions
76+
// above is not already met (idempotency / stability).
8377
//
84-
// 6. Vertex and edge locations do not change unless one of the conditions
85-
//
86-
// above is not already met (idempotency / stability).
87-
//
88-
// 7. The topology of the input geometry is preserved (up to the creation
89-
//
90-
// of degeneracies). This means that there exists a continuous
91-
// deformation from the input to the output such that no vertex
92-
// crosses an edge.
78+
// 7. The topology of the input geometry is preserved (up to the creation
79+
// of degeneracies). This means that there exists a continuous
80+
// deformation from the input to the output such that no vertex
81+
// crosses an edge.
9382
type Snapper interface {
94-
// SnapRadius reports the maximum distance that vertices can move when snapped.
95-
// This requires that SnapRadius <= maxSnapRadius
83+
// SnapRadius reports the maximum distance that vertices can move when
84+
// snapped. The snap radius can be any value between 0 and maxSnapRadius.
85+
//
86+
// If the snap radius is zero, then vertices are snapped together only if
87+
// they are identical. Edges will not be snapped to any vertices other
88+
// than their endpoints, even if there are vertices whose distance to the
89+
// edge is zero, unless split_crossing_edges() is true (see below).
9690
SnapRadius() s1.Angle
97-
98-
// MaxEdgeDeviation returns the maximum distance that the center of an
99-
// edge can move when snapped. This is slightly larger than SnapRadius
100-
// because when a geodesic edge is snapped, the center of the edge moves
101-
// further than its endpoints.
102-
MaxEdgeDeviation() s1.Angle
91+
// TODO(rsned): Add SetSnapRadius method to allow users to update value.
10392

10493
// MinVertexSeparation returns the guaranteed minimum distance between
10594
// vertices in the output. This is generally some fraction of SnapRadius.
@@ -158,20 +147,19 @@ func (sf IdentitySnapper) SnapRadius() s1.Angle {
158147
return sf.snapRadius
159148
}
160149

161-
// MaxEdgeDeviation returns the maximum edge deviation this type supports.
162-
func (sf IdentitySnapper) MaxEdgeDeviation() s1.Angle {
163-
return maxEdgeDeviationRatio * sf.snapRadius
164-
}
165-
166150
// MinVertexSeparation returns the minimum vertex separation for this snap type.
167151
func (sf IdentitySnapper) MinVertexSeparation() s1.Angle {
152+
// Since SnapFunction does not move the input point, output vertices are
153+
// separated by the full snapRadius.
168154
return sf.snapRadius
169155
}
170156

171157
// MinEdgeVertexSeparation returns the minimum edge vertex separation.
172158
// For the identity snap function, edges are separated from all non-incident
173159
// vertices by at least 0.5 * snapRadius.
174160
func (sf IdentitySnapper) MinEdgeVertexSeparation() s1.Angle {
161+
// In the worst case configuration, the edge-vertex separation is half of the
162+
// vertex separation.
175163
return 0.5 * sf.snapRadius
176164
}
177165

@@ -197,6 +185,8 @@ type CellIDSnapper struct {
197185
snapRadius s1.Angle
198186
}
199187

188+
// TODO(rsned): Add SetLevel method to allow changes to the type.
189+
200190
// NewCellIDSnapper returns a snap function with the default level set.
201191
func NewCellIDSnapper() CellIDSnapper {
202192
return CellIDSnapper{
@@ -215,12 +205,6 @@ func CellIDSnapperForLevel(level int) CellIDSnapper {
215205

216206
// SnapRadius reports the maximum distance that vertices can move when snapped.
217207
// This requires that SnapRadius <= maxSnapRadius
218-
// Defines the snap radius to be used (see Builder). The snap radius
219-
// must be at least the minimum value for the current level, but larger
220-
// values can also be used (e.g., to simplify the geometry).
221-
//
222-
// This requires snapRadius >= MinSnapRadiusForLevel(level)
223-
// and snapRadius <= maxSnapRadius
224208
func (sf CellIDSnapper) SnapRadius() s1.Angle {
225209
return sf.snapRadius
226210
}
@@ -247,53 +231,48 @@ func (sf CellIDSnapper) minSnapRadiusForLevel(level int) s1.Angle {
247231
// the minimum possible snap radius for the chosen level, do this:
248232
//
249233
// sf := CellIDSnapperForLevel(f.levelForMaxSnapRadius(distance));
234+
//
235+
// TODO(rsned): pop this method out to standalone.
250236
func (sf CellIDSnapper) levelForMaxSnapRadius(snapRadius s1.Angle) int {
251237
// When choosing a level, we need to account for the error bound of
252238
// 4 * dblEpsilon that is added by MinSnapRadiusForLevel.
253239
return MaxDiagMetric.MinLevel(2 * (snapRadius.Radians() - 4*dblEpsilon))
254240
}
255241

256-
// MaxEdgeDeviation returns the maximum edge deviation this type supports.
257-
func (sf CellIDSnapper) MaxEdgeDeviation() s1.Angle {
258-
return maxEdgeDeviationRatio * sf.snapRadius
259-
}
260-
261-
// MinVertexSeparation returns the guaranteed minimum distance between
262-
// vertices in the output. This is generally some fraction of SnapRadius.
263-
// For CellID snapping, the minimum separation between vertices depends on
264-
// level and snapRadius. It can vary between 0.5 * snapRadius
265-
// and snapRadius.
242+
// MinVertexSeparation reports the minimum separation between vertices depending
243+
// on level and snapRadius. It can vary between 0.5 * snapRadius and snapRadius.
266244
func (sf CellIDSnapper) MinVertexSeparation() s1.Angle {
267245
// We have three different bounds for the minimum vertex separation: one is
268246
// a constant bound, one is proportional to snapRadius, and one is equal to
269-
// snapRadius minus a constant. These bounds give the best results for
270-
// small, medium, and large snap radii respectively. We return the maximum
247+
// snapRadius minus a constant. These bounds give the best results for
248+
// small, medium, and large snap radii respectively. We return the maximum
271249
// of the three bounds.
272250
//
273251
// 1. Constant bound: Vertices are always separated by at least
274-
// MinEdgeMetric.Value(level), the minimum edge length for the chosen snap level.
252+
// MinEdgeMetric.Value(level), the minimum edge length for the chosen
253+
// snap level.
275254
//
276255
// 2. Proportional bound: It can be shown that in the plane, the worst-case
277256
// configuration has a vertex separation of 2 / sqrt(13) * snapRadius.
278257
// This is verified in the unit test, except that on the sphere the ratio
279258
// is slightly smaller at cell level 2 (0.54849 vs. 0.55470). We reduce
280259
// that value a bit more below to be conservative.
281260
//
282-
// 3. Best asymptotic bound: This bound bound is derived by observing we
261+
// 3. Best asymptotic bound: This bound is derived by observing we
283262
// only select a new site when it is at least snapRadius away from all
284263
// existing sites, and the site can move by at most
285264
// 0.5 * MaxDiagMetric.Value(level) when snapped.
286265
minEdge := s1.Angle(MinEdgeMetric.Value(sf.level))
287266
maxDiag := s1.Angle(MaxDiagMetric.Value(sf.level))
288267
return maxAngle(minEdge,
289-
maxAngle(s1.Angle(2/math.Sqrt(13))*sf.snapRadius, sf.snapRadius-0.5*maxDiag))
268+
// per 2 above, a little less than 2 / sqrt(13)
269+
maxAngle(0.548*sf.snapRadius,
270+
sf.snapRadius-0.5*maxDiag))
290271
}
291272

292273
// MinEdgeVertexSeparation returns the guaranteed minimum spacing between
293-
// edges and non-incident vertices in the output.
294-
// For CellID snapping, the minimum separation between edges and
295-
// non-incident vertices depends on level and snapRadius. It can
296-
// be as low as 0.219 * snapRadius, but is typically 0.5 * snapRadius
274+
// edges and non-incident vertices in the output depending on level and snapRadius..
275+
// It can be as low as 0.219 * snapRadius, but is typically 0.5 * snapRadius
297276
// or more.
298277
func (sf CellIDSnapper) MinEdgeVertexSeparation() s1.Angle {
299278
// Similar to MinVertexSeparation, in this case we have four bounds: a
@@ -302,15 +281,16 @@ func (sf CellIDSnapper) MinEdgeVertexSeparation() s1.Angle {
302281
// snapRadius, and a bound that is equal to snapRadius minus a constant.
303282
//
304283
// 1. Constant bounds:
305-
// (a) At the minimum snap radius for a given level, it can be shown that
306-
// vertices are separated from edges by at least 0.5 * MinDiagMetric.Value(level) in
307-
// the plane. The unit test verifies this, except that on the sphere the
308-
// worst case is slightly better: 0.5652980068 * MinDiagMetric.Value(level).
284+
// (a) At the minimum snap radius for a given level, it can be shown
285+
// that vertices are separated from edges by at least 0.5 *a
286+
// MinDiagMetric.Value(level) in the plane. The unit test verifies this,
287+
// except that on the sphere the worst case is slightly better:
288+
// 0.5652980068 * MinDiagMetric.Value(level).
309289
//
310290
// (b) Otherwise, for arbitrary snap radii the worst-case configuration
311291
// in the plane has an edge-vertex separation of sqrt(3/19) *
312-
// MinDiagMetric.Value(level), where sqrt(3/19) is about 0.3973597071. The unit
313-
// test verifies that the bound is slightly better on the sphere:
292+
// MinDiagMetric.Value(level), where sqrt(3/19) is about 0.3973597071.
293+
// The unit test verifies that the bound is slightly better on the sphere:
314294
// 0.3973595687 * MinDiagMetric.Value(level).
315295
//
316296
// 2. Proportional bound: In the plane, the worst-case configuration has an
@@ -337,8 +317,8 @@ func (sf CellIDSnapper) MinEdgeVertexSeparation() s1.Angle {
337317

338318
// Otherwise, these bounds hold for any snapRadius.
339319
vertexSep := sf.MinVertexSeparation()
340-
return maxAngle(s1.Angle(math.Sqrt(3.0/19.0))*minDiag,
341-
maxAngle(s1.Angle(2*math.Sqrt(3.0/247.0))*sf.snapRadius,
320+
return maxAngle(0.397*minDiag, // sqrt(3/19) in the plane
321+
maxAngle(0.219*sf.snapRadius, // 2*sqrt(3/247) in the plane
342322
0.5*(vertexSep/sf.snapRadius)*vertexSep))
343323
}
344324

@@ -360,6 +340,11 @@ const (
360340
// example, in E6 coordinates the point (23.12345651, -45.65432149) would
361341
// become (23123457, -45654321).
362342
//
343+
// The main argument of the Snapper is the exponent for the power of 10
344+
// that coordinates should be multiplied by before rounding. For example,
345+
// NewIntLatLngSnapper(7) is a function that snaps to E7 coordinates. The
346+
// exponent can range from 0 to 10.
347+
//
363348
// Each exponent has a corresponding minimum snap radius, which is simply the
364349
// maximum distance that a vertex can move when snapped. It is approximately
365350
// equal to 1/sqrt(2) times the nominal point spacing; for example, for
@@ -388,6 +373,8 @@ func NewIntLatLngSnapper(exponent int) IntLatLngSnapper {
388373
return sf
389374
}
390375

376+
// TODO(rsned): Add SetExponent() method.
377+
391378
// SnapRadius reports the snap radius to be used. The snap radius
392379
// must be at least the minimum value for the current exponent, but larger
393380
// values can also be used (e.g., to simplify the geometry).
@@ -400,6 +387,8 @@ func (sf IntLatLngSnapper) SnapRadius() s1.Angle {
400387

401388
// minSnapRadiusForExponent returns the minimum allowable snap radius for the given
402389
// exponent (approximately equal to 10**(-exponent) / sqrt(2)) degrees).
390+
//
391+
// TODO(rsned): Pop this method out so it can be used by other callers.
403392
func (sf IntLatLngSnapper) minSnapRadiusForExponent(exponent int) s1.Angle {
404393
// snapRadius needs to be an upper bound on the true distance that a
405394
// point can move when snapped, taking into account numerical errors.
@@ -426,18 +415,21 @@ func (sf IntLatLngSnapper) minSnapRadiusForExponent(exponent int) s1.Angle {
426415
// (much larger than the errors above), which can change the position by
427416
// up to (sqrt(2) * 0.5 * sf.to) radians.
428417
power := math.Pow10(exponent)
429-
return (s1.Degree*s1.Angle((1/math.Sqrt2)/power) + s1.Angle((9*math.Sqrt2+1.5)*dblEpsilon))
418+
return (s1.Degree*s1.Angle((1/math.Sqrt2)/power) +
419+
s1.Angle((9*math.Sqrt2+1.5)*dblEpsilon))
430420
}
431421

432422
// exponentForMaxSnapRadius returns the minimum exponent such that vertices will
433423
// not move by more than snapRadius. This can be useful when choosing an appropriate
434424
// exponent for snapping. The return value is always a valid exponent (out of
435425
// range values are silently clamped).
426+
//
427+
// TODO(rsned): Pop this method out so it can be used by other callers.
436428
func (sf IntLatLngSnapper) exponentForMaxSnapRadius(snapRadius s1.Angle) int {
437429
// When choosing an exponent, we need to account for the error bound of
438430
// (9 * sqrt(2) + 1.5) * dblEpsilon added by minSnapRadiusForExponent.
439431
snapRadius -= (9*math.Sqrt2 + 1.5) * dblEpsilon
440-
snapRadius = s1.Angle(math.Max(float64(snapRadius), 1e-30))
432+
snapRadius = maxAngle(snapRadius, 1e-30)
441433
exponent := math.Log10((1 / math.Sqrt2) / snapRadius.Degrees())
442434

443435
// There can be small errors in the calculation above, so to ensure that
@@ -447,14 +439,8 @@ func (sf IntLatLngSnapper) exponentForMaxSnapRadius(snapRadius s1.Angle) int {
447439
minInt(maxIntSnappingExponent, int(math.Ceil(exponent-2*dblEpsilon))))
448440
}
449441

450-
// MaxEdgeDeviation returns the maximum edge deviation this type supports.
451-
func (sf IntLatLngSnapper) MaxEdgeDeviation() s1.Angle {
452-
return maxEdgeDeviationRatio * sf.snapRadius
453-
}
454-
455-
// MinVertexSeparation returns the guaranteed minimum distance between vertices
456-
// in the output. For IntLatLng snapping, the minimum separation between vertices
457-
// depends on exponent and snapRadius.
442+
// MinVertexSeparation reports the minimum separation between vertices depending on
443+
// exponent and snapRadius. It can vary between 0.471 * snapRadius and snapRadius.
458444
func (sf IntLatLngSnapper) MinVertexSeparation() s1.Angle {
459445
// We have two bounds for the minimum vertex separation: one is proportional
460446
// to snapRadius, and one is equal to snapRadius minus a constant. These
@@ -471,19 +457,18 @@ func (sf IntLatLngSnapper) MinVertexSeparation() s1.Angle {
471457
// only select a new site when it is at least snapRadius away from all
472458
// existing sites, and snapping a vertex can move it by up to
473459
// ((1 / sqrt(2)) * sf.to) degrees.
474-
return maxAngle((math.Sqrt2/3)*sf.snapRadius,
460+
return maxAngle(0.471*sf.snapRadius, // sqrt(2)/3 in the plane
475461
sf.snapRadius-s1.Degree*s1.Angle(1/math.Sqrt2)*sf.to)
476462
}
477463

478-
// MinEdgeVertexSeparation returns the guaranteed minimum spacing between edges
479-
// and non-incident vertices in the output. For IntLatLng snapping, the minimum
480-
// separation between edges and non-incident vertices depends on level and
464+
// MinEdgeVertexSeparation reports the minimum separation between edges
465+
// and non-incident vertices in the output depending on the level and
481466
// snapRadius. It can be as low as 0.222 * snapRadius, but is typically
482467
// 0.39 * snapRadius or more.
483468
func (sf IntLatLngSnapper) MinEdgeVertexSeparation() s1.Angle {
484469
// Similar to MinVertexSeparation, in this case we have three bounds:
485470
// one is a constant bound, one is proportional to snapRadius, and one is
486-
// equal to snapRadius minus a constant.
471+
// approaches 0.5 * snapRadius asymptotically.
487472
//
488473
// 1. Constant bound: In the plane, the worst-case configuration has an
489474
// edge-vertex separation of ((1 / sqrt(13)) * sf.to) degrees.
@@ -504,12 +489,15 @@ func (sf IntLatLngSnapper) MinEdgeVertexSeparation() s1.Angle {
504489
// bound approaches 0.5 * snapRadius as the snap radius becomes large
505490
// relative to the grid spacing.
506491
vertexSep := sf.MinVertexSeparation()
507-
return maxAngle(s1.Angle(1/math.Sqrt(13))*s1.Degree*sf.to,
508-
maxAngle((2.0/9.0)*sf.snapRadius, 0.5*(vertexSep/sf.snapRadius)*vertexSep))
492+
return maxAngle(0.277*s1.Degree*sf.to, // 1/sqrt(13) in the plane
493+
maxAngle(0.222*sf.snapRadius, // 2/9 in the plane
494+
0.5*(vertexSep/sf.snapRadius)*vertexSep))
509495
}
510496

511497
// SnapPoint returns a candidate snap site for the given point.
512498
func (sf IntLatLngSnapper) SnapPoint(point Point) Point {
499+
// TODO(rsned): C++ DCHECK's on exponent being in valid range. What should we
500+
// do when it's bad here.
513501
input := LatLngFromPoint(point)
514502
lat := s1.Angle(roundAngle(input.Lat * sf.from))
515503
lng := s1.Angle(roundAngle(input.Lng * sf.from))

s2/builder_snapper_test.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,6 @@ func TestIdentitySnapper(t *testing.T) {
3636
t.Errorf("identSnap.MinEdgeVertexSeparation() = %v, want %v", i.MinEdgeVertexSeparation(), 0.5*rad)
3737
}
3838

39-
if i.MaxEdgeDeviation() != maxEdgeDeviationRatio {
40-
t.Errorf("identSnap.SnapRadius() = %v, want %v", i.MaxEdgeDeviation(), maxEdgeDeviationRatio)
41-
}
42-
4339
p := randomPoint()
4440
if got := i.SnapPoint(p); !p.ApproxEqual(got) {
4541
t.Errorf("identSnap.SnapPoint(%v) = %v, want %v", p, got, p)

0 commit comments

Comments
 (0)