diff --git a/encoding/mvt/feature.go b/encoding/mvt/feature.go index 03090477..1c3dc843 100644 --- a/encoding/mvt/feature.go +++ b/encoding/mvt/feature.go @@ -6,8 +6,8 @@ import ( "log" "github.com/go-spatial/geom" - "github.com/go-spatial/geom/encoding/wkt" vectorTile "github.com/go-spatial/geom/encoding/mvt/vector_tile" + "github.com/go-spatial/geom/encoding/wkt" ) var ( @@ -36,7 +36,6 @@ func (f Feature) String() string { return fmt.Sprintf("encoding error for geom geom, %v", err) } - if f.ID != nil { return fmt.Sprintf("{Feature: %v, GEO: %v, Tags: %+v}", *f.ID, g, f.Tags) } @@ -255,6 +254,23 @@ func encodeGeometry(ctx context.Context, geometry geom.Geometry) (g []uint32, vt } return g, vectorTile.Tile_POLYGON, nil + case *geom.MultiPolygon: + if t == nil { + return g, vectorTile.Tile_POLYGON, nil + } + + polygons := t.Polygons() + for _, p := range polygons { + lines := geom.Polygon(p).LinearRings() + for _, l := range lines { + points := geom.LineString(l).Verticies() + g = append(g, c.MoveTo(points[0])...) + g = append(g, c.LineTo(points[1:]...)...) + g = append(g, c.ClosePath()) + } + } + return g, vectorTile.Tile_POLYGON, nil + default: return nil, vectorTile.Tile_UNKNOWN, ErrUnknownGeometryType } diff --git a/planar/makevalid/walker/walker_test.go b/planar/makevalid/walker/walker_test.go index 1aeffbab..56af4f04 100644 --- a/planar/makevalid/walker/walker_test.go +++ b/planar/makevalid/walker/walker_test.go @@ -218,7 +218,7 @@ func TestPolygonForTriangle(t *testing.T) { }, idx: []int{0}, polygons: [][][][2]float64{ - {{{0, 0}, {10, 0}, {10, 10}, {0, 10}, {0, 7}}, {{0, 7}, {7, 7}, {7, 2}}}, + {{{0, 0}, {10, 0}, {10, 10}, {0, 10}}, {{0, 7}, {7, 7}, {7, 2}}}, {{{0, 0}, {10, 0}, {10, 10}, {0, 10}}}, }, seenTriangles: [][]int{ diff --git a/planar/planar.go b/planar/planar.go index 842ef652..1a5e26bb 100644 --- a/planar/planar.go +++ b/planar/planar.go @@ -51,10 +51,10 @@ func IsPointOnLine(pt [2]float64, l1, l2 [2]float64) bool { switch { case !defined: // line is vertical, so if we the y values are the same it's on the line. - return cmp.Float(pt[1], l1[1]) - case m == 0: - // line is horizontal, so if the x values are the same it's on the line. return cmp.Float(pt[0], l1[0]) + case m == 0: + // line is horizontal, so if the y values are the same it's on the line. + return cmp.Float(pt[1], l1[1]) default: y := (m * pt[0]) + b return cmp.Float(pt[1], y) @@ -89,6 +89,20 @@ func IsPointOnLineSegment(pt geom.Point, seg geom.Line) bool { } +// PointOnLineAt will return a point on the given line at the distance from the +// origin of the line +func PointOnLineAt(ln geom.Line, distance float64) geom.Point { + + lineDist := math.Sqrt(ln.LenghtSquared()) + ratio := distance / lineDist + var x, y float64 + + x = ln[0][0] + (ratio * (ln[1][0] - ln[0][0])) + y = ln[0][1] + (ratio * (ln[1][1] - ln[0][1])) + return geom.Point{x, y} +} + +// IsCCW will check if the given points are in counter-clockwise order. func IsCCW(a, b, c geom.Point) bool { return geom.Triangle{[2]float64(a), [2]float64(b), [2]float64(c)}.Area() > 0 } diff --git a/planar/planar_test.go b/planar/planar_test.go index 2e4b671b..f4a70b1a 100644 --- a/planar/planar_test.go +++ b/planar/planar_test.go @@ -5,6 +5,8 @@ import ( "strconv" "testing" + "github.com/go-spatial/geom/cmp" + "github.com/go-spatial/geom" ) @@ -51,6 +53,71 @@ func TestSlope(t *testing.T) { t.Run(strconv.FormatInt(int64(i), 10), func(t *testing.T) { fn(t, tc) }) } } +func TestIsPointOnLine(t *testing.T) { + type tcase struct { + desc string + point geom.Point + segment geom.Line + expected bool + } + fn := func(tc tcase) (string, func(*testing.T)) { + return fmt.Sprintf("%v on %v", tc.point, tc.segment), + func(t *testing.T) { + if tc.expected != IsPointOnLine(tc.point, tc.segment[0], tc.segment[1]) { + t.Errorf("expected %v, got %v", tc.expected, !tc.expected) + } + } + } + tests := [...]tcase{ + { + // Diagonal line + point: geom.Point{1, 1}, + segment: geom.Line{{0, 0}, {1, 10}}, + }, + { + // Vertical line + point: geom.Point{1, 1}, + segment: geom.Line{{0, 0}, {0, 10}}, + }, + { + // Vertical line + point: geom.Point{1, 1}, + segment: geom.Line{{0, 10}, {10, 10}}, + }, + { + // horizontal line + point: geom.Point{1, 1}, + segment: geom.Line{{1, 0}, {1, 10}}, + expected: true, + }, + { + // horizontal line + point: geom.Point{1, 100}, + segment: geom.Line{{1, 0}, {1, 10}}, + expected: true, + }, + { + // horizontal line + point: geom.Point{1, -100}, + segment: geom.Line{{1, 0}, {1, 10}}, + expected: true, + }, + { + // horizontal line on close to the end point + point: geom.Point{-0.5, 0}, + segment: geom.Line{{1, 0}, {1, 10}}, + }, + { + // horizontal line on the end point + point: geom.Point{1, 0}, + segment: geom.Line{{1, 0}, {1, 10}}, + expected: true, + }, + } + for _, tc := range tests { + t.Run(fn(tc)) + } +} func TestIsPointOnLineSegment(t *testing.T) { type tcase struct { @@ -63,7 +130,7 @@ func TestIsPointOnLineSegment(t *testing.T) { return fmt.Sprintf("%v on %v", tc.point, tc.segment), func(t *testing.T) { if tc.expected != IsPointOnLineSegment(tc.point, tc.segment) { - t.Errorf("got %v, expected %v", !tc.expected, tc.expected) + t.Errorf("expected %v, got %v", tc.expected, !tc.expected) } } } @@ -105,3 +172,123 @@ func TestIsPointOnLineSegment(t *testing.T) { t.Run(fn(tc)) } } + +func TestIsCCW(t *testing.T) { + type tcase struct { + desc string + p1, p2, p3 geom.Point + is bool + } + fn := func(tc tcase) func(*testing.T) { + return func(t *testing.T) { + got := IsCCW(tc.p1, tc.p2, tc.p3) + if got != tc.is { + t.Errorf( + "%v:%v:%v, expected %v got %v", + tc.p1, tc.p2, tc.p3, + tc.is, got, + ) + return + } + } + } + + tests := []tcase{ + { + p1: geom.Point{0, 0}, + p2: geom.Point{1, 0}, + p3: geom.Point{1, 1}, + is: true, + }, + { + p1: geom.Point{204, 694}, + p2: geom.Point{-2511, -3640}, + p3: geom.Point{3462, -3640}, + is: true, + }, + { + p2: geom.Point{204, 694}, + p3: geom.Point{-2511, -3640}, + p1: geom.Point{3462, -3640}, + is: true, + }, + { + p3: geom.Point{204, 694}, + p1: geom.Point{-2511, -3640}, + p2: geom.Point{3462, -3640}, + is: true, + }, + { + p1: geom.Point{-2511, -3640}, + p2: geom.Point{204, 694}, + p3: geom.Point{3462, -3640}, + is: false, + }, + { + p1: geom.Point{-2511, 3640}, + p2: geom.Point{204, 694}, + p3: geom.Point{3462, -3640}, + is: false, + }, + } + + for _, tc := range tests { + t.Run(tc.desc, fn(tc)) + } +} +func TestPointOnLineAt(t *testing.T) { + + type tcase struct { + desc string + line geom.Line + distance float64 + point geom.Point + } + + fn := func(tc tcase) func(*testing.T) { + return func(t *testing.T) { + got := PointOnLineAt(tc.line, tc.distance) + if !cmp.GeomPointEqual(tc.point, got) { + t.Errorf("point, expected %v, got %v", tc.point, got) + } + } + } + + tests := []tcase{ + { + desc: "simple test case", + line: geom.Line{{0, 0}, {10, 0}}, + distance: 5.0, + point: geom.Point{5, 0}, + }, + { + line: geom.Line{{204, 694}, {-2511, -3640}}, + distance: 100, + point: geom.Point{150.9122535714552, 609.2551406919657}, + }, + { + line: geom.Line{{204, 694}, {475.500, 8853}}, + distance: 100, + point: geom.Point{207.3257728713106, 793.9446808730132}, + }, + { + line: geom.Line{{204, 694}, {369, 793}}, + distance: 100, + point: geom.Point{289.7492925712544, 745.4495755427527}, + }, + { + line: geom.Line{{204, 694}, {426, 539}}, + distance: 100, + point: geom.Point{285.9925374282251, 636.7529581019149}, + }, + { + line: geom.Line{{204, 694}, {273, 525}}, + distance: 100, + point: geom.Point{241.79928289224065, 601.4191476987149}, + }, + } + + for _, tc := range tests { + t.Run(tc.desc, fn(tc)) + } +} diff --git a/planar/triangulate/gdey/quadedge/subdivision/subdivision.go b/planar/triangulate/gdey/quadedge/subdivision/subdivision.go index 9ae021a0..22530d52 100644 --- a/planar/triangulate/gdey/quadedge/subdivision/subdivision.go +++ b/planar/triangulate/gdey/quadedge/subdivision/subdivision.go @@ -253,11 +253,13 @@ func (sd *Subdivision) InsertSite(x geom.Point) bool { } } +// appendNonrepeate will append points to an array if that point +// is not the same as the point immediately prior func appendNonrepeat(pts []geom.Point, v geom.Point) []geom.Point { if len(pts) == 0 || cmp.GeomPointEqual(v, pts[len(pts)-1]) { - return append(pts, v) + return pts } - return pts + return append(pts, v) } func selectCorrectEdges(from, to *quadedge.Edge) (cfrom, cto *quadedge.Edge) {