From 60131bef3fad2c9b20e6ac9379f725311db881d3 Mon Sep 17 00:00:00 2001 From: Gautam Dey Date: Fri, 9 Aug 2019 07:34:43 -0400 Subject: [PATCH] updated wkt Update wkt test and package --- encoding/wkt/internal/token/token_test.go | 143 +++++++++-------- encoding/wkt/wkt.go | 92 ++++++++++- encoding/wkt/wkt_test.go | 184 +++++++++++----------- 3 files changed, 258 insertions(+), 161 deletions(-) diff --git a/encoding/wkt/internal/token/token_test.go b/encoding/wkt/internal/token/token_test.go index 15f0f7e8..c668ca8c 100644 --- a/encoding/wkt/internal/token/token_test.go +++ b/encoding/wkt/internal/token/token_test.go @@ -76,58 +76,59 @@ func TestParsePointe(t *testing.T) { exp *geom.Point err error } - fn := func(t *testing.T, tc tcase) { - tt := NewT(strings.NewReader(tc.input)) - pt, err := tt.ParsePoint() - if msg, expstr, gotstr, ok := assertError(tc.err, err); !ok { - if msg != "" { - t.Errorf("%v, expected %v got %v", msg, expstr, gotstr) + fn := func(tc tcase) (string, func(t *testing.T)) { + return tc.input, func(t *testing.T) { + tt := NewT(strings.NewReader(tc.input)) + pt, err := tt.ParsePoint() + if msg, expstr, gotstr, ok := assertError(tc.err, err); !ok { + if msg != "" { + t.Errorf("%v, expected %v got %v", msg, expstr, gotstr) + } + return + } + if !reflect.DeepEqual(tc.exp, pt) { + t.Errorf("point values, expected %v got %v", tc.exp, pt) } - return - } - if !reflect.DeepEqual(tc.exp, pt) { - t.Errorf("point values, expected %v got %v", tc.exp, pt) } } - tests := map[string]tcase{ - "POINT EMPTY": { + tests := [...]tcase{ + { input: "POINT EMPTY", }, - "POINT EMPTY ": { + { input: "POINT EMPTY ", }, - "POINT ( 1 2 )": { + { input: "POINT ( 1 2 )", exp: &geom.Point{1, 2}, }, - " POINT ( 1 2 ) ": { + { input: " POINT ( 1 2 ) ", exp: &geom.Point{1, 2}, }, - " POINT ZM ( 1 2 3 4 ) ": { + { input: " POINT ZM ( 1 2 3 4 ) ", exp: &geom.Point{1, 2}, }, - "POINT 1 2": { + { input: "POINT 1 2", err: fmt.Errorf("expected to find “(” or “EMPTY”"), }, - "POINT ( 1 2": { + { input: "POINT ( 1 2", err: fmt.Errorf("expected to find “)”"), }, - "POINT ( 1 )": { + { input: "POINT ( 1 )", err: fmt.Errorf("expected to have at least 2 coordinates in a POINT"), }, - "POINT ( 1 2 3 4 5 )": { + { input: "POINT ( 1 2 3 4 5 )", err: fmt.Errorf("expected to have no more then 4 coordinates in a POINT"), }, } - for name, tc := range tests { - tc := tc - t.Run(name, func(t *testing.T) { fn(t, tc) }) + for _, tc := range tests { + t.Run(fn(tc)) } } @@ -138,35 +139,38 @@ func TestParseMultiPointe(t *testing.T) { err error } - fn := func(t *testing.T, tc tcase) { - t.Parallel() - tt := NewT(strings.NewReader(tc.input)) - mpt, err := tt.ParseMultiPoint() - if msg, expstr, gotstr, ok := assertError(tc.err, err); !ok { - if msg != "" { - t.Errorf("%v, expected %v got %v", msg, expstr, gotstr) + fn := func(tc tcase) (string, func(t *testing.T)) { + return tc.input, func(t *testing.T) { + t.Parallel() + tt := NewT(strings.NewReader(tc.input)) + mpt, err := tt.ParseMultiPoint() + if msg, expstr, gotstr, ok := assertError(tc.err, err); !ok { + if msg != "" { + t.Errorf("%v, expected %v got %v", msg, expstr, gotstr) + } + return + } + if !reflect.DeepEqual(tc.exp, mpt) { + t.Errorf("did not get correct multipoint values, expected %v got %v", tc.exp, mpt) } - return - } - if !reflect.DeepEqual(tc.exp, mpt) { - t.Errorf("did not get correct multipoint values, expected %v got %v", tc.exp, mpt) } } - tests := map[string]tcase{ - "empty": {input: "MultiPoint EMPTY"}, - "without pren": { + tests := [...]tcase{ + { + input: "MultiPoint EMPTY", + }, + { input: "MULTIPOINT ( 10 10, 12 12 )", exp: geom.MultiPoint{{10, 10}, {12, 12}}, }, - "with pren": { + { input: "MULTIPOINT ( (10 10), (12 12) )", exp: geom.MultiPoint{{10, 10}, {12, 12}}, }, } - for name, test := range tests { - test := test // make copy - t.Run(name, func(t *testing.T) { fn(t, test) }) + for _, test := range tests { + t.Run(fn(test)) } } @@ -176,34 +180,37 @@ func TestParseFloat64(t *testing.T) { exp float64 err error } - fn := func(t *testing.T, tc tcase) { - tt := NewT(strings.NewReader(tc.input)) - f, err := tt.ParseFloat64() - if tc.err != err { - t.Errorf("error, expected %v got %v", tc.err, err) - } - if tc.err != nil { - return - } - if tc.exp != f { - t.Errorf("prase float64 expected %v got %v", tc.exp, f) + fn := func(tc tcase) (string, func(t *testing.T)) { + return tc.input, func(t *testing.T) { + tt := NewT(strings.NewReader(tc.input)) + f, err := tt.ParseFloat64() + if tc.err != err { + t.Errorf("error, expected %v got %v", tc.err, err) + } + if tc.err != nil { + return + } + if tc.exp != f { + t.Errorf("parse for '%v' float64 expected %v got %v", tc.input, tc.exp, f) + } } } - tests := map[string]tcase{ - "-12": {input: "-12", exp: -12.0}, - "0": {input: "0", exp: 0.0}, - "+1000.00": {input: "+1000.00", exp: 1000.0}, - "-12000.00": {input: "-12000.00", exp: -12000.0}, - "10.005e5": {input: "10.005e5", exp: 10.005e5}, - "10.005e+5": {input: "10.005e+5", exp: 10.005e5}, - "10.005e+05": {input: "10.005e+05", exp: 10.005e5}, - "1.0005e+6": {input: "1.0005e+6", exp: 10.005e5}, - "1.0005e+06": {input: "1.0005e+06", exp: 10.005e5}, - "1.0005e-06": {input: "1.0005e-06", exp: 1.0005e-06}, - "1.0005e-06a": {input: "1.0005e-06a", exp: 1.0005e-06}, - } - for name, tc := range tests { - tc := tc - t.Run(name, func(t *testing.T) { fn(t, tc) }) + tests := []tcase{ + {input: "-12", exp: -12.0}, + {input: "-.12", exp: -0.12}, + {input: "-0.12", exp: -0.12}, + {input: "0", exp: 0.0}, + {input: "+1000.00", exp: 1000.0}, + {input: "-12000.00", exp: -12000.0}, + {input: "10.005e5", exp: 10.005e5}, + {input: "10.005e+5", exp: 10.005e5}, + {input: "10.005e+05", exp: 10.005e5}, + {input: "1.0005e+6", exp: 10.005e5}, + {input: "1.0005e+06", exp: 10.005e5}, + {input: "1.0005e-06", exp: 1.0005e-06}, + {input: "1.0005e-06a", exp: 1.0005e-06}, + } + for _, tc := range tests { + t.Run(fn(tc)) } } diff --git a/encoding/wkt/wkt.go b/encoding/wkt/wkt.go index 686a5e0f..c69c2b68 100644 --- a/encoding/wkt/wkt.go +++ b/encoding/wkt/wkt.go @@ -3,9 +3,11 @@ package wkt import ( "fmt" "reflect" + "strconv" "strings" "github.com/go-spatial/geom" + "github.com/go-spatial/geom/cmp" ) func isNil(a interface{}) bool { @@ -98,6 +100,19 @@ func isCollectionerEmpty(col geom.Collectioner) bool { return true } +func formatFloat(f float64) string { + s := strconv.FormatFloat(f, 'f', 3, 64) + if s[len(s)-3:] == "000" { + // remove the . + return s[:len(s)-4] + } + return s +} + +func formatPoint(pt [2]float64) string { + return formatFloat(pt[0]) + " " + formatFloat(pt[1]) +} + /* This purpose of this file is to house the wkt functions. These functions are use to take a tagola.Geometry and convert it to a wkt string. It will, also, @@ -109,8 +124,10 @@ func _encode(geo geom.Geometry) string { switch g := geo.(type) { case geom.Pointer: - xy := g.XY() - return fmt.Sprintf("%v %v", xy[0], xy[1]) + return formatPoint(g.XY()) + + case [2]float64: + return formatPoint(g) case geom.MultiPointer: var points []string @@ -142,6 +159,10 @@ func _encode(geo geom.Geometry) string { if len(l) == 0 { continue } + if !cmp.PointEqual(l[0], l[len(l)-1]) { + // Dup the first point to close the polygon. + l = append(l, l[0]) + } rings = append(rings, _encode(geom.LineString(l))) } return "(" + strings.Join(rings, ",") + ")" @@ -165,45 +186,58 @@ func _encode(geo geom.Geometry) string { func Encode(geo geom.Geometry) (string, error) { switch g := geo.(type) { default: + return "", geom.ErrUnknownGeometry{geo} + case geom.Pointer: + // POINT( 10 10) if isNil(g) { return "POINT EMPTY", nil } return "POINT (" + _encode(geo) + ")", nil + case [2]float64: + + return "POINT (" + _encode(geo) + ")", nil + case geom.MultiPointer: + if isNil(g) || len(g.Points()) == 0 { return "MULTIPOINT EMPTY", nil } return "MULTIPOINT " + _encode(geo), nil case geom.LineStringer: + if isNil(g) || len(g.Verticies()) == 0 { return "LINESTRING EMPTY", nil } return "LINESTRING " + _encode(geo), nil case geom.MultiLineStringer: + if isMultiLineStringerEmpty(g) { return "MULTILINESTRING EMPTY", nil } return "MULTILINESTRING " + _encode(geo), nil case geom.Polygoner: + if isPolygonerEmpty(g) { return "POLYGON EMPTY", nil } return "POLYGON " + _encode(geo), nil case geom.MultiPolygoner: + if isMultiPolygonerEmpty(g) { return "MULTIPOLYGON EMPTY", nil } return "MULTIPOLYGON " + _encode(geo), nil case geom.Collectioner: + if isCollectionerEmpty(g) { return "GEOMETRYCOLLECTION EMPTY", nil } @@ -216,7 +250,61 @@ func Encode(geo geom.Geometry) (string, error) { geometries = append(geometries, s) } return "GEOMETRYCOLLECTION (" + strings.Join(geometries, ",") + ")", nil + + case geom.Line: + + return Encode(geom.LineString(g[:])) + + case [2][2]float64: + + return Encode(geom.LineString(g[:])) + + case [][2]float64: + + return Encode(geom.LineString(g)) + + case []geom.Line: + + ml := make(geom.MultiLineString, len(g)) + for i := range g { + ml[i] = g[i][:] + } + return Encode(ml) + + case []geom.Point: + mp := make(geom.MultiPoint, len(g)) + for i := range g { + mp[i] = [2]float64(g[i]) + } + return Encode(mp) + + case geom.Triangle: + // treat a triangle as polygon + return Encode(geom.Polygon{g[:]}) + + case []geom.Triangle: + mp := make(geom.MultiPolygon, len(g)) + for i := range g { + mp[i] = geom.Polygon{g[i][:]} + } + return Encode(mp) + case geom.Extent: + // treat an extent as a ploygon + return Encode(g.AsPolygon()) + case *geom.Extent: + // treat an extent as a ploygon + if g != nil { + return Encode(g.AsPolygon()) + } + return Encode(geom.Polygon{}) + } +} +func MustEncode(geo geom.Geometry) (str string) { + var err error + if str, err = Encode(geo); err != nil { + panic(fmt.Sprintf("unable to encode %T as wkt", geo)) } + return str } func Decode(text string) (geo geom.Geometry, err error) { diff --git a/encoding/wkt/wkt_test.go b/encoding/wkt/wkt_test.go index 85e7596d..9fa5c4fb 100644 --- a/encoding/wkt/wkt_test.go +++ b/encoding/wkt/wkt_test.go @@ -12,294 +12,297 @@ func TestEncode(t *testing.T) { Rep string Err error } - fn := func(t *testing.T, tc tcase) { - t.Parallel() - grep, gerr := Encode(tc.Geom) - if tc.Err != nil { - if tc.Err.Error() != gerr.Error() { - t.Errorf("error, expected %v got %v", tc.Err.Error(), gerr.Error()) + fn := func(tc tcase) (string, func(*testing.T)) { + return tc.Rep, func(t *testing.T) { + t.Parallel() + + grep, gerr := Encode(tc.Geom) + if tc.Err != nil { + if tc.Err.Error() != gerr.Error() { + t.Errorf("error, expected %v got %v", tc.Err.Error(), gerr.Error()) + } + return + } + if tc.Err == nil && gerr != nil { + t.Errorf("error, expected nil got %v", gerr) + return + } + if tc.Rep != grep { + t.Errorf("representation, expected ‘%v’ got ‘%v’", tc.Rep, grep) } - return - } - if tc.Err == nil && gerr != nil { - t.Errorf("error, expected nil got %v", gerr) - return - } - if tc.Rep != grep { - t.Errorf("representation, expected ‘%v’ got ‘%v’", tc.Rep, grep) - } + } } - tests := map[string]map[string]tcase{ + tests := map[string][]tcase{ "Point": { - "empty nil": { + { Err: geom.ErrUnknownGeometry{nil}, }, - "empty": { + { Geom: (*geom.Point)(nil), Rep: "POINT EMPTY", }, - "zero": { + { Geom: geom.Point{0, 0}, Rep: "POINT (0 0)", }, - "one": { + { Geom: geom.Point{10, 0}, Rep: "POINT (10 0)", }, }, "MultiPoint": { - "empty nil": { + { Geom: (*geom.MultiPoint)(nil), Rep: "MULTIPOINT EMPTY", }, - "empty zero": { + { Geom: geom.MultiPoint{}, Rep: "MULTIPOINT EMPTY", }, - "one": { + { Geom: geom.MultiPoint{{0, 0}}, Rep: "MULTIPOINT (0 0)", }, - "two": { + { Geom: geom.MultiPoint{{0, 0}, {10, 10}}, Rep: "MULTIPOINT (0 0,10 10)", }, - "three": { + { Geom: geom.MultiPoint{{1, 1}, {3, 3}, {4, 5}}, Rep: "MULTIPOINT (1 1,3 3,4 5)", }, }, "LineString": { - "empty nil": { + { Geom: (*geom.LineString)(nil), Rep: "LINESTRING EMPTY", }, - "empty zero": { + { Geom: geom.LineString{}, Rep: "LINESTRING EMPTY", }, - "one": { + { Geom: geom.LineString{{0, 0}}, Rep: "LINESTRING (0 0)", }, - "two": { + { Geom: geom.LineString{{10, 10}, {0, 0}}, Rep: "LINESTRING (10 10,0 0)", }, - "three": { + { Geom: geom.LineString{{10, 10}, {9, 9}, {0, 0}}, Rep: "LINESTRING (10 10,9 9,0 0)", }, }, "MultiLineString": { - "empty nil": { + { Geom: (*geom.MultiLineString)(nil), Rep: "MULTILINESTRING EMPTY", }, - "zero lines": { + { Geom: geom.MultiLineString{}, Rep: "MULTILINESTRING EMPTY", }, - "one line zero points": { + { Geom: geom.MultiLineString{{}}, Rep: "MULTILINESTRING EMPTY", }, - "one line one point": { + { Geom: geom.MultiLineString{{{10, 10}}}, Rep: "MULTILINESTRING ((10 10))", }, - "one line two points": { + { Geom: geom.MultiLineString{{{10, 10}, {11, 11}}}, Rep: "MULTILINESTRING ((10 10,11 11))", }, - "two lines zero,zero point": { + { Geom: geom.MultiLineString{{}, {}}, Rep: "MULTILINESTRING EMPTY", }, - "two lines zero,one point": { + { Geom: geom.MultiLineString{{}, {{10, 10}}}, Rep: "MULTILINESTRING ((10 10))", }, - "two lines zero,two point": { + { Geom: geom.MultiLineString{{}, {{10, 10}, {20, 20}}}, Rep: "MULTILINESTRING ((10 10,20 20))", }, - "two lines one,zero point": { + { Geom: geom.MultiLineString{{{10, 10}}, {}}, Rep: "MULTILINESTRING ((10 10))", }, - "two lines one,one point": { + { Geom: geom.MultiLineString{{{10, 10}}, {{10, 10}}}, Rep: "MULTILINESTRING ((10 10),(10 10))", }, - "two lines one,two point": { + { Geom: geom.MultiLineString{{{10, 10}}, {{10, 10}, {20, 20}}}, Rep: "MULTILINESTRING ((10 10),(10 10,20 20))", }, - "two lines two,zero point": { + { Geom: geom.MultiLineString{{{10, 10}, {20, 20}}, {}}, Rep: "MULTILINESTRING ((10 10,20 20))", }, - "two lines two,one point": { + { Geom: geom.MultiLineString{{{10, 10}, {20, 20}}, {{10, 10}}}, Rep: "MULTILINESTRING ((10 10,20 20),(10 10))", }, - "two lines two,two point": { + { Geom: geom.MultiLineString{{{10, 10}, {20, 20}}, {{10, 10}, {20, 20}}}, Rep: "MULTILINESTRING ((10 10,20 20),(10 10,20 20))", }, }, "Polygon": { - "empty nil": { + { Geom: (*geom.Polygon)(nil), Rep: "POLYGON EMPTY", }, - "empty": { + { Geom: geom.Polygon{}, Rep: "POLYGON EMPTY", }, - "one line zero": { + { Geom: geom.Polygon{{}}, Rep: "POLYGON EMPTY", }, - "two lines zero zero": { + { Geom: geom.Polygon{{}, {}}, Rep: "POLYGON EMPTY", }, - "two lines one zero": { + { Geom: geom.Polygon{{{10, 10}, {11, 11}, {12, 12}}, {}}, - Rep: "POLYGON ((10 10,11 11,12 12))", + Rep: "POLYGON ((10 10,11 11,12 12,10 10))", }, - "two lines one one": { + { Geom: geom.Polygon{{{10, 10}, {11, 11}, {12, 12}}, {{20, 20}, {21, 21}, {22, 22}}}, - Rep: "POLYGON ((10 10,11 11,12 12),(20 20,21 21,22 22))", + Rep: "POLYGON ((10 10,11 11,12 12,10 10),(20 20,21 21,22 22,20 20))", }, - "two lines zero one": { + { Geom: geom.Polygon{{}, {{10, 10}, {11, 11}, {12, 12}}}, - Rep: "POLYGON ((10 10,11 11,12 12))", + Rep: "POLYGON ((10 10,11 11,12 12,10 10))", }, }, "MultiPolygon": { - "empty nil": { + { Geom: (*geom.MultiPolygon)(nil), Rep: "MULTIPOLYGON EMPTY", }, - "empty MultiPolygon": { - Geom: geom.MultiPolygon{}, + { + Geom: &geom.MultiPolygon{}, Rep: "MULTIPOLYGON EMPTY", }, - "empty one polygon": { - Geom: geom.MultiPolygon{{}}, + { + Geom: &geom.MultiPolygon{{}}, Rep: "MULTIPOLYGON EMPTY", }, - "empty one polygon one line": { - Geom: geom.MultiPolygon{{{}}}, + { + Geom: &geom.MultiPolygon{{{}}}, Rep: "MULTIPOLYGON EMPTY", }, - "empty two polygon 0": { - Geom: geom.MultiPolygon{{}, {}}, + { + Geom: &geom.MultiPolygon{{}, {}}, Rep: "MULTIPOLYGON EMPTY", }, - "empty two polygon 1": { - Geom: geom.MultiPolygon{{{}}, {}}, + { + Geom: &geom.MultiPolygon{{{}}, {}}, Rep: "MULTIPOLYGON EMPTY", }, - "empty two polygon 2": { - Geom: geom.MultiPolygon{{}, {{}}}, + { + Geom: &geom.MultiPolygon{{}, {{}}}, Rep: "MULTIPOLYGON EMPTY", }, - "empty two polygon 3": { - Geom: geom.MultiPolygon{{{}}, {{}}}, + { + Geom: &geom.MultiPolygon{{{}}, {{}}}, Rep: "MULTIPOLYGON EMPTY", }, - "one polygon": { - Geom: geom.MultiPolygon{{{{10, 10}, {11, 11}, {12, 12}}}}, - Rep: "MULTIPOLYGON (((10 10,11 11,12 12)))", + { + Geom: &geom.MultiPolygon{{{{10, 10}, {11, 11}, {12, 12}}}}, + Rep: "MULTIPOLYGON (((10 10,11 11,12 12,10 10)))", }, }, "Collectioner": { - "empty nil": { + { Geom: (*geom.Collection)(nil), Rep: "GEOMETRYCOLLECTION EMPTY", }, - "empty": { + { Geom: geom.Collection{}, Rep: "GEOMETRYCOLLECTION EMPTY", }, - "empty nil point": { + { Geom: geom.Collection{ (*geom.Point)(nil), }, Rep: "GEOMETRYCOLLECTION EMPTY", }, - "empty nil MultiPoint": { + { Geom: geom.Collection{ (*geom.MultiPoint)(nil), }, Rep: "GEOMETRYCOLLECTION EMPTY", }, - "empty nil LineString": { + { Geom: geom.Collection{ (*geom.LineString)(nil), }, Rep: "GEOMETRYCOLLECTION EMPTY", }, - "empty nil MultiLineString": { + { Geom: geom.Collection{ (*geom.MultiLineString)(nil), }, Rep: "GEOMETRYCOLLECTION EMPTY", }, - "empty nil Polygon": { + { Geom: geom.Collection{ (*geom.Polygon)(nil), }, Rep: "GEOMETRYCOLLECTION EMPTY", }, - "empty nil MultiPolygon": { + { Geom: geom.Collection{ (*geom.MultiPolygon)(nil), }, Rep: "GEOMETRYCOLLECTION EMPTY", }, - "empty MultiPoint": { + { Geom: geom.Collection{ geom.MultiPoint{}, }, Rep: "GEOMETRYCOLLECTION EMPTY", }, - "empty LineString": { + { Geom: geom.Collection{ geom.LineString{}, }, Rep: "GEOMETRYCOLLECTION EMPTY", }, - "empty MultiLineString": { + { Geom: geom.Collection{ geom.MultiLineString{}, }, Rep: "GEOMETRYCOLLECTION EMPTY", }, - "empty Polygon": { + { Geom: geom.Collection{ geom.Polygon{}, }, Rep: "GEOMETRYCOLLECTION EMPTY", }, - "empty MultiPolygon": { + { Geom: geom.Collection{ - geom.MultiPolygon{}, + &geom.MultiPolygon{}, }, Rep: "GEOMETRYCOLLECTION EMPTY", }, - "point": { + { Geom: geom.Collection{ geom.Point{10, 10}, }, Rep: "GEOMETRYCOLLECTION (POINT (10 10))", }, - "point and linestring": { + { Geom: geom.Collection{ geom.Point{10, 10}, geom.LineString{{11, 11}, {22, 22}}, @@ -310,9 +313,8 @@ func TestEncode(t *testing.T) { } for name, subtests := range tests { t.Run(name, func(t *testing.T) { - for subname, tc := range subtests { - tc := tc - t.Run(subname, func(t *testing.T) { fn(t, tc) }) + for _, tc := range subtests { + t.Run(fn(tc)) } }) }