Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 770b483

Browse files
rsneddsymonds
authored andcommitted
s2: Add initial pieces of Loop validation.
This is a precursor to Polygon validation. Signed-off-by: David Symonds <[email protected]>
1 parent 03e25be commit 770b483

File tree

2 files changed

+110
-0
lines changed

2 files changed

+110
-0
lines changed

s2/loop.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,59 @@ func (l *Loop) initBound() {
204204
l.subregionBound = ExpandForSubregions(l.bound)
205205
}
206206

207+
// IsValid reports whether this is a valid loop or not.
208+
func (l *Loop) IsValid() bool {
209+
return l.findValidationError() == nil
210+
}
211+
212+
// findValidationError reports whether this is not a valid loop and if so
213+
// returns an error describing why. This function requires the Loops ShapeIndex
214+
// to have been intialized.
215+
func (l *Loop) findValidationError() error {
216+
if err := l.findValidationErrorNoIndex(); err != nil {
217+
return err
218+
}
219+
// Check for intersections between non-adjacent edges (including at vertices)
220+
// TODO(roberts): Once shapeutil gets findAnyCrossing uncomment this.
221+
// return findAnyCrossing(l.index)
222+
return nil
223+
}
224+
225+
// findValidationErrorNoIndex reports whether this is not a valid loop, but
226+
// skips checks that would require a ShapeIndex to be built for the loop. This
227+
// is primarily used by Polygon to do validation so it doesn't trigger the
228+
// creation of unneeded ShapeIndices.
229+
func (l *Loop) findValidationErrorNoIndex() error {
230+
// All vertices must be unit length.
231+
for i, v := range l.vertices {
232+
if !v.IsUnit() {
233+
return fmt.Errorf("vertex %d is not unit length", i)
234+
}
235+
}
236+
237+
// Loops must have at least 3 vertices (except for empty and full).
238+
if len(l.vertices) < 3 {
239+
if l.isEmptyOrFull() {
240+
return nil // Skip remaining tests.
241+
}
242+
return fmt.Errorf("non-empty, non-full loops must have at least 3 vertices")
243+
}
244+
245+
// Loops are not allowed to have any duplicate vertices or edge crossings.
246+
// We split this check into two parts. First we check that no edge is
247+
// degenerate (identical endpoints). Then we check that there are no
248+
// intersections between non-adjacent edges (including at vertices). The
249+
// second check needs the ShapeIndex, so it does not fall within the scope
250+
// of this method.
251+
for i, v := range l.vertices {
252+
if v == l.Vertex(i+1) {
253+
return fmt.Errorf("edge %d is degenerate (duplicate vertex)", i)
254+
}
255+
}
256+
257+
return nil
258+
}
259+
207260
// ContainsOrigin reports true if this loop contains s2.OriginPoint().
208261
func (l *Loop) ContainsOrigin() bool {
209262
return l.originInside

s2/loop_test.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1582,6 +1582,63 @@ func TestLoopNormalizedCompatibleWithContains(t *testing.T) {
15821582
}
15831583
}
15841584

1585+
func TestLoopIsValidDetectsInvalidLoops(t *testing.T) {
1586+
tests := []struct {
1587+
msg string
1588+
points []Point
1589+
}{
1590+
// Not enough vertices. Note that all single-vertex loops are valid; they
1591+
// are interpreted as being either "empty" or "full".
1592+
{
1593+
msg: "loop has no vertices",
1594+
points: parsePoints(""),
1595+
},
1596+
{
1597+
msg: "loop has too few vertices",
1598+
points: parsePoints("20:20, 21:21"),
1599+
},
1600+
// degenerate edge checks happen in validation before duplicate vertices.
1601+
{
1602+
msg: "loop has degenerate first edge",
1603+
points: parsePoints("20:20, 20:20, 20:21"),
1604+
},
1605+
{
1606+
msg: "loop has degenerate third edge",
1607+
points: parsePoints("20:20, 20:21, 20:20"),
1608+
},
1609+
// TODO(roberts): Uncomment these cases when FindAnyCrossings is in.
1610+
/*
1611+
{
1612+
msg: "loop has duplicate points",
1613+
points: parsePoints("20:20, 21:21, 21:20, 20:20, 20:21"),
1614+
},
1615+
{
1616+
msg: "loop has crossing edges",
1617+
points: parsePoints("20:20, 21:21, 21:20.5, 21:20, 20:21"),
1618+
},
1619+
*/
1620+
{
1621+
// Ensure points are not normalized.
1622+
msg: "loop with non-normalized vertices",
1623+
points: []Point{
1624+
Point{r3.Vector{2, 0, 0}},
1625+
Point{r3.Vector{0, 1, 0}},
1626+
Point{r3.Vector{0, 0, 1}},
1627+
},
1628+
},
1629+
}
1630+
1631+
for _, test := range tests {
1632+
loop := LoopFromPoints(test.points)
1633+
err := loop.findValidationError()
1634+
if err == nil {
1635+
t.Errorf("%s. %v.findValidationError() = nil, want err to be non-nil", test.msg, loop)
1636+
}
1637+
// The C++ tests also tests that the returned error message string contains
1638+
// a specific set of text. That part of the test is skipped here.
1639+
}
1640+
}
1641+
15851642
const (
15861643
// TODO(roberts): Convert these into changeable flags or parameters.
15871644
// A loop with a 10km radius and 4096 vertices has an edge length of 15 meters.

0 commit comments

Comments
 (0)