Skip to content

Commit

Permalink
Merge pull request #184 from erikgrinaker/wkt-gc-empty
Browse files Browse the repository at this point in the history
encoding/wkt: fix handling of collections with empty elements
  • Loading branch information
otan committed Aug 30, 2020
2 parents 9e0b3e4 + 8e54ea8 commit 218e778
Show file tree
Hide file tree
Showing 3 changed files with 140 additions and 41 deletions.
82 changes: 59 additions & 23 deletions encoding/wkt/decode.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ func decode(wkt string) (geom.T, error) {

switch t {
case tPoint:
coords, _, err := readCoordsDim1(l, wkt)
coords, _, err := readCoordsDim1(l, wkt, false)
if err != nil {
return nil, err
}
Expand All @@ -30,7 +30,7 @@ func decode(wkt string) (geom.T, error) {
}
return p, nil
case tLineString:
coords, _, err := readCoordsDim1(l, wkt)
coords, _, err := readCoordsDim1(l, wkt, false)
if err != nil {
return nil, err
}
Expand All @@ -53,7 +53,7 @@ func decode(wkt string) (geom.T, error) {
}
return p, nil
case tMultiPoint:
coords, _, err := readCoordsDim1(l, wkt)
coords, _, err := readCoordsDim1(l, wkt, true)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -163,18 +163,22 @@ func createGeomCollectionForWkt(wkt string) (*geom.GeometryCollection, error) {
return gc, nil
}

func readCoordsDim1(l geom.Layout, wkt string) ([]geom.Coord, string, error) {
isEmpty := strings.HasSuffix(wkt, tEmpty)
if isEmpty {
func readCoordsDim1(
l geom.Layout, wkt string, allowEmptyCoordsInBrace bool,
) ([]geom.Coord, string, error) {
if strings.HasPrefix(wkt, tEmpty) {
rest := strings.TrimLeft(wkt[len(tEmpty):], ", ")
return []geom.Coord{}, rest, nil
} else if strings.HasSuffix(wkt, tEmpty) {
return []geom.Coord{}, "", nil
}

braceContent, rest, err := braceContentAndRestStartingWithOpeningBrace(wkt)
braceContent, rest, err := braceContentAndRestAfterComma(wkt)
if err != nil {
return nil, rest, err
}

coords, err := coordsFromBraceContent(braceContent, l)
coords, err := coordsFromBraceContent(braceContent, l, allowEmptyCoordsInBrace)
if err != nil {
return nil, rest, err
}
Expand All @@ -184,18 +188,24 @@ func readCoordsDim1(l geom.Layout, wkt string) ([]geom.Coord, string, error) {

func readCoordsDim2(l geom.Layout, wkt string) ([][]geom.Coord, string, error) {
coordsDim2 := [][]geom.Coord{}
isEmpty := strings.HasSuffix(wkt, tEmpty)
if isEmpty {
if strings.HasPrefix(wkt, tEmpty) {
rest := strings.TrimLeft(wkt[len(tEmpty):], ", ")
return coordsDim2, rest, nil
} else if strings.HasSuffix(wkt, tEmpty) {
return coordsDim2, "", nil
}

contentDim2, restDim2, err := braceContentAndRestStartingWithOpeningBrace(wkt)
if strings.HasSuffix(wkt, tEmpty) {
return coordsDim2, "", nil
}

contentDim2, restDim2, err := braceContentAndRestAfterComma(wkt)
if err != nil {
return nil, restDim2, err
}

for {
coordsDim1, restDim1, err := readCoordsDim1(l, contentDim2)
coordsDim1, restDim1, err := readCoordsDim1(l, contentDim2, false)
if err != nil {
return coordsDim2, restDim2, err
}
Expand All @@ -213,8 +223,7 @@ func readCoordsDim2(l geom.Layout, wkt string) ([][]geom.Coord, string, error) {

func readCoordsDim3(l geom.Layout, wkt string) ([][][]geom.Coord, string, error) {
coordsDim3 := [][][]geom.Coord{}
isEmpty := strings.HasSuffix(wkt, tEmpty)
if isEmpty {
if strings.HasSuffix(wkt, tEmpty) {
return coordsDim3, "", nil
}

Expand All @@ -240,12 +249,18 @@ func readCoordsDim3(l geom.Layout, wkt string) ([][][]geom.Coord, string, error)
return coordsDim3, restDim3, nil
}

func coordsFromBraceContent(s string, l geom.Layout) ([]geom.Coord, error) {
func coordsFromBraceContent(s string, l geom.Layout, allowEmpty bool) ([]geom.Coord, error) {
coords := []geom.Coord{}

coordStrings := strings.Split(s, ",")
for _, coordStr := range coordStrings {
coordElems := strings.Split(strings.TrimSpace(coordStr), " ")
coordStr = strings.TrimSpace(coordStr)
if allowEmpty && coordStr == tEmpty {
coords = append(coords, nil)
continue
}

coordElems := strings.Split(coordStr, " ")
if len(coordElems) != l.Stride() {
return nil, geom.ErrStrideMismatch{
Got: len(coordElems),
Expand Down Expand Up @@ -300,6 +315,21 @@ func braceContentAndRest(s string) (string, string, error) {
return braceContent, rest, nil
}

func braceContentAndRestAfterComma(s string) (string, string, error) {
content, rest, err := braceContentAndRest(s)
if err != nil {
return content, rest, err
}

nextComma := strings.Index(rest, ",")
if nextComma >= 0 {
rest = strings.TrimSpace(rest[nextComma+1:])
} else {
rest = ""
}
return content, rest, nil
}

func braceContentAndRestStartingWithOpeningBrace(s string) (string, string, error) {
content, rest, err := braceContentAndRest(s)
if err != nil {
Expand All @@ -316,16 +346,22 @@ func braceContentAndRestStartingWithOpeningBrace(s string) (string, string, erro
}

func typeContentAndRestStartingWithLetter(s string) (string, string, error) {
content, rest, err := braceContentAndRest(s)
content, _, err := findTypeAndLayout(s)
if err != nil {
return content, rest, err
return "", "", err
}

t, _, err := findTypeAndLayout(s)
if err != nil {
return content, rest, err
rest := s[len(content):]
if strings.HasPrefix(rest, tEmpty) {
content += tEmpty
rest = rest[len(tEmpty):]
} else {
c, r, err := braceContentAndRest(rest)
if err != nil {
return content, rest, err
}
content = content + "(" + c + ")"
rest = r
}
content = t + "(" + content + ")"

nextLetterIdx := -1
for i, char := range rest {
Expand Down
59 changes: 49 additions & 10 deletions encoding/wkt/encode.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,22 +78,22 @@ func (e *Encoder) write(sb *strings.Builder, g geom.T) error {
}
return e.writeFlatCoords2(sb, g.FlatCoords(), 0, g.Ends(), layout.Stride())
case *geom.MultiPoint:
if g.Empty() {
if g.NumPoints() == 0 {
return e.writeEMPTY(sb)
}
return e.writeFlatCoords1(sb, g.FlatCoords(), layout.Stride())
return e.writeFlatCoords1Ends(sb, g.FlatCoords(), 0, g.Ends())
case *geom.MultiLineString:
if g.Empty() {
if g.NumLineStrings() == 0 {
return e.writeEMPTY(sb)
}
return e.writeFlatCoords2(sb, g.FlatCoords(), 0, g.Ends(), layout.Stride())
case *geom.MultiPolygon:
if g.Empty() {
if g.NumPolygons() == 0 {
return e.writeEMPTY(sb)
}
return e.writeFlatCoords3(sb, g.FlatCoords(), g.Endss(), layout.Stride())
case *geom.GeometryCollection:
if g.Empty() {
if g.NumGeoms() == 0 {
return e.writeEMPTY(sb)
}
if _, err := sb.WriteRune('('); err != nil {
Expand Down Expand Up @@ -168,6 +168,33 @@ func (e *Encoder) writeFlatCoords1(sb *strings.Builder, flatCoords []float64, st
return err
}

func (e *Encoder) writeFlatCoords1Ends(
sb *strings.Builder, flatCoords []float64, start int, ends []int,
) error {
if _, err := sb.WriteRune('('); err != nil {
return err
}
for i, end := range ends {
if i != 0 {
if _, err := sb.WriteString(", "); err != nil {
return err
}
}
if end <= start {
if err := e.writeEMPTY(sb); err != nil {
return err
}
} else {
if err := e.writeCoord(sb, flatCoords[start:end]); err != nil {
return err
}
}
start = end
}
_, err := sb.WriteRune(')')
return err
}

func (e *Encoder) writeFlatCoords2(
sb *strings.Builder, flatCoords []float64, start int, ends []int, stride int,
) error {
Expand All @@ -180,8 +207,14 @@ func (e *Encoder) writeFlatCoords2(
return err
}
}
if err := e.writeFlatCoords1(sb, flatCoords[start:end], stride); err != nil {
return err
if end <= start {
if err := e.writeEMPTY(sb); err != nil {
return err
}
} else {
if err := e.writeFlatCoords1(sb, flatCoords[start:end], stride); err != nil {
return err
}
}
start = end
}
Expand All @@ -202,10 +235,16 @@ func (e *Encoder) writeFlatCoords3(
return err
}
}
if err := e.writeFlatCoords2(sb, flatCoords, start, ends, stride); err != nil {
return err
if len(ends) == 0 {
if err := e.writeEMPTY(sb); err != nil {
return err
}
} else {
if err := e.writeFlatCoords2(sb, flatCoords, start, ends, stride); err != nil {
return err
}
start = ends[len(ends)-1]
}
start = ends[len(ends)-1]
}
_, err := sb.WriteRune(')')
return err
Expand Down
40 changes: 32 additions & 8 deletions encoding/wkt/wkt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,41 +74,53 @@ func TestMarshalAndUnmarshal(t *testing.T) {
g: geom.NewMultiPoint(geom.XY),
s: "MULTIPOINT EMPTY",
},
{
g: geom.NewMultiPoint(geom.XY).MustSetCoords([]geom.Coord{nil, nil}),
s: "MULTIPOINT (EMPTY, EMPTY)",
},
{
g: geom.NewMultiPoint(geom.XY).MustSetCoords([]geom.Coord{{1, 2}}),
s: "MULTIPOINT (1 2)",
},
{
g: geom.NewMultiPoint(geom.XY).MustSetCoords([]geom.Coord{{1, 2}, {3, 4}}),
s: "MULTIPOINT (1 2, 3 4)",
g: geom.NewMultiPoint(geom.XY).MustSetCoords([]geom.Coord{{1, 2}, nil, {3, 4}}),
s: "MULTIPOINT (1 2, EMPTY, 3 4)",
},
{
g: geom.NewMultiPoint(geom.XYZM).MustSetCoords([]geom.Coord{{1, 2, 1, 42}, {3, 4, 1, 43}}),
s: "MULTIPOINT ZM (1 2 1 42, 3 4 1 43)",
g: geom.NewMultiPoint(geom.XYZM).MustSetCoords([]geom.Coord{{1, 2, 1, 42}, nil, {3, 4, 1, 43}}),
s: "MULTIPOINT ZM (1 2 1 42, EMPTY, 3 4 1 43)",
},
{
g: geom.NewMultiLineString(geom.XY),
s: "MULTILINESTRING EMPTY",
},
{
g: geom.NewMultiLineString(geom.XY).MustSetCoords([][]geom.Coord{nil, nil}),
s: "MULTILINESTRING (EMPTY, EMPTY)",
},
{
g: geom.NewMultiLineString(geom.XY).MustSetCoords([][]geom.Coord{{{1, 2}, {3, 4}}}),
s: "MULTILINESTRING ((1 2, 3 4))",
},
{
g: geom.NewMultiLineString(geom.XY).MustSetCoords([][]geom.Coord{{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}}),
s: "MULTILINESTRING ((1 2, 3 4), (5 6, 7 8))",
g: geom.NewMultiLineString(geom.XY).MustSetCoords([][]geom.Coord{{{1, 2}, {3, 4}}, nil, {{5, 6}, {7, 8}}}),
s: "MULTILINESTRING ((1 2, 3 4), EMPTY, (5 6, 7 8))",
},
{
g: geom.NewMultiPolygon(geom.XY),
s: "MULTIPOLYGON EMPTY",
},
{
g: geom.NewMultiPolygon(geom.XY).MustSetCoords([][][]geom.Coord{nil, nil}),
s: "MULTIPOLYGON (EMPTY, EMPTY)",
},
{
g: geom.NewMultiPolygon(geom.XY).MustSetCoords([][][]geom.Coord{{{{1, 2}, {3, 4}, {5, 6}}}}),
s: "MULTIPOLYGON (((1 2, 3 4, 5 6)))",
},
{
g: geom.NewMultiPolygon(geom.XY).MustSetCoords([][][]geom.Coord{{{{1, 2}, {3, 4}, {5, 6}}}, {{{7, 8}, {9, 10}, {11, 12}}}}),
s: "MULTIPOLYGON (((1 2, 3 4, 5 6)), ((7 8, 9 10, 11 12)))",
g: geom.NewMultiPolygon(geom.XY).MustSetCoords([][][]geom.Coord{{{{1, 2}, {3, 4}, {5, 6}}}, nil, {{{7, 8}, {9, 10}, {11, 12}}}}),
s: "MULTIPOLYGON (((1 2, 3 4, 5 6)), EMPTY, ((7 8, 9 10, 11 12)))",
},
{
g: geom.NewMultiPolygon(geom.XYZM).MustSetCoords([][][]geom.Coord{
Expand All @@ -126,6 +138,18 @@ func TestMarshalAndUnmarshal(t *testing.T) {
g: geom.NewGeometryCollection(),
s: "GEOMETRYCOLLECTION EMPTY",
},
{
g: geom.NewGeometryCollection().MustPush(geom.NewGeometryCollection()),
s: "GEOMETRYCOLLECTION (GEOMETRYCOLLECTION EMPTY)",
},
{
g: geom.NewGeometryCollection().MustPush(
geom.NewPointEmpty(geom.XY),
geom.NewLineString(geom.XY),
geom.NewPolygon(geom.XY),
),
s: "GEOMETRYCOLLECTION (POINT EMPTY, LINESTRING EMPTY, POLYGON EMPTY)",
},
{
g: geom.NewGeometryCollection().MustPush(
geom.NewPoint(geom.XY).MustSetCoords(geom.Coord{1, 2}),
Expand Down

0 comments on commit 218e778

Please sign in to comment.