Skip to content
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
192 changes: 192 additions & 0 deletions utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
package geom

import "fmt"

// ApplyToPoints applys the given function to each point in the geometry and any sub geometries, return a new transformed geometry.
func ApplyToPoints(geometry Geometry, f func(coords ...float64) ([]float64, error)) (Geometry, error) {
switch geo := geometry.(type) {
default:
return nil, fmt.Errorf("unknown Geometry: %T", geometry)

case Point:
c, err := f(geo.X(), geo.Y())
if err != nil {
return nil, err
}
if len(c) < 2 {
return nil, fmt.Errorf("function did not return minimum number of coordinates got %v expected 2", len(c))
}
return Point{c[0], c[1]}, nil

case MultiPoint:
pts := make(MultiPoint, len(geo))

for i, pt := range geo {
c, err := f(pt[:]...)
if err != nil {
return nil, err
}
if len(c) < 2 {
return nil, fmt.Errorf("function did not return minimum number of coordinates got %v expected 2", len(c))
}
pts[i][0], pts[i][1] = c[0], c[1]
}
return pts, nil

case LineString:
line := make(LineString, len(geo))
for i, pt := range geo {
c, err := f(pt[:]...)
if err != nil {
return nil, err
}
if len(c) < 2 {
return nil, fmt.Errorf("function did not return minimum number of coordinates got %v expected 2", len(c))
}
line[i][0], line[i][1] = c[0], c[1]
}
return line, nil

case MultiLineString:
lines := make(MultiLineString, len(geo))

for i, line := range geo {
// getting a geometry interface back
linei, err := ApplyToPoints(LineString(line), f)
if err != nil {
return nil, fmt.Errorf("got error converting line(%v) of multiline: %v", i, err)
}

// get the value
linev, ok := linei.(LineString)
if !ok {
panic("we did not get the conversion we were expecting")
}

lines[i] = linev
}
return lines, nil

case Polygon:
poly := make(Polygon, len(geo))

for i, line := range geo {
// getting a geometry inteface back
linei, err := ApplyToPoints(LineString(line), f)
if err != nil {
return nil, fmt.Errorf("got error converting line(%v) of polygon: %v", i, err)
}

// get the value
linev, ok := linei.(LineString)
if !ok {
panic("we did not get the conversion we were expecting")
}

poly[i] = linev
}
return poly, nil

case MultiPolygon:
mpoly := make(MultiPolygon, len(geo))

for i, poly := range geo {
// getting a geometry inteface back
polyi, err := ApplyToPoints(Polygon(poly), f)
if err != nil {
return nil, fmt.Errorf("got error converting poly(%v) of multipolygon: %v", i, err)
}

// get the value
polyv, ok := polyi.(Polygon)
if !ok {
panic("we did not get the conversion we were expecting")
}

mpoly[i] = polyv
}
return mpoly, nil
}
}

// Clone returns a deep clone of the Geometry.
func Clone(geometry Geometry) (Geometry, error) {
switch geo := geometry.(type) {
default:
return nil, fmt.Errorf("unknown Geometry: %T", geometry)

case Point:
return Point{geo.X(), geo.Y()}, nil

case MultiPoint:
pts := make(MultiPoint, len(geo))
for i, pt := range geo {
pts[i] = pt
}
return pts, nil

case LineString:
line := make(LineString, len(geo))
for i, pt := range geo {
line[i] = pt
}
return line, nil

case MultiLineString:
lines := make(MultiLineString, len(geo))
for i, line := range geo {
// getting a geometry interface back
linei, err := Clone(LineString(line))
if err != nil {
return nil, fmt.Errorf("got error converting line(%v) of multiline: %v", i, err)
}

// get the value
linev, ok := linei.(LineString)
if !ok {
panic("we did not get the conversion we were expecting")
}

lines[i] = linev
}
return lines, nil

case Polygon:
// getting a geometry inteface back
poly := make(Polygon, len(geo))
for i, line := range geo {
linei, err := Clone(LineString(line))
if err != nil {
return nil, fmt.Errorf("got error converting line(%v) of polygon: %v", i, err)
}

// get the value
linev, ok := linei.(LineString)
if !ok {
panic("we did not get the conversion we were expecting")
}

poly[i] = linev
}
return poly, nil

case MultiPolygon:
mpoly := make(MultiPolygon, len(geo))
for i, poly := range geo {
// getting a geometry inteface back
polyi, err := Clone(Polygon(poly))
if err != nil {
return nil, fmt.Errorf("got error converting polygon(%v) of multipolygon: %v", i, err)
}

// get the value
polyv, ok := polyi.(Polygon)
if !ok {
panic("we did not get the conversion we were expecting")
}

mpoly[i] = polyv
}
return mpoly, nil
}
}
103 changes: 103 additions & 0 deletions utils_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package geom

import (
"errors"
"reflect"
"testing"
)

func TestClone(t *testing.T) {
type tcase struct {
a Geometry
err string
}

fn := func(tc tcase) func(t *testing.T) {
return func(t *testing.T) {
b, err := Clone(tc.a)
if err != nil {
if err.Error() != tc.err {
t.Fatalf("expected error %v got %v", tc.err, err)
}
return
} else if len(tc.err) != 0 {
t.Fatalf("expected error %v got %v", tc.err, err)
return
}

if !reflect.DeepEqual(b, tc.a) {
t.Fatalf("expected geom %v got %v", tc.a, b)
}
}
}

tcases := map[string]tcase{
"err type": {
a: int(0),
err: "unknown Geometry: int",
},
"point ok": {
a: Point{},
},
"point ok 2": {
a: Point{3.14, 2.7},
},
}

for k, v := range tcases {
t.Run(k, fn(v))
}
}

func TestApply(t *testing.T) {
type tcase struct {
a, b Geometry
f func(...float64) ([]float64, error)
err string
}

fn := func(tc tcase) func(*testing.T) {
return func(t *testing.T) {
b, err := ApplyToPoints(tc.a, tc.f)
if err != nil {
if err.Error() != tc.err {
t.Fatalf("expected error %v got %v", tc.err, err)
}
return
} else if len(tc.err) != 0 {
t.Fatalf("expected error %v got %v", tc.err, err)
}

if !reflect.DeepEqual(b, tc.b) {
t.Fatalf("expected %v got %v", tc.b, b)
}
}
}

tcases := map[string]tcase{
"type err": {
a: int(0),
err: "unknown Geometry: int",
},
"func err": {
a: Point{},
f: func(...float64) ([]float64, error) { return nil, errors.New("fn err") },
err: "fn err",
},
"add 1": {
a: Point{},
b: Point{1, 1},
f: func(p ...float64) ([]float64, error) {
for i, v := range p {
p[i] = v + 1
}

return p, nil
},
},
}

for k, v := range tcases {
t.Run(k, fn(v))
}
}