From fa72bef69c48d74005b683f3f129ec2e7ff880ec Mon Sep 17 00:00:00 2001 From: Peter Stace Date: Sun, 18 Jul 2021 17:17:38 +1000 Subject: [PATCH] Add cmprefimpl (PostGIS) tests for Dump --- internal/cmprefimpl/cmppg/checks.go | 33 ++++++++++++++++++++++++++ internal/cmprefimpl/cmppg/fuzz_test.go | 1 + internal/cmprefimpl/cmppg/pg.go | 16 ++++++++++++- 3 files changed, 49 insertions(+), 1 deletion(-) diff --git a/internal/cmprefimpl/cmppg/checks.go b/internal/cmprefimpl/cmppg/checks.go index 7a5d2487..4941a9b3 100644 --- a/internal/cmprefimpl/cmppg/checks.go +++ b/internal/cmprefimpl/cmppg/checks.go @@ -453,6 +453,39 @@ func CheckForceOrientation(t *testing.T, want UnaryResult, g geom.Geometry) { }) } +func CheckDump(t *testing.T, want UnaryResult, g geom.Geometry) { + if g.IsEmpty() { + // For empty geometries, PostGIS just returns no dumped geometries. + // Simplefeatures chooses not to do this behaviour to provide better + // consistency when it comes to Multi or GeometryCollections that + // contain only empty elements. If we were to follow the PostGIS + // behaviour, then 'MULTIPOLYGON(EMPTY)' would return 0 dumped + // geometries, whereas 'MULTIPOLYGON(((0 0,0 1,1 0,0 0)),EMPTY)' would + // return 2 dumped geometries (triangle, and empty polygon). + return + } + t.Run("CheckDump", func(t *testing.T) { + got := g.Dump() + if len(got) != len(want.Dump) { + for i, g := range got { + t.Logf("got %d: %s", i, g.AsText()) + } + for i, g := range want.Dump { + t.Logf("want %d: %s", i, g.AsText()) + } + t.Errorf("length mismatch, got=%d want=%d", len(got), len(want.Dump)) + } else { + for i, g := range got { + if !geom.ExactEquals(g, want.Dump[i], geom.ToleranceXY(0.00001)) { + t.Logf("got: %s", g.AsText()) + t.Logf("want: %s", want.Dump[i].AsText()) + t.Errorf("mismatch at position %d", i) + } + } + } + }) +} + func containsOnlyPolygonsOrMultiPolygons(g geom.Geometry) bool { switch g.Type() { case geom.TypePolygon, geom.TypeMultiPolygon: diff --git a/internal/cmprefimpl/cmppg/fuzz_test.go b/internal/cmprefimpl/cmppg/fuzz_test.go index 7d5cb7c6..ce2c830e 100644 --- a/internal/cmprefimpl/cmppg/fuzz_test.go +++ b/internal/cmprefimpl/cmppg/fuzz_test.go @@ -57,6 +57,7 @@ func TestFuzz(t *testing.T) { CheckReverse(t, want, g) CheckType(t, want, g) CheckForceOrientation(t, want, g) + CheckDump(t, want, g) }) } } diff --git a/internal/cmprefimpl/cmppg/pg.go b/internal/cmprefimpl/cmppg/pg.go index 2679390a..9b55a692 100644 --- a/internal/cmprefimpl/cmppg/pg.go +++ b/internal/cmprefimpl/cmppg/pg.go @@ -4,6 +4,7 @@ import ( "database/sql" "strings" + "github.com/lib/pq" "github.com/peterstace/simplefeatures/geom" ) @@ -33,6 +34,7 @@ type UnaryResult struct { Force4D geom.Geometry ForceCW geom.Geometry ForceCCW geom.Geometry + Dump []geom.Geometry } const ( @@ -47,6 +49,7 @@ func (p BatchPostGIS) Unary(g geom.Geometry) (UnaryResult, error) { var boundaryWKT sql.NullString var convexHullWKT string var reverseWKT string + var dumpWKTs []string var result UnaryResult err := p.db.QueryRow(` @@ -110,7 +113,9 @@ func (p BatchPostGIS) Unary(g geom.Geometry) (UnaryResult, error) { ST_AsBinary(ST_Force4D(ST_GeomFromWKB($1))), ST_AsBinary(ST_ForcePolygonCW(ST_GeomFromWKB($1))), - ST_AsBinary(ST_ForcePolygonCCW(ST_GeomFromWKB($1))) + ST_AsBinary(ST_ForcePolygonCCW(ST_GeomFromWKB($1))), + + array_agg(ST_AsText(geom)) FROM ST_Dump(ST_GeomFromWKB($1)) `, g, isNestedGeometryCollection(g), g.AsText(), ).Scan( @@ -135,6 +140,7 @@ func (p BatchPostGIS) Unary(g geom.Geometry) (UnaryResult, error) { &result.Force4D, &result.ForceCW, &result.ForceCCW, + pq.Array(&dumpWKTs), ) if err != nil { return result, err @@ -160,6 +166,14 @@ func (p BatchPostGIS) Unary(g geom.Geometry) (UnaryResult, error) { // remove ST_ prefix that ST_GeometryType returned, since we don't want ST_ prefix in our type result.Type = strings.TrimPrefix(result.Type, postgisTypePrefix) + + for _, wkt := range dumpWKTs { + dumpGeom, err := geom.UnmarshalWKT(wkt) + if err != nil { + return result, err + } + result.Dump = append(result.Dump, dumpGeom) + } return result, nil }