Skip to content

Commit

Permalink
smartclip: implicitly close rings that have endpoints in the bound
Browse files Browse the repository at this point in the history
  • Loading branch information
paulmach committed Feb 24, 2018
1 parent b03507a commit 1f34bf6
Show file tree
Hide file tree
Showing 2 changed files with 30 additions and 3 deletions.
15 changes: 12 additions & 3 deletions clip/smartclip/smart.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ import (
)

// Geometry will do a smart more involved clipping and wrapping of the geometry.
// It will return simple OGC geometries.
// It will return simple OGC geometries. Rings that are NOT closed AND have an
// endpoint in the bound will be implicitly closed.
func Geometry(box orb.Bound, g orb.Geometry, o orb.Orientation) orb.Geometry {
if g == nil {
return nil
Expand Down Expand Up @@ -61,7 +62,8 @@ func Geometry(box orb.Bound, g orb.Geometry, o orb.Orientation) orb.Geometry {
}

// Ring will smart clip a ring to the boundary. This may result multiple rings so
// a multipolygon is possible.
// a multipolygon is possible. Rings that are NOT closed AND have an endpoint in
// the bound will be implicitly closed.
func Ring(box orb.Bound, r orb.Ring, o orb.Orientation) orb.MultiPolygon {
if len(r) == 0 {
return nil
Expand All @@ -82,6 +84,8 @@ func Ring(box orb.Bound, r orb.Ring, o orb.Orientation) orb.MultiPolygon {
}

// Polygon will smart clip a polygon to the bound.
// Rings that are NOT closed AND have an endpoint in the bound will be
// implicitly closed.
func Polygon(box orb.Bound, p orb.Polygon, o orb.Orientation) orb.MultiPolygon {
if len(p) == 0 {
return nil
Expand Down Expand Up @@ -110,6 +114,8 @@ func Polygon(box orb.Bound, p orb.Polygon, o orb.Orientation) orb.MultiPolygon {
}

// MultiPolygon will smart clip a multipolygon to the bound.
// Rings that are NOT closed AND have an endpoint in the bound will be
// implicitly closed.
func MultiPolygon(box orb.Bound, mp orb.MultiPolygon, o orb.Orientation) orb.MultiPolygon {
if len(mp) == 0 {
return nil
Expand Down Expand Up @@ -160,6 +166,9 @@ func MultiPolygon(box orb.Bound, mp orb.MultiPolygon, o orb.Orientation) orb.Mul
func clipRings(box orb.Bound, rings []orb.Ring) (open []orb.LineString, closed []orb.Ring) {
var result []orb.LineString
for _, r := range rings {
if !r.Closed() && (box.Contains(r[0]) || box.Contains(r[len(r)-1])) {
r = append(r, r[0])
}
out := clip.LineString(box, orb.LineString(r), clip.OpenBound(true))
if len(out) == 0 {
continue // outside of bound
Expand Down Expand Up @@ -277,7 +286,7 @@ func smartWrap(box orb.Bound, input []orb.LineString, o orb.Orientation) orb.Mul
)

// this operation is O(n^2). Technically we could use a linked list
// and remove poitns instead of marking them as "used".
// and remove points instead of marking them as "used".
// However since n is 2x the number of segements I think we're okay.
for i := 0; i < 2*len(points); i++ {
ep := points[i%len(points)]
Expand Down
18 changes: 18 additions & 0 deletions clip/smartclip/smart_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,24 @@ func TestRing(t *testing.T) {
input: orb.Ring{{2, 2}, {3, 2}, {3, 3}, {2, 3}, {2, 2}},
expected: orb.MultiPolygon{{{{2, 2}, {3, 2}, {3, 3}, {2, 3}, {2, 2}}}},
},
{
name: "not closed with endpoints inside bound",
bound: oneSix,
input: orb.Ring{{2, 2}, {8, 2}, {8, 3}, {2, 3}},
expected: orb.MultiPolygon{{{{2, 2}, {6, 2}, {6, 3}, {2, 3}, {2, 2}}}},
},
{
name: "not closed with first endpoint inside bound",
bound: oneSix,
input: orb.Ring{{2, 2}, {8, 2}, {8, 3}, {0, 3}},
expected: orb.MultiPolygon{{{{6, 3}, {1, 3}, {1, 2.5}, {2, 2}, {6, 2}, {6, 3}}}},
},
{
name: "not closed with last endpoint inside bound",
bound: oneSix,
input: orb.Ring{{0, 2}, {8, 2}, {8, 3}, {2, 3}},
expected: orb.MultiPolygon{{{{6, 3}, {2, 3}, {1, 2.5}, {1, 2}, {6, 2}, {6, 3}}}},
},
{
name: "intersects one side",
bound: oneSix,
Expand Down

0 comments on commit 1f34bf6

Please sign in to comment.