Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

encoding/wkt: fix handling of collections with empty elements #184

Merged
merged 1 commit into from
Aug 30, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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