Skip to content

Commit

Permalink
s2: Add Polyline.Project and Polyline.IsOnRight.
Browse files Browse the repository at this point in the history
Signed-off-by: David Symonds <dsymonds@golang.org>
  • Loading branch information
rsned authored and dsymonds committed Aug 1, 2018
1 parent b97bd2a commit 0bf2f2a
Show file tree
Hide file tree
Showing 2 changed files with 125 additions and 3 deletions.
59 changes: 56 additions & 3 deletions s2/polyline.go
Expand Up @@ -381,13 +381,66 @@ func (p *Polyline) decode(d decoder) {
}
}

// Project returns a point on the polyline that is closest to the given point,
// and the index of the next vertex after the projected point. The
// value of that index is always in the range [1, len(polyline)].
// The polyline must not be empty.
func (p *Polyline) Project(point Point) (Point, int) {
if len(*p) == 1 {
// If there is only one vertex, it is always closest to any given point.
return (*p)[0], 1
}

// Initial value larger than any possible distance on the unit sphere.
minDist := 10 * s1.Degree
minIndex := -1

// Find the line segment in the polyline that is closest to the point given.
for i := 1; i < len(*p); i++ {
if dist := DistanceFromSegment(point, (*p)[i-1], (*p)[i]); dist < minDist {
minDist = dist
minIndex = i
}
}

// Compute the point on the segment found that is closest to the point given.
closest := Project(point, (*p)[minIndex-1], (*p)[minIndex])
if closest == (*p)[minIndex] {
minIndex++
}

return closest, minIndex
}

// IsOnRight reports whether the point given is on the right hand side of the
// polyline, using a naive definition of "right-hand-sideness" where the point
// is on the RHS of the polyline iff the point is on the RHS of the line segment
// in the polyline which it is closest to.
// The polyline must have at least 2 vertices.
func (p *Polyline) IsOnRight(point Point) bool {
// If the closest point C is an interior vertex of the polyline, let B and D
// be the previous and next vertices. The given point P is on the right of
// the polyline (locally) if B, P, D are ordered CCW around vertex C.
closest, next := p.Project(point)
if closest == (*p)[next-1] && next > 1 && next < len(*p) {
if point == (*p)[next-1] {
// Polyline vertices are not on the RHS.
return false
}
return OrderedCCW((*p)[next-2], point, (*p)[next], (*p)[next-1])
}
// Otherwise, the closest point C is incident to exactly one polyline edge.
// We test the point P against that edge.
if next == len(*p) {
next--
}
return Sign(point, (*p)[next], (*p)[next-1])
}

// TODO(roberts): Differences from C++.
// IsValid
// Suffix
// Interpolate/UnInterpolate
// Project
// IsPointOnRight
// Intersects(Polyline)
// Reverse
// ApproxEqual
// NearlyCoversPolyline
69 changes: 69 additions & 0 deletions s2/polyline_test.go
Expand Up @@ -284,3 +284,72 @@ func TestPolylineSubsample(t *testing.T) {
}
}
}

func TestProject(t *testing.T) {
latlngs := []LatLng{
LatLngFromDegrees(0, 0),
LatLngFromDegrees(0, 1),
LatLngFromDegrees(0, 2),
LatLngFromDegrees(1, 2),
}
line := PolylineFromLatLngs(latlngs)
tests := []struct {
haveLatLng LatLng
wantProjection LatLng
wantNext int
}{
{LatLngFromDegrees(0.5, -0.5), LatLngFromDegrees(0, 0), 1},
{LatLngFromDegrees(0.5, 0.5), LatLngFromDegrees(0, 0.5), 1},
{LatLngFromDegrees(0.5, 1), LatLngFromDegrees(0, 1), 2},
{LatLngFromDegrees(-0.5, 2.5), LatLngFromDegrees(0, 2), 3},
{LatLngFromDegrees(2, 2), LatLngFromDegrees(1, 2), 4},
}
for _, test := range tests {
projection, next := line.Project(PointFromLatLng(test.haveLatLng))
if !PointFromLatLng(test.wantProjection).ApproxEqual(projection) {
t.Errorf("line.Project(%v) = %v, want %v", test.haveLatLng, projection, test.wantProjection)
}
if next != test.wantNext {
t.Errorf("line.Project(%v) = %v, want %v", test.haveLatLng, next, test.wantNext)
}
}
}

func TestIsOnRight(t *testing.T) {
latlngs := []LatLng{
LatLngFromDegrees(0, 0),
LatLngFromDegrees(0, 1),
LatLngFromDegrees(0, 2),
LatLngFromDegrees(1, 2),
}
line1 := PolylineFromLatLngs(latlngs)
latlngs = []LatLng{
LatLngFromDegrees(0, 0),
LatLngFromDegrees(0, 1),
LatLngFromDegrees(-1, 0),
}
line2 := PolylineFromLatLngs(latlngs)
tests := []struct {
line *Polyline
haveLatLng LatLng
wantOnRight bool
}{
{line1, LatLngFromDegrees(-0.5, 0.5), true},
{line1, LatLngFromDegrees(0.5, -0.5), false},
{line1, LatLngFromDegrees(0.5, 0.5), false},
{line1, LatLngFromDegrees(0.5, 1.0), false},
{line1, LatLngFromDegrees(-0.5, 2.5), true},
{line1, LatLngFromDegrees(1.5, 2.5), true},
// Explicitly test the case where the closest point is an interior vertex.
// The points are chosen such that they are on different sides of the two
// edges that the interior vertex is on.
{line2, LatLngFromDegrees(-0.5, 5.0), false},
{line2, LatLngFromDegrees(5.5, 5.0), false},
}
for _, test := range tests {
onRight := test.line.IsOnRight(PointFromLatLng(test.haveLatLng))
if onRight != test.wantOnRight {
t.Errorf("line.IsOnRight(%v) = %v, want %v", test.haveLatLng, onRight, test.wantOnRight)
}
}
}

0 comments on commit 0bf2f2a

Please sign in to comment.