diff --git a/container/list/point/list/point.go b/container/list/point/list/point.go index 543b42d82..6c1e38143 100644 --- a/container/list/point/list/point.go +++ b/container/list/point/list/point.go @@ -8,6 +8,10 @@ import ( "github.com/terranodo/tegola/maths" ) +type Elementer interface { + list.Elementer +} + type ElementerPointer interface { list.Elementer maths.Pointer @@ -161,7 +165,7 @@ func (l *List) GoString() string { strs := []string{"List{"} for p := l.Front(); p != nil; p = p.Next() { pt := p.(maths.Pointer) - strs = append(strs, fmt.Sprintf("%v(%p)", pt.Point(), p)) + strs = append(strs, fmt.Sprintf("%v(%p:%[2]T)", pt.Point(), p)) } strs = append(strs, "}") return strings.Join(strs, "") diff --git a/maths/clip/clip.go b/maths/clip/clip.go new file mode 100644 index 000000000..d67f8fed5 --- /dev/null +++ b/maths/clip/clip.go @@ -0,0 +1,385 @@ +package clip + +import ( + "github.com/terranodo/tegola/maths" + "github.com/terranodo/tegola/maths/clip/intersect" + "github.com/terranodo/tegola/maths/clip/region" + "github.com/terranodo/tegola/maths/clip/subject" +) + +/* +Basics of the alogrithim. + +Given: + +Clipping polygon + +Subject polygon + +Result: + +One or more polygons clipped into the clipping polygon. + + +for each vertex for the clipping and subject polygon create a link list. + +Say you have the following: + + k——m + β---------|--|-ℽ + | | | | + a——————|———b | | | + | | | | | | + | | | | | | + | d——|———c g———h | | + | | | | | | + | | | | | | + | | α-----|------|-δ + | e————————f | + | | + p———————————————————n + + We will create the following linked lists: + + a → b → c → d → e → f → g → h → k → m → n → p → a + α → β → ℽ → δ → α + + +Now, we will iterate from through the vertices of the subject polygon (a to b, etc…) look for point of intersection with the +clipping polygon (α,β,ℽ,δ). When we come upon an intersection, we will insert an intersection point into both lists. + +For example, examing vertex a, and b; against the line formed by (α,β). We notice we have an intersection at I. + + k——m + β---------|--|-ℽ + | | | | + a——————I———b | | | + | | | | | | + | | | | | | + | d——————c g———h | | + | | | | | | + | | | | | | + | | α-----|------|-δ + | e————————f | + | | + p———————————————————n + + We will add I to both lists. We will also note that I, in heading into the clipping region. + (We will also, mark a as being outside the clipping region, and b being inside the clipping region. If the point is on the boarder of the clipping polygon, it is considered outside of the clipping region.) + + a → I → b → c → d → e → f → g → h → k → m → n → p → a + α → I → β → ℽ → δ → α + + We will also keep track of the intersections, and weather they are inbound or outbound. + I(i) + +We will check (a,b) against the line formed by (β,ℽ). And see there isn't an intersection. +We will check (a,b) against the line formed by (ℽ,δ). And see there isn't an intersection. +We will check (a,b) against the line formed by (δ,α). And see there isn't an intersection. + +When we look at (b,c) we notice that they are both inside the clipping region. And move on to the next set of vertices. + +We looking at (c,d), we notice that c is inside and d is outside. This means that there is an intersection point head out. +We check against the line formed by (α,β), and add a Point J, after checking to see we don't already have another equi to J; and adjust +the pointers accordingly. The point c in the subject will now point to J, and J will point to d. And for the intersecting line, α will now point +to J, and J will point to I, as that is what α was pointing to. Our lists will now look like the following. + + a → I → b → c → J → d → e → f → g → h → k → m → n → p → a + α → J → I → β → ℽ → δ → α + + I(i), J(o) + +We will check (c,d) against the line formed by (β,ℽ). And see there isn't an intersection. +We will check (c,d) against the line formed by (ℽ,δ). And see there isn't an intersection. +We will check (c,d) against the line formed by (δ,α). We see there is an intersection, but it is outside of the clipping area. + +Next we look at (d,e), notice they are both outside the clipping area, and don't cross through the clipping aread. Thus we can ignore +the points. + + k——m + β---------|--|-ℽ + | | | | + a——————I———b | | | + | | | | | | + | | | | | | + | d——J———c g———h | | + | | | | | | + | | | | | | + | | α-----|------|-δ + | e————————f | + | | + p———————————————————n + + +Next we look at (e,f), and just (d,e) we can ignore the points as they lie outside and don't cross the clipping area. + +Now we look at (f,g), we notice that f is outside, and g is inside the clipping area. This means that The intersection is entering into the clipping area. + +We will check (f,g) against the line formed by (α,β). And see there isn't an intersection. +We will check (f,g) against the line formed by (β,ℽ). And see there isn't an intersection. +We will check (f,g) against the line formed by (ℽ,δ). And see there isn't an intersection. + + k——m + β---------|--|-ℽ + | | | | + a——————I———b | | | + | | | | | | + | | | | | | + | d——J———c g———h | | + | | | | | | + | | | | | | + | | α-----K------|-δ + | e————————f | + | | + p———————————————————n + +We will check (f,g) against the line formed by (δ,α). We see there is an intersection, and from the previous statement, we know it's an intersection point that is heading inwards. We adjust the link lists to include the point. + + a → I → b → c → J → d → e → f → K → g → h → k → m → n → p → a + α → J → I → β → ℽ → δ → K → α + + I(i), J(o), K(i) + +Looking at (g,h) we realize they are both in the clipping area, and can ignore them. +Next we look at (h,k), here we see that h is inside and k is outside. This means that the intersection point will be outbound. + +We will check (h,k) against the line formed by (α,β). And see there isn't an intersection. + + k——m + β---------L--|-ℽ + | | | | + a——————I———b | | | + | | | | | | + | | | | | | + | d——J———c g———h | | + | | | | | | + | | | | | | + | | α-----K------|-δ + | e————————f | + | | + p———————————————————n + +We will check (h,k) against the line formed by (β,ℽ). We see there is an intersection (L); also, note that we can stop look at the points, as we found the intersection. We adjust the link lists to include the point. + + a → I → b → c → J → d → e → f → K → g → h → L → k → m → n → p → a + α → J → I → β → L → ℽ → δ → K → α + + I(i), J(o), K(i), L(o), + + +Next we look at (k,m) and notice they are not crossing the clipping area and are both outside. So, we know we can skip them. + +Looking at (m,n); we notice they are both outside, but are crossing the clipping area, which means there will be two intersection points. + +We will check (f,g) against the line formed by (α,β). And see there isn't an intersection. + + k——m + β---------L--M-ℽ + | | | | + a——————I———b | | | + | | | | | | + | | | | | | + | d——J———c g———h | | + | | | | | | + | | | | | | + | | α-----K------|-δ + | e————————f | + | | + p———————————————————n + + +We will check (f,g) against the line formed by (β,ℽ). We find our first intersection point. We go ahead and insert point (M), as we have done for the other points. We know it's bound as it's the first point in the crossing. +We, adjust, the point we are comparing against from (m,n) to (M,n). Also, note we need to place the point in the correct position between β and ℽ, after L. + + a → I → b → c → J → d → e → f → K → g → h → L → k → m → M → n → p → a + α → J → I → β → L → M → ℽ → δ → K → α + + I(i), J(o), K(i), L(o), M(i) + + +We will check (M,n) against the line formed by (ℽ,δ). And see there isn't an intersection. + + k——m + β---------L--M-ℽ + | | | | + a——————I———b | | | + | | | | | | + | | | | | | + | d——J———c g———h | | + | | | | | | + | | | | | | + | | α-----K------N-δ + | e————————f | + | | + p———————————————————n + +We will check (M,n) against the line formed by (δ,α). We see there is an intersection, and from the previous statement, we know it's an intersection point that is heading outwards. We adjust the link lists to include the point. + + a → I → b → c → J → d → e → f → K → g → h → L → k → m → M → N → n → p → a + α → J → I → β → L → M → ℽ → δ → N → K → α + + I(i), J(o), K(i), L(o), M(i), N(o) + +Next we look at (n,p), and know we can skip the points as they are both outside, and not crossing the clipping area. +Finally we look at (p,a), and again because they are both outside, and not corssing the clipping area, we know we can skip them. + +Finally we check to see if we have at least one external and one internal point. If we don't have any external points, we know the polygon is contained within the clipping area and can just return it. +If we don't have any internal points, and no Intersections points, we know the polygon is contained compleatly outside and we can return any empty array of polygons. + +First thing we do is iterate our list of Intersection points looking for the first point that is an inward bound point. I is such a point. The rule is if an intersection point is inward, we following the subject links, if it's outward we follow, the clipping links. +Since I is inward, we write it down, and follow the subject link to b. + +LineString1: I,b + +Then we follow the links till we get to the next Intersection point. + +LineString1: I,b,c,J + + •··············• + · · + +———+ · + | | · + | | · + +———+ · + · · + · · + •··············• + + + +Since, J is outward we follow the clipping links, which leads us to I. Since I is also the first point in this line string. We know we are done, with the first clipped polygon. + +Next we iterate to the next inward Intersection point from J, to K. +LineString1: I,b,c,J +LineString2: K + +And as before since K is inward point we follow the subject polygon points, till we get to an intersection point. + +LineString1: I,b,c,J +LineString2: K,g,h,L + +As L is an outward intersection point we follow the clipping polygon points, till we get to an intersection point. + +LineString1: I,b,c,J +LineString2: K,g,h,L,M + +As M is an inward intersection point we follow the subject. + +LineString1: I,b,c,J +LineString2: K,g,h,L,M,N + +As N is an outward intersection point we follow the clipping, and discover that the point is our starting Intersection point K. That ends is our second clipped polygon. + +LineString1: I,b,c,J +LineString2: K,g,h,L,M,N + + + •·········+--+·• + · | | · + +———+ | | · + | | | | · + | | | | · + +———+ +———+ | · + · | | · + · | | · + •·····+------+·• + + +Since N(o), is the end of the array we, start at the beginning and notice, that we already accounted for I(i). And so, we are done. + +*/ + +func LineString(w maths.WindingOrder, sub []float64, rMinPt, rMaxPt maths.Pt) (clippedSubjects [][]float64, err error) { + il := intersect.New() + rl := region.New(w, rMinPt, rMaxPt) + sl, err := subject.New(w, sub) + if err != nil { + return clippedSubjects, err + } + + // log.Println("Starting to work through the pair of points.") + allSubjectsPtsIn := true + for p, i := sl.FirstPair(), 0; p != nil; p, i = p.Next(), i+1 { + // log.Printf("Looking pair %v : %#v ", i, p) + line := p.AsLine() + if !rl.Contains(p.Pt1().Point()) { + allSubjectsPtsIn = false + } + for a := rl.FirstAxis(); a != nil; a = a.Next() { + pt, doesIntersect := a.Intersect(line) + if !doesIntersect { + continue + } + ipt := intersect.NewPt(pt, a.IsInward(line)) + // log.Printf("Found Intersect (%p)%[1]v\n", ipt) + // Only care about inbound intersect points. + if ipt.Inward { + il.PushBack(ipt) + } + p.PushInBetween(ipt.AsSubjectPoint()) + a.PushInBetween(ipt.AsRegionPoint()) + } + } + /* + log.Printf("Done working through the pair of points. allSubjectsPtsIn %v\n", allSubjectsPtsIn) + log.Printf("intersect: %#v\n", il) + log.Printf(" region: %#v\n", rl) + log.Printf(" region: ") + for p := rl.Front(); p != nil; p = p.Next() { + switch pp := p.(type) { + case *intersect.RegionPoint: + log.Printf("\t%p - (%v;%v)", pp, pp.Point(), pp.Inward) + case *intersect.SubjectPoint: + log.Printf("\t%p - (%v;%v)", pp, pp.Point(), pp.Inward) + case list.ElementerPointer: + log.Printf("\t%p - (%v)", pp, pp.Point()) + default: + log.Printf("\t%p - %[1]#v\n", p) + } + } + log.Printf(" subject: %#v\n", sl) + */ + // Check to see if all the subject points are contained in the region. + if allSubjectsPtsIn { + clippedSubjects = append(clippedSubjects, sub) + return clippedSubjects, nil + } + // Need to check if there are no intersection points, it could be for two reason. + // 2. The region points are all inside the subject. + if il.Len() == 0 { + for _, pt := range rl.SentinalPoints() { + if !sl.Contains(pt) { + // log.Printf("pt(%v)(%v) was not contained in subject(%#v).", i, pt, sl) + // Not all region points are contain by the subject, so none of the subject points must be in the region. + return clippedSubjects, nil + } + } + // All region points are in the subject, so just return the region. + clippedSubjects = append(clippedSubjects, rl.LineString()) + return clippedSubjects, nil + } + // log.Println("Walking through the Inbound Intersection points.") + for w := il.FirstInboundPtWalker(); w != nil; w = w.Next() { + // log.Printf("Looking at: %p", w) + var s []float64 + var opt *maths.Pt + w.Walk(func(idx int, pt maths.Pt) bool { + if opt == nil || !opt.IsEqual(pt) { + // Only add point if it's not the same as the last point + // log.Printf("Adding point(%v): %v\n", idx, pt) + s = append(s, pt.X, pt.Y) + } + opt = &pt + if idx == sl.Len() { + return false + } + return true + }) + // Must have at least 3 points for it to be a valid runstring. (3 *2 = 6) + if len(s) > 6 { + clippedSubjects = append(clippedSubjects, s) + } + } + // log.Println("Done walking through the Inbound Intersection points.") + return clippedSubjects, nil +} diff --git a/maths/clip/intersect/intersect.go b/maths/clip/intersect/intersect.go index 3b15d9ca0..33dde8ae0 100644 --- a/maths/clip/intersect/intersect.go +++ b/maths/clip/intersect/intersect.go @@ -1,8 +1,6 @@ package intersect -import ( - "github.com/terranodo/tegola/container/list/point/list" -) +import "github.com/terranodo/tegola/container/list/point/list" type Intersect struct { list.List @@ -13,3 +11,18 @@ func New() *Intersect { l.List.Init() return l } + +func (i *Intersect) FirstInboundPtWalker() *Inbound { + if i == nil || i.Len() == 0 { + return nil + } + // We have to find the first Inbound Pt. + var ok bool + var pt *Point + for p := i.Front(); p != nil; p = p.Next() { + if pt, ok = p.(*Point); ok && pt.Inward { + break + } + } + return NewInbound(pt) +} diff --git a/maths/clip/intersect/intersect_test.go b/maths/clip/intersect/intersect_test.go index 876fce131..283da6675 100644 --- a/maths/clip/intersect/intersect_test.go +++ b/maths/clip/intersect/intersect_test.go @@ -25,5 +25,15 @@ func TestNewIntersect(t *testing.T) { log.Printf("intersect: %#v\n", l) log.Printf("region: %#v\n", rl) log.Printf("subject: %#v\n", sl) + for ib := l.FirstInboundPtWalker(); ib != nil; ib = ib.Next() { + log.Printf("InBound %#v\n", ib) + ib.Walk(func(idx int, pt maths.Pt) bool { + log.Printf("Pt %v: %v\n", idx, pt) + if idx == 10 { + return false + } + return true + }) + } } diff --git a/maths/clip/intersect/point.go b/maths/clip/intersect/point.go index 38a15382d..e7d686fc6 100644 --- a/maths/clip/intersect/point.go +++ b/maths/clip/intersect/point.go @@ -13,7 +13,6 @@ type Point struct { ptList.Pt Inward bool - Seen bool subject list.Sentinel region list.Sentinel @@ -26,11 +25,18 @@ func NewPoint(x, y float64, inward bool) *Point { return &Point{Pt: *ptList.NewPoint(x, y), Inward: inward} } -func (i *Point) String() string { - return fmt.Sprintf("Intersec{ X: %v, Y: %v, Inward: %v}", i.Pt.X, i.Pt.Y, i.Inward) +func (p *Point) String() string { + return fmt.Sprintf("Intersec{ X: %v, Y: %v, Inward: %v}", p.Pt.X, p.Pt.Y, p.Inward) +} +func (p *Point) AsSubjectPoint() *SubjectPoint { return (*SubjectPoint)(p) } +func (p *Point) AsRegionPoint() *RegionPoint { return (*RegionPoint)(p) } + +func (p *Point) NextWalk() list.Elementer { + if p.Inward { + return p.subject.Next() + } + return p.region.Next() } -func (i *Point) AsSubjectPoint() *SubjectPoint { return (*SubjectPoint)(i) } -func (i *Point) AsRegionPoint() *RegionPoint { return (*RegionPoint)(i) } /* func (i *Point) Walk() (w Walker) { @@ -67,6 +73,9 @@ func (i *RegionPoint) AsSubjectPoint() *SubjectPoint { } func (i *RegionPoint) AsIntersectPoint() *Point { return (*Point)(i) } func (i *RegionPoint) Point() maths.Pt { return i.Pt.Point() } +func (i *RegionPoint) GoString() string { + return fmt.Sprintf("%T(%[1]p)[%v;%v]", i, i.Point(), i.Inward) +} // SubjectPoing causes an intersect point to "act" like a subject point so that it can be inserted into a subject list. type SubjectPoint Point @@ -86,3 +95,6 @@ func (i *SubjectPoint) AsRegionPoint() *RegionPoint { } func (i *SubjectPoint) AsIntersectPoint() *Point { return (*Point)(i) } func (i *SubjectPoint) Point() maths.Pt { return i.Pt.Point() } +func (i *SubjectPoint) GoString() string { + return fmt.Sprintf("%T(%[1]p)[%v;%v]", i, i.Point(), i.Inward) +} diff --git a/maths/clip/intersect/walker.go b/maths/clip/intersect/walker.go index 4009afbfb..7da50a5ab 100644 --- a/maths/clip/intersect/walker.go +++ b/maths/clip/intersect/walker.go @@ -1,4 +1,89 @@ package intersect -type Walker struct { +import ( + "github.com/terranodo/tegola/container/list/point/list" + + "github.com/terranodo/tegola/maths" +) + +type Inbound struct { + pt *Point +} + +func NewInbound(pt *Point) *Inbound { + if pt == nil { + return nil + } + return &Inbound{pt: pt} +} + +func (ib *Inbound) Next() (nib *Inbound) { + var pt *Point + var ok bool + for p := ib.pt.Next(); p != nil; p = p.Next() { + pt, ok = p.(*Point) + if !ok { + pt = nil + continue + } + if pt.Inward { + return NewInbound(pt) + } + } + return nil +} + +func (ib *Inbound) Walk(fn func(idx int, pt maths.Pt) bool) { + fib := ib.pt + if !fn(0, fib.Point()) { + return + } + var pt maths.Pt + var ipt *Point + seen := make(map[list.Elementer]bool) + for p, i := fib.NextWalk(), 1; p != nil; i++ { + op := p + if seen[p] { + //log.Printf("Already saw %p -- cycle bailing.\n", p) + return + } + seen[p] = true + switch ppt := p.(type) { + case *Point: + ipt = ppt + case *SubjectPoint: + ipt = ppt.AsIntersectPoint() + case *RegionPoint: + ipt = ppt.AsIntersectPoint() + case list.ElementerPointer: + ipt = nil + pt = ppt.Point() + p = ppt.Next() + default: + continue + } + if ipt == fib { + return + } + if ipt != nil { + if ipt.Inward { + ib.pt = ipt + } + pt = ipt.Point() + p = ipt.NextWalk() + } + if fib.Point().IsEqual(pt) { + //log.Println("fib point value is same.") + return + } + + if p == nil { + p = op.List().Front() + } + //log.Printf("Looking Point(%v) looking at pt(%p)%[2]v fib(%p)%[3]v\n", i, p, fib) + + if !fn(i, pt) { + return + } + } } diff --git a/maths/clip/region/axis.go b/maths/clip/region/axis.go index 0145a377c..a1c567d62 100644 --- a/maths/clip/region/axis.go +++ b/maths/clip/region/axis.go @@ -30,20 +30,20 @@ func (a *axis) PushInBetween(pt list.ElementerPointer) bool { func (a *axis) AsLine() maths.Line { return maths.Line{a.pt0.Point(), a.pt1.Point()} } // Intersect finds the intersections point if one exists with the line described by pt0,pt1. This point will be clamped to the line of the clipping region. -func (a *axis) Intersect(pt0, pt1 maths.Pointer) (x, y float64, doesIntersect bool) { - var pt maths.Pt - line := maths.Line{pt0.Point(), pt1.Point()} +func (a *axis) Intersect(line maths.Line) (pt maths.Pt, doesIntersect bool) { axisLine := a.AsLine() if pt, doesIntersect = maths.Intersect(axisLine, line); !doesIntersect { - return pt.X, pt.Y, doesIntersect + return pt, doesIntersect } // Now we need to make sure that the point in between the end points of the line. if !line.InBetween(pt) { - return pt.X, pt.Y, false + return pt, false } - // Clamp the point to the axis that it crosses… - pt = axisLine.Clamp(pt) - return pt.X, pt.Y, true + // Make sure the pt is on the axis as well + if !axisLine.InBetween(pt) { + return pt, false + } + return pt, true } /* @@ -67,30 +67,41 @@ func (a *axis) Intersect(pt0, pt1 maths.Pointer) (x, y float64, doesIntersect bo |_____| 1pt 1 2pt */ +func (a *axis) staticCoord() float64 { + switch a.idx % 4 { + case 0, 2: + return a.pt0.X + case 1, 3: + return a.pt0.Y + } + return 0 +} + +func (a *axis) inside(pt maths.Pt) bool { -// IsInward returns weather the line described by pt1,pt2 is headed inward with respect to the axis. -func (a *axis) IsInward(pt1, pt2 maths.Pointer) bool { - p1, p2 := pt1.Point(), pt2.Point() switch a.idx % 4 { - // There is no change in x for case 0, and 2 as they are the y axises for either clockwise or counter clockwise. case 0: - // check to see if the p1.X is less then a.pts[0].X and p2.X is greater then a.pts[0].X - return p1.X <= a.pt0.X && a.pt1.X <= p2.X - case 2: - // for case two it's oppsite of case 1. - return p2.X <= a.pt0.X && a.pt1.X <= p1.X - // + return pt.X > a.staticCoord() case 1: if a.winding.IsClockwise() { - return p1.Y <= a.pt0.Y && a.pt1.Y <= p2.Y + return pt.Y > a.staticCoord() } - return p2.Y <= a.pt0.Y && a.pt1.Y <= p1.Y + return pt.Y < a.staticCoord() + case 2: + return pt.X < a.staticCoord() case 3: if a.winding.IsClockwise() { - return p2.Y <= a.pt0.Y && a.pt1.Y <= p1.Y + return pt.Y < a.staticCoord() } - return p1.Y <= a.pt0.Y && a.pt1.Y <= p2.Y - default: - return false + return pt.Y > a.staticCoord() } + return false +} +func (a *axis) outside(pt maths.Pt) bool { return !a.inside(pt) } + +// IsInward returns weather the line described by pt1,pt2 is headed inward with respect to the axis. +func (a *axis) IsInward(line maths.Line) bool { + p1, p2 := line[0], line[1] + p1out, p2in := a.outside(p1), a.inside(p2) + return p1out && p2in } diff --git a/maths/clip/region/axis_test.go b/maths/clip/region/axis_test.go new file mode 100644 index 000000000..e1d91c91d --- /dev/null +++ b/maths/clip/region/axis_test.go @@ -0,0 +1,36 @@ +package region + +import ( + "testing" + + "github.com/gdey/tbl" + "github.com/terranodo/tegola/maths" +) + +func TestAxis(t *testing.T) { + type testcase struct { + line maths.Line + doesIntersect [4]bool + pt [4]maths.Pt + } + + r := New(maths.CounterClockwise, maths.Pt{5, 2}, maths.Pt{11, 9}) + + test := tbl.Cases( + testcase{ + line: maths.Line{maths.Pt{-3, 1}, maths.Pt{-3, 10}}, + doesIntersect: [4]bool{false, false, false, false}, + }, + ) + test.Run(func(idx int, tc testcase) { + for a, i := r.FirstAxis(), 0; a != nil; a, i = a.Next(), i+1 { + pt, ok := a.Intersect(tc.line) + if ok != tc.doesIntersect[i] { + t.Errorf("Test(%v) Does Intersect is not correct got %v [[%v]] want %v", idx, ok, pt, tc.doesIntersect[i]) + } + if tc.doesIntersect[i] && !tc.pt[i].IsEqual(pt) { + t.Errorf("Test(%v) Point is not correct got %v want %v", idx, pt, tc.pt) + } + } + }) +} diff --git a/maths/clip/region/region.go b/maths/clip/region/region.go index 615fca182..91f8d5bab 100644 --- a/maths/clip/region/region.go +++ b/maths/clip/region/region.go @@ -64,7 +64,12 @@ func (r *Region) Init(winding maths.WindingOrder, Min, Max maths.Pt) *Region { 0pt 3 3pt MinX,MaxY MaxX,MaxY */ - pts = [4][2]float64{[2]float64{Min.X, Max.Y}, [2]float64{Min.X, Min.Y}, [2]float64{Max.X, Min.Y}, [2]float64{Max.X, Max.Y}} + pts = [4][2]float64{ + [2]float64{Min.X, Max.Y}, + [2]float64{Min.X, Min.Y}, + [2]float64{Max.X, Min.Y}, + [2]float64{Max.X, Max.Y}, + } r.aDownOrRight = [4]bool{false, true, true, false} } else { /* @@ -113,3 +118,13 @@ func (r *Region) LineString() []float64 { func (r *Region) Max() maths.Pt { return r.max } func (r *Region) Min() maths.Pt { return r.min } func (r *Region) WindingOrder() maths.WindingOrder { return r.winding } +func (r *Region) Contains(pt maths.Pt) bool { + return r.max.X > pt.X && pt.X > r.min.X && + r.max.Y > pt.Y && pt.Y > r.min.Y +} +func (r *Region) SentinalPoints() (pts []maths.Pt) { + for _, p := range r.sentinelPoints { + pts = append(pts, p.Point()) + } + return pts +} diff --git a/maths/clip/subject/pair.go b/maths/clip/subject/pair.go index 473294447..3948d33af 100644 --- a/maths/clip/subject/pair.go +++ b/maths/clip/subject/pair.go @@ -1,6 +1,11 @@ package subject -import "github.com/terranodo/tegola/container/list/point/list" +import ( + "fmt" + + "github.com/terranodo/tegola/container/list/point/list" + "github.com/terranodo/tegola/maths" +) type pair struct { pts [2]*list.Pt @@ -24,5 +29,13 @@ func (p *pair) Next() *pair { return p } -func (p *pair) Pt1() *list.Pt { return p.pts[0] } -func (p *pair) Pt2() *list.Pt { return p.pts[1] } +func (p *pair) Pt1() *list.Pt { return p.pts[0] } +func (p *pair) Pt2() *list.Pt { return p.pts[1] } +func (p *pair) AsLine() maths.Line { return maths.Line{p.pts[0].Point(), p.pts[1].Point()} } +func (p *pair) PushInBetween(e list.ElementerPointer) bool { + return p.l.PushInBetween(p.pts[0], p.pts[1], e) +} + +func (p *pair) GoString() string { + return fmt.Sprintf("p[%p]( %#v %#v )", p, p.Pt1(), p.Pt2()) +} diff --git a/maths/clip/subject/subject.go b/maths/clip/subject/subject.go index 1d6a9e604..67b65aa99 100644 --- a/maths/clip/subject/subject.go +++ b/maths/clip/subject/subject.go @@ -2,7 +2,6 @@ package subject import ( "fmt" - "log" "github.com/terranodo/tegola/container/list/point/list" "github.com/terranodo/tegola/maths" @@ -31,7 +30,6 @@ func (s *Subject) Init(winding maths.WindingOrder, coords []float64) (*Subject, func (s *Subject) FirstPair() *pair { if s == nil { - log.Println("Subject is nil") return nil } var first, last *list.Pt @@ -49,3 +47,20 @@ func (s *Subject) FirstPair() *pair { } } + +// Contains will test to see if the point if fully contained by the subject. If the point is on the broader it is not considered as contained. +func (s *Subject) Contains(pt maths.Pt) bool { + line := maths.Line{pt, maths.Pt{pt.X - 1, pt.Y}} + count := 0 + for p := s.FirstPair(); p != nil; p = p.Next() { + pline := p.AsLine() + if ipt, ok := maths.Intersect(line, pline); ok { + // We only care about intersect points that are left of the point being tested. + if pline.InBetween(ipt) && ipt.X < pt.X { + count++ + } + } + } + // If it's odd then it's inside of the polygon, otherwise it's outside of the polygon. + return count%2 != 0 +} diff --git a/maths/maths.go b/maths/maths.go index 29ef71f67..33f08674a 100644 --- a/maths/maths.go +++ b/maths/maths.go @@ -77,6 +77,18 @@ func (l Line) InBetween(pt Pt) bool { } return lx <= pt.X && pt.X <= gx && ly <= pt.Y && pt.Y <= gy +} +func (l Line) ExInBetween(pt Pt) bool { + lx, gx := l[0].X, l[1].X + if l[0].X > l[1].X { + lx, gx = l[1].X, l[0].X + } + ly, gy := l[0].Y, l[1].Y + if l[0].Y > l[1].Y { + ly, gy = l[1].Y, l[0].Y + } + return lx < pt.X && pt.X < gx && ly < pt.Y && pt.Y < gy + } //Clamp will return a point that is on the line based on pt. It will do this by restricting each of the coordiantes to the line.