From d5515c789552c8c44df465894b1c9afe6ffa9afb Mon Sep 17 00:00:00 2001 From: Gautam Dey Date: Fri, 17 Mar 2017 16:14:01 +0000 Subject: [PATCH] Mostly Prep work for Validate Polygon code This is mostly prep work for the IsValidate and MakeValidate functions we need for polygons. I think this is one of the issues with the polygons, that the multipolygons we get are not always OGC complient and we are making the assumption that is the case. It has also been pointed out that after scaling it is possible for certain small polygons to flip directions, so we need to insure this does not happen. --- basic/line.go | 39 ++--- basic/polygon.go | 12 +- cmd/printwkb/main.go | 30 +++- cmd/tegola/.gitignore | 4 + isequal.go | 158 ++++++++++++++++++ isequal_test.go | 376 ++++++++++++++++++++++++++++++++++++++++++ maths/clean.go | 67 ++++++++ maths/clean_test.go | 40 +++++ maths/clip/clip.go | 11 +- maths/maths.go | 59 ++++++- 10 files changed, 760 insertions(+), 36 deletions(-) create mode 100644 cmd/tegola/.gitignore create mode 100644 isequal.go create mode 100644 isequal_test.go create mode 100644 maths/clean.go create mode 100644 maths/clean_test.go diff --git a/basic/line.go b/basic/line.go index 1aee72199..43b1a6993 100644 --- a/basic/line.go +++ b/basic/line.go @@ -13,34 +13,24 @@ import ( type Line []Point // Just to make basic collection only usable with basic types. -func (Line) basicType() {} -func (Line) String() string { return "Line" } -func (l Line) direction() maths.WindingOrder { - sum := 0 - var npt Point - for i, pt := range l { - - if i == (len(l) - 1) { - npt = l[0] - } else { - npt = l[i+1] - } - sum += int((npt.X() - pt.X()) * (npt.Y() + pt.Y())) - } - if sum < 0 { - return maths.Clockwise - } - return maths.CounterClockwise -} +func (Line) basicType() {} +func (Line) String() string { return "Line" } +func (l Line) Direction() maths.WindingOrder { return maths.WindingOrderOfLine(l) } func (l Line) GoString() string { - str := fmt.Sprintf("[%v--%v]{", len(l), l.direction()) + str := fmt.Sprintf("\n[%v--%v]{\n\t", len(l), l.Direction()) + count := 0 for i, p := range l { if i != 0 { str += "," } str += fmt.Sprintf("(%v,%v)", p[0], p[1]) + if count == 10 { + str += "\n\t" + count = 0 + } + count++ } - str += "}" + str += "\n}" return str } @@ -64,6 +54,13 @@ func NewLineFromPt(points ...maths.Pt) Line { return line } +func CloneLine(line tegola.LineString) (l Line) { + for _, pt := range line.Subpoints() { + l = append(l, Point{pt.X(), pt.Y()}) + } + return l +} + // Subpoints return the points in a line. func (l Line) Subpoints() (points []tegola.Point) { points = make([]tegola.Point, 0, len(l)) diff --git a/basic/polygon.go b/basic/polygon.go index 0f14ddcf0..f3a7ce395 100644 --- a/basic/polygon.go +++ b/basic/polygon.go @@ -1,6 +1,8 @@ package basic import ( + "fmt" + "github.com/terranodo/tegola" "github.com/terranodo/tegola/maths" ) @@ -22,6 +24,14 @@ func (p Polygon) Sublines() (slines []tegola.LineString) { func (Polygon) String() string { return "Polygon" } +func (p Polygon) GoString() string { + str := fmt.Sprintf("\nPolygon[%v]{\n", len(p)) + for _, l := range p { + str += fmt.Sprintf("%#", l) + } + str += "}\n" + return str +} // MultiPolygon describes a set of polygons. type MultiPolygon []Polygon @@ -38,7 +48,7 @@ func (mp MultiPolygon) Polygons() (polygons []tegola.Polygon) { return polygons } func (MultiPolygon) String() string { - return "Polygon" + return "MultiPolygon" } func NewPolygon(main []maths.Pt, clines ...[]maths.Pt) Polygon { diff --git a/cmd/printwkb/main.go b/cmd/printwkb/main.go index 83cf0ea05..597e14697 100644 --- a/cmd/printwkb/main.go +++ b/cmd/printwkb/main.go @@ -19,9 +19,13 @@ func print(srid int, geostr string, tile tegola.BoundingBox) { rd1 := strings.NewReader(geostr) geo, err := wkb.Decode(rd1) if err != nil { - fmt.Fprintf(os.Stderr, "Error decoding goe(road1): %v", err) + fmt.Fprintf(os.Stderr, "Error decoding goemetry: %v", err) os.Exit(2) } + if geo == nil { + fmt.Fprintf(os.Stderr, "Geo is nil.") + os.Exit(0) + } gwm, err := basic.ToWebMercator(srid, geo) c := mvt.NewCursor(tile, 4096) g := c.ScaleGeo(gwm.Geometry) @@ -33,7 +37,6 @@ func print(srid int, geostr string, tile tegola.BoundingBox) { panic(err) } fmt.Printf("Clip GEO:%T: %#[1]v\n", cg) - } var configFile string @@ -77,6 +80,7 @@ type ProviderLayer struct { password string host string geoField string + geoID string table string } @@ -133,11 +137,17 @@ func LoadProvider(configfile string, providerlayer string) (pl ProviderLayer, er } } var ok bool - srid, ok := provider["srid"].(int64) - if !ok { - return pl, fmt.Errorf("Cound not convert %T", provider["srid"]) + + if _, ok = provider["srid"]; ok { + srid, ok := provider["srid"].(int64) + if !ok { + return pl, fmt.Errorf("Cound not convert %T", provider["srid"]) + } + pl.srid = int(srid) + } else { + pl.srid = 4326 } - pl.srid = int(srid) + port, ok := provider["port"].(int64) if !ok { return pl, fmt.Errorf("Cound not convert %T", provider["port"]) @@ -174,9 +184,11 @@ func LoadProvider(configfile string, providerlayer string) (pl ProviderLayer, er if pl.geoField, ok = providerLayer["geometry_fieldname"].(string); !ok { return pl, fmt.Errorf("was not able to convert geometry_fieldsname to string %v.", providerLayer["geometry_fieldname"]) } - pl.table = layerName - if tbln, ok := providerLayer["tablename"].(string); ok && tbln != "" { - pl.table = tbln + if pl.geoID, ok = providerLayer["id_fieldname"].(string); !ok || pl.geoID == "" { + pl.geoID = "gid" + } + if pl.table, ok = providerLayer["tablename"].(string); !ok || pl.table == "" { + pl.table = layerName } return pl, nil } diff --git a/cmd/tegola/.gitignore b/cmd/tegola/.gitignore new file mode 100644 index 000000000..11b9c76a0 --- /dev/null +++ b/cmd/tegola/.gitignore @@ -0,0 +1,4 @@ +*.toml +tegola +static/debug +static/landuse2 diff --git a/isequal.go b/isequal.go new file mode 100644 index 000000000..3321e2de1 --- /dev/null +++ b/isequal.go @@ -0,0 +1,158 @@ +package tegola + +// IsPointEqual will check to see if the two tegola points are equal. +func IsPointEqual(p1, p2 Point) bool { + if p1 == nil || p2 == nil { + return p1 == p2 + } + return p1.X() == p2.X() && p1.Y() == p2.Y() +} + +// IsPoint3Equal will check to see if the two 3d tegola points are equal. +func IsPoint3Equal(p1, p2 Point3) bool { + return p1.X() == p2.X() && p1.Y() == p2.Y() && p1.Z() == p2.Z() +} + +// IsMultiPointEqual will check to see if the two provided multipoints are equal +func IsMultiPointEqual(mp1, mp2 MultiPoint) bool { + pts1, pts2 := mp1.Points(), mp2.Points() + if len(pts1) != len(pts2) { + return false + } + for i, pt := range pts1 { + if !IsPointEqual(pt, pts2[i]) { + return false + } + } + return true +} + +// IsLineStringEqual will check to see if the two linesstrings provided are equal. +func IsLineStringEqual(l1, l2 LineString) bool { + pts1, pts2 := l1.Subpoints(), l2.Subpoints() + if len(pts1) != len(pts2) { + return false + } + for i, pt := range pts1 { + if !IsPointEqual(pt, pts2[i]) { + return false + } + } + return true +} + +// IsMultiLineEqual will check to see if the two Multilines that are provided are equal. +func IsMultiLineEqual(ml1, ml2 MultiLine) bool { + lns1, lns2 := ml1.Lines(), ml2.Lines() + if len(lns1) != len(lns2) { + return false + } + for i, ln := range lns1 { + if !IsLineStringEqual(ln, lns2[i]) { + return false + } + } + return true +} + +// PolygonIsEqual will check to see if the two provided polygons are equal. +func IsPolygonEqual(p1, p2 Polygon) bool { + lns1, lns2 := p1.Sublines(), p2.Sublines() + if len(lns1) != len(lns2) { + return false + } + for i, ln := range lns1 { + if !IsLineStringEqual(ln, lns2[i]) { + return false + } + } + return true +} + +// MultiPolygonIsEqual will check to see if the two provided multi-polygons are equal. +func IsMultiPolygonEqual(mp1, mp2 MultiPolygon) bool { + pgs1, pgs2 := mp1.Polygons(), mp2.Polygons() + if len(pgs1) != len(pgs2) { + return false + } + for i, pg := range pgs1 { + if !IsPolygonEqual(pg, pgs2[i]) { + return false + } + } + return true +} + +// GeometryIsEqual will check to see if the two given geometeries are equal. This function does not check to see if there are any +// recursive structures if there are any recursive structures it will hang. If the type of the geometry is unknown, it is assumed +// that it does not match any other geometries. +func IsGeometryEqual(g1, g2 Geometry) bool { + switch geo1 := g1.(type) { + case Point: + geo2, ok := g2.(Point) + if !ok { + return false + } + return IsPointEqual(geo1, geo2) + case Point3: + geo2, ok := g2.(Point3) + if !ok { + return false + } + return IsPoint3Equal(geo1, geo2) + case MultiPoint: + geo2, ok := g2.(MultiPoint) + if !ok { + return false + } + return IsMultiPointEqual(geo1, geo2) + case LineString: + geo2, ok := g2.(LineString) + if !ok { + return false + } + return IsLineStringEqual(geo1, geo2) + case MultiLine: + geo2, ok := g2.(MultiLine) + if !ok { + return false + } + return IsMultiLineEqual(geo1, geo2) + case Polygon: + geo2, ok := g2.(Polygon) + if !ok { + return false + } + return IsPolygonEqual(geo1, geo2) + case MultiPolygon: + geo2, ok := g2.(MultiPolygon) + if !ok { + return false + } + return IsMultiPolygonEqual(geo1, geo2) + case Collection: + geo2, ok := g2.(Collection) + if !ok { + return false + } + return IsCollectionEqual(geo1, geo2) + } + // If we don't know the type, we will assume they don't match. + return false +} + +// CollectionIsEqual will check to see if the provided collections are equal. This function does not check to see if the collections +// contain any recursive structures, and if there are any recursive structures it will hang. If the collections contains any unknown +// geometries it will be assumed to not match. +func IsCollectionEqual(c1, c2 Collection) bool { + geos1, geos2 := c1.Geometries(), c2.Geometries() + if len(geos1) != len(geos2) { + return false + } + for i, geo := range geos1 { + if !IsGeometryEqual(geo, geos2[i]) { + return false + } + } + return true +} diff --git a/isequal_test.go b/isequal_test.go new file mode 100644 index 000000000..298216199 --- /dev/null +++ b/isequal_test.go @@ -0,0 +1,376 @@ +package tegola_test + +import ( + "testing" + + "github.com/gdey/tbltest" + "github.com/terranodo/tegola" + "github.com/terranodo/tegola/basic" +) + +func TestIsPointEqual(t *testing.T) { + type testcase struct { + desc string + pt1 tegola.Point + pt2 tegola.Point + expected bool + } + tests := tbltest.Cases( + testcase{ + desc: "Simple Points not equal", + pt1: basic.Point{1, 1}, + pt2: basic.Point{2, 2}, + }, + testcase{ + desc: "Simple Points not equal", + pt1: basic.Point{1, 1}, + pt2: basic.Point{1, 2}, + }, + testcase{ + desc: "Simple Points not equal", + pt1: basic.Point{1, 1}, + pt2: basic.Point{2, 1}, + }, + testcase{ + desc: "One nil and one nonnil Points not equal", + pt1: basic.Point{1, 1}, + }, + testcase{ + desc: "One nil and one nonnil Points not equal", + pt2: basic.Point{1, 1}, + }, + testcase{ + desc: "Simple Points are equal", + pt1: basic.Point{1, 1}, + pt2: basic.Point{1, 1}, + expected: true, + }, + testcase{ + desc: "nil Points are equal", + expected: true, + }, + ) + tests.Run(func(test testcase) { + if test.expected != tegola.IsPointEqual(test.pt1, test.pt2) { + match := "match." + if !test.expected { + match = "not " + match + } + t.Errorf("Expected Points( %v, %v ) to %v", test.pt1, test.pt2, match) + } + if test.expected != tegola.IsPointEqual(test.pt2, test.pt1) { + match := "match." + if !test.expected { + match = "not " + match + } + t.Errorf("Expected Points( %v, %v ) to %v", test.pt2, test.pt1, match) + } + }) +} + +func TestIsMultiPointEqual(t *testing.T) { + type testcase struct { + desc string + mpt1 basic.MultiPoint + mpt2 basic.MultiPoint + expected bool + } + tests := tbltest.Cases( + testcase{ + desc: "Simple single point, the same.", + expected: true, + mpt1: basic.MultiPoint{basic.Point{1, 1}}, + mpt2: basic.MultiPoint{basic.Point{1, 1}}, + }, + testcase{ + desc: "Simple single point, the same.", + expected: true, + mpt1: basic.MultiPoint{basic.Point{1, 1}, basic.Point{2, 2}}, + mpt2: basic.MultiPoint{basic.Point{1, 1}, basic.Point{2, 2}}, + }, + testcase{ + desc: "Simple nil points are the same.", + expected: true, + }, + testcase{ + desc: "multipoint and nil is not the same.", + mpt1: basic.MultiPoint{basic.Point{1, 1}, basic.Point{2, 2}}, + }, + testcase{ + desc: "Different points are not the same.", + mpt1: basic.MultiPoint{basic.Point{1, 1}, basic.Point{2, 2}}, + mpt2: basic.MultiPoint{basic.Point{-1, 1}, basic.Point{2, 2}}, + }, + testcase{ + desc: "Different points are not the same.", + mpt1: basic.MultiPoint{basic.Point{1, 1}, basic.Point{2, 2}}, + mpt2: basic.MultiPoint{basic.Point{1, 1}, basic.Point{-2, 2}}, + }, + testcase{ + desc: "Different points are not the same.", + mpt1: basic.MultiPoint{basic.Point{1, 1}}, + mpt2: basic.MultiPoint{basic.Point{1, 1}, basic.Point{2, 2}}, + }, + ) + tests.Run(func(test testcase) { + if test.expected != tegola.IsMultiPointEqual(test.mpt1, test.mpt2) { + match := "match." + if !test.expected { + match = "not " + match + } + t.Errorf("Expected MultiPoints( %v, %v ) to %v", test.mpt1, test.mpt2, match) + } + if test.expected != tegola.IsMultiPointEqual(test.mpt2, test.mpt1) { + match := "match." + if !test.expected { + match = "not " + match + } + t.Errorf("Expected MultiPoints( %v, %v ) to %v", test.mpt2, test.mpt1, match) + } + }) +} + +func TestIsLineEqual(t *testing.T) { + type testcase struct { + Desc string + ln1 basic.Line + ln2 basic.Line + expected bool + } + tests := tbltest.Cases( + testcase{ + Desc: "Simple lines", + ln1: basic.NewLine(0, 0, 1, 1, 2, 2, 3, 3, 4, 4), + ln2: basic.NewLine(0, 0, 1, 1, 2, 2, 3, 3, 4, 4), + expected: true, + }, + testcase{ + Desc: "Simple lines that don't match wrong length.", + ln1: basic.NewLine(0, 0, 1, 1, 2, 2, 3, 3, 4, 4), + ln2: basic.NewLine(0, 0, 1, 1, 2, 2, 3, 3), + }, + testcase{ + Desc: "Simple lines that don't match wrong length.", + ln1: basic.NewLine(0, 0, 1, 1, 2, 2, 3, 3, 4, 4), + ln2: basic.NewLine(1, 1, 2, 2, 3, 3, 4, 4), + }, + testcase{ + Desc: "Simple lines that don't match wrong length.", + ln1: basic.NewLine(0, 0, 1, 1, 2, 2, 3, 3, 4, 4), + ln2: basic.NewLine(0, 0, 1, 1, 3, 3, 4, 4), + }, + testcase{ + Desc: "Simple lines that don't matc; numbers are off.", + ln1: basic.NewLine(0, 0, 1, 1, 2, 2, 3, 3, 4, 4), + ln2: basic.NewLine(-1, -1, 1, 1, 2, 2, 3, 3, 4, 4), + }, + testcase{ + Desc: "Simple lines that don't matc; numbers are off.", + ln1: basic.NewLine(0, 0, 1, 1, 2, 2, 3, 3, 4, 4), + ln2: basic.NewLine(0, 0, 1, 1, 2, 2, 3, 3, -4, -4), + }, + testcase{ + Desc: "Simple lines that don't match; numbers are off.", + ln1: basic.NewLine(0, 0, 1, 1, 2, 2, 3, 3, 4, 4), + ln2: basic.NewLine(1, 0, 1, 1, 2, 2, 3, 3, 4, 4), + }, + testcase{ + Desc: "Simple lines that don't matc; numbers are off.", + ln1: basic.NewLine(0, 0, 1, 1, 2, 2, 3, 3, 4, 4), + ln2: basic.NewLine(0, 0, 1, 1, 2, 2, 3, 3, 4, -4), + }, + testcase{ + Desc: "Simple lines that don't matc; numbers are off.", + ln1: basic.NewLine(0, 0, 1, 1, 2, 2, 3, 3, 4, 4), + ln2: basic.NewLine(0, 0, 1, 1, 2, -2, 3, 3, 4, 4), + }, + ) + tests.Run(func(test testcase) { + if test.expected != tegola.IsLineStringEqual(test.ln1, test.ln2) { + match := "match." + if !test.expected { + match = "not " + match + } + t.Errorf("Expected Lines( %v, %v ) to %v", test.ln1, test.ln2, match) + } + if test.expected != tegola.IsLineStringEqual(test.ln2, test.ln1) { + match := "match." + if !test.expected { + match = "not " + match + } + t.Errorf("Expected Lines( %v, %v ) to %v", test.ln2, test.ln1, match) + } + }) +} + +func TestIsMultiLineEqual(t *testing.T) { + type testcase struct { + Desc string + lns1 basic.MultiLine + lns2 basic.MultiLine + expected bool + } + tests := tbltest.Cases( + testcase{ + Desc: "Simple lines", + lns1: basic.MultiLine{basic.NewLine(0, 0, 1, 1, 2, 2, 3, 3, 4, 4)}, + lns2: basic.MultiLine{basic.NewLine(0, 0, 1, 1, 2, 2, 3, 3, 4, 4)}, + expected: true, + }, + testcase{ + Desc: "Simple lines", + lns1: basic.MultiLine{ + basic.NewLine(0, 0, 1, 1, 2, 2, 3, 3, 4, 4), + basic.NewLine(5, 0, 5, 1, 5, 2, 5, 3, 5, 4), + }, + lns2: basic.MultiLine{ + basic.NewLine(0, 0, 1, 1, 2, 2, 3, 3, 4, 4), + basic.NewLine(5, 0, 5, 1, 5, 2, 5, 3, 5, 4), + }, + expected: true, + }, + testcase{ + Desc: "Simple lines", + lns1: basic.MultiLine{ + basic.NewLine(0, 0, 1, 1, 2, 2, 3, 3, 4, 4), + basic.NewLine(5, 0, 5, 1, 5, 2, 5, 3, 5, 4), + }, + lns2: basic.MultiLine{ + basic.NewLine(0, 0, 1, 1, 2, 2, 3, 3, 4, 4), + basic.NewLine(5, 0, 5, 1, 5, 2, 5, 3), + }, + }, + testcase{ + Desc: "Simple lines", + lns1: basic.MultiLine{ + basic.NewLine(0, 0, 1, 1, 2, 2, 3, 3, 4, 4), + }, + lns2: basic.MultiLine{ + basic.NewLine(0, 0, 1, 1, 2, 2, 3, 3, 4, 4), + basic.NewLine(5, 0, 5, 1, 5, 2, 5, 3, 5, 4), + }, + }, + testcase{ + Desc: "Simple lines that don't match wrong length.", + lns1: basic.MultiLine{basic.NewLine(0, 0, 1, 1, 2, 2, 3, 3, 4, 4)}, + lns2: basic.MultiLine{basic.NewLine(0, 0, 1, 1, 2, 2, 3, 3)}, + }, + testcase{ + Desc: "Simple lines that don't match wrong length.", + lns1: basic.MultiLine{basic.NewLine(0, 0, 1, 1, 2, 2, 3, 3, 4, 4)}, + lns2: basic.MultiLine{basic.NewLine(1, 1, 2, 2, 3, 3, 4, 4)}, + }, + testcase{ + Desc: "Simple lines that don't match wrong length.", + lns1: basic.MultiLine{basic.NewLine(0, 0, 1, 1, 2, 2, 3, 3, 4, 4)}, + lns2: basic.MultiLine{basic.NewLine(0, 0, 1, 1, 3, 3, 4, 4)}, + }, + testcase{ + Desc: "Simple lines that don't matc; numbers are off.", + lns1: basic.MultiLine{basic.NewLine(0, 0, 1, 1, 2, 2, 3, 3, 4, 4)}, + lns2: basic.MultiLine{basic.NewLine(-1, -1, 1, 1, 2, 2, 3, 3, 4, 4)}, + }, + testcase{ + Desc: "Simple lines that don't matc; numbers are off.", + lns1: basic.MultiLine{basic.NewLine(0, 0, 1, 1, 2, 2, 3, 3, 4, 4)}, + lns2: basic.MultiLine{basic.NewLine(0, 0, 1, 1, 2, 2, 3, 3, -4, -4)}, + }, + testcase{ + Desc: "Simple lines that don't match; numbers are off.", + lns1: basic.MultiLine{basic.NewLine(0, 0, 1, 1, 2, 2, 3, 3, 4, 4)}, + lns2: basic.MultiLine{basic.NewLine(1, 0, 1, 1, 2, 2, 3, 3, 4, 4)}, + }, + testcase{ + Desc: "Simple lines that don't matc; numbers are off.", + lns1: basic.MultiLine{basic.NewLine(0, 0, 1, 1, 2, 2, 3, 3, 4, 4)}, + lns2: basic.MultiLine{basic.NewLine(0, 0, 1, 1, 2, 2, 3, 3, 4, -4)}, + }, + testcase{ + Desc: "Simple lines that don't matc; numbers are off.", + lns1: basic.MultiLine{basic.NewLine(0, 0, 1, 1, 2, 2, 3, 3, 4, 4)}, + lns2: basic.MultiLine{basic.NewLine(0, 0, 1, 1, 2, -2, 3, 3, 4, 4)}, + }, + ) + tests.Run(func(test testcase) { + if test.expected != tegola.IsMultiLineEqual(test.lns1, test.lns2) { + match := "match." + if !test.expected { + match = "not " + match + } + t.Errorf("Expected Lines( %v, %v ) to %v", test.lns1, test.lns2, match) + } + if test.expected != tegola.IsMultiLineEqual(test.lns2, test.lns1) { + match := "match." + if !test.expected { + match = "not " + match + } + t.Errorf("Expected Lines( %v, %v ) to %v", test.lns2, test.lns1, match) + } + }) +} + +func TestIsPolygonEqual(t *testing.T) { + type testcase struct { + Desc string + poly1 basic.Polygon + poly2 basic.Polygon + expected bool + } + tests := tbltest.Cases( + testcase{ + Desc: "Simple lines", + poly1: basic.Polygon{basic.NewLine(0, 0, 1, 0, 0, 1)}, + poly2: basic.Polygon{basic.NewLine(0, 0, 1, 0, 0, 1)}, + expected: true, + }, + testcase{ + Desc: "Simple lines", + poly1: basic.Polygon{ + basic.NewLine(0, 0, 5, 0, 5, 5, 0, 5), + basic.NewLine(0, 0, 0, 3, 3, 3, 3, 0), + }, + poly2: basic.Polygon{ + basic.NewLine(0, 0, 5, 0, 5, 5, 0, 5), + basic.NewLine(0, 0, 0, 3, 3, 3, 3, 0), + }, + expected: true, + }, + testcase{ + Desc: "Simple lines", + poly1: basic.Polygon{ + basic.NewLine(0, 0, 5, 0, 5, 5, 0, 5), + basic.NewLine(0, 0, 0, 3, 3, 3, 3, 0), + }, + poly2: basic.Polygon{ + basic.NewLine(0, 0, 5, 0, 5, 5, 0, 5), + basic.NewLine(0, 0, 0, 3, 3, 3), + }, + }, + testcase{ + Desc: "Simple lines", + poly1: basic.Polygon{ + basic.NewLine(0, 0, 5, 0, 5, 5, 0, 5), + basic.NewLine(0, 0, 0, 3, 3, 3, 3, 0), + }, + poly2: basic.Polygon{ + basic.NewLine(0, 0, 5, 0, 5, 5, 0, 5), + }, + }, + ) + tests.Run(func(test testcase) { + if test.expected != tegola.IsPolygonEqual(test.poly1, test.poly2) { + match := "match." + if !test.expected { + match = "not " + match + } + t.Errorf("Expected Lines( %v, %v ) to %v", test.poly1, test.poly2, match) + } + if test.expected != tegola.IsPolygonEqual(test.poly2, test.poly1) { + match := "match." + if !test.expected { + match = "not " + match + } + t.Errorf("Expected Lines( %v, %v ) to %v", test.poly2, test.poly1, match) + } + }) +} diff --git a/maths/clean.go b/maths/clean.go new file mode 100644 index 000000000..d10e81c66 --- /dev/null +++ b/maths/clean.go @@ -0,0 +1,67 @@ +package maths + +/* +// cleanPolygon will take a look at a polygon and attempt to clean it, returning one or more valid polygons, and an invalid line if it exists. +// It will remove polygons that have no lines, and remove lines that have no points. +// A valid polygon will have the following shape. The first linestring will be clockwise, and all +// linestrings are counter-clockwise. +func cleanPolygon(p tegola.Polygon) (polygons []basic.Polygon, invalid basic.Polygon) { + // If the polygon is empty, return empty polygons. + if p == nil { + return polygons, invalids + } + lines := p.Lines() + // If there are no lines, then we return empty polygons. + if len(lines) == 0 { + return polygons, invalids + } + var currentPolygon basic.Polygon + for _, l := range lines { + bl := basic.CloneLine(l) + // skip lines that don't have any points. + if len(bl) == 0 { + continue + } + switch bl.Direction() { + case Clockwise: + // Need to start a new polygon, after adding the current polygon to the polygons + // array. + if currentPolygon != nil { + polygons = append(polygons, currentPolygon) + currentPolygon = nil + } + case CounterClockwise: + if currentPolygon == nil { + // This is an error. There can only be one line that is invalid. + invalid = append(invalid, bl) + continue + } + } + currentPolygon = append(currentPolygon, bl) + } + if currentPolygon != nil { + polygons = append(polygons, currentPolygon) + } + return polygons, invalid +} + +func cleanMultiPolygon(mpolygon tegola.MultiPolygon) (mp basic.MultiPolygon, err error) { + for _, p := range mpolygon.Polygons() { + poly, invalid := cleanPolygon(p) + invalidLen = len(invalid) + mpLen = len(mp) + switch { + // This is an error; we can not clean this. + case invalidLen != 0 && mpLen == 0: + return mp, fmt.Errorf("Unable to clean MultiPolygon.") + case invalidLen != 0 && mpLen != 0: + lastPoly := mp[len(mp)-1] + lastPoly = append(lastPoly, invalid.Lines()...) + continue + } + mp = append(mp, poly...) + } + return mp, nil +} + +*/ diff --git a/maths/clean_test.go b/maths/clean_test.go new file mode 100644 index 000000000..5018d47f5 --- /dev/null +++ b/maths/clean_test.go @@ -0,0 +1,40 @@ +package maths + +import ( + "testing" + + "github.com/gdey/tbltest" + "github.com/terranodo/tegola/basic" +) + +func TestCleanPolygon(t *testing.T) { + type testcase struct { + Desc string + Polygon basic.Polygon + Expected []basic.Polygon + ExpectedInvalid basic.Polygon + } + + tests := tbltest.Cases( + testcase{ + Desc: "empty Polygon, Should return nothing.", + }, + testcase{ + Desc: "single Polygon, with bad counter clockwise first line.", + Polygon: basic.Polygon{ + basic.NewLine(4, 2, 2, 4, 2, 6, 3, 7, 5, 8, 7, 7, 8, 5, 8, 3, 6, 2), + }, + ExpectedInvalid: basic.Polygon{ + basic.NewLine(4, 2, 2, 4, 2, 6, 3, 7, 5, 8, 7, 7, 8, 5, 8, 3, 6, 2), + }, + }, + ) + + tests.Run(t, func(test testcase) { + poly, invalid := cleanPolygon(test.Polygon) + if len(test.Expected) != len(poly) { + t.Errorf("Expected len to get %v got %v", len(test.Expected), len(poly)) + } + }) + +} diff --git a/maths/clip/clip.go b/maths/clip/clip.go index 93e20a304..847fbf4d1 100644 --- a/maths/clip/clip.go +++ b/maths/clip/clip.go @@ -2,7 +2,6 @@ package clip import ( "fmt" - "log" "github.com/terranodo/tegola" "github.com/terranodo/tegola/basic" @@ -329,7 +328,7 @@ func linestring(w maths.WindingOrder, sub []float64, rMinPt, rMaxPt maths.Pt) (c il.PushBack(ipt) } if !p.PushInBetween(ipt.AsSubjectPoint()) { - log.Printf("Was not able to add to subject list %v\n", ipt) + // log.Printf("Was not able to add to subject list %v\n", ipt) } a.PushInBetween(ipt.AsRegionPoint()) } @@ -489,14 +488,18 @@ func Polygon(polygon tegola.Polygon, min, max maths.Pt, extant int) (p []basic.P // and set of ourter rignts. The outer ring is clockwise while the inner ring is // usually conter clockwise. + // log.Println("Starting Polygon clipping.") sls := polygon.Sublines() + if len(sls) == 0 { + return p, nil + } var subls []*subject.Subject emin := maths.Pt{min.X - float64(extant), min.Y - float64(extant)} emax := maths.Pt{max.X + float64(extant), max.Y + float64(extant)} - //log.Printf("Starting to clip main line to %v, %v", emin, emax) + // log.Printf("Starting to clip main line to %v, %v", emin, emax) plstrs, err := linestring(maths.Clockwise, linestring2floats(sls[0]), emin, emax) - //log.Printf("Done to clipping main line to %v, %v", emin, emax) + // log.Printf("Done to clipping main line to %v, %v", emin, emax) if err != nil { return nil, err } diff --git a/maths/maths.go b/maths/maths.go index 32d09f5dc..9b8284dfa 100644 --- a/maths/maths.go +++ b/maths/maths.go @@ -46,7 +46,7 @@ func WindingOrderOf(sub []float64) WindingOrder { sum := 0 for x, y := 0, 1; y < len(sub); x, y = x+2, y+2 { nx, ny := x+2, y+2 - if y == (len(sub) - 1) { + if y == (len(sub) - 2) { nx, ny = 0, 1 } sum += int((sub[nx] - sub[x]) * (sub[ny] + sub[y])) @@ -57,6 +57,23 @@ func WindingOrderOf(sub []float64) WindingOrder { return CounterClockwise } +func WindingOrderOfLine(l tegola.LineString) WindingOrder { + pts := l.Subpoints() + sum := 0 + for i, pt := range pts { + ni := i + 1 + if i == len(pts) { + ni = 0 + } + npt := pts[ni] + sum += int((npt.X() - pt.X()) * (npt.Y() - pt.Y())) + } + if sum < 0 { + return Clockwise + } + return CounterClockwise +} + // Pt describes a 2d Point. type Pt struct { X float64 `json:"x"` @@ -240,3 +257,43 @@ func Intersect(l1, l2 Line) (pt Pt, ok bool) { y := (m1 * x) + b1 return Pt{X: x, Y: y}, true } + +// Validate will transform a polygon that was encoded as a multipolygon as a polygon. +func Validate(geo tegola.Geometry) (vg tegola.Geometry) { + return + /* + switch g := geo.(type) { + default: + return geo + case tegola.MultiPolygon: + polys := g.Polygons() + if len(polys) == 0 { + return geo + } + // If there is only one Polygon in the MultiPolygon, return the polygon. + if len(polys) == 1 { + return polys[0] + } + var p tegola.Polygon + for i, p := range polys { + lines := p.Lines() + // Ignore polygons that don't have any lines in them. + if len(lines) == 0 { + continue + } + var hasClockwiseLine bool + for _, l := range lines { + if WindingOrderOfLine(l) == Clockwise { + hasClockwiseLine = true + + + } + + + } + + } + } + */ + +}