diff --git a/encoding/error.go b/encoding/error.go index ac2dd82a..523b90ce 100644 --- a/encoding/error.go +++ b/encoding/error.go @@ -1,15 +1 @@ package encoding - -import ( - "fmt" - - "github.com/go-spatial/geom" -) - -type ErrUnknownGeometry struct { - Geom geom.Geometry -} - -func (e ErrUnknownGeometry) Error() string { - return fmt.Sprintf("unknown geometry: %T", e.Geom) -} diff --git a/encoding/geojson/geojson.go b/encoding/geojson/geojson.go index 19b48fd7..7ad80823 100644 --- a/encoding/geojson/geojson.go +++ b/encoding/geojson/geojson.go @@ -4,7 +4,6 @@ import ( "encoding/json" "github.com/go-spatial/geom" - "github.com/go-spatial/geom/encoding" ) type GeoJSONType string @@ -95,7 +94,7 @@ func (geo Geometry) MarshalJSON() ([]byte, error) { }) default: - return nil, encoding.ErrUnknownGeometry{g} + return nil, geom.ErrUnknownGeometry{g} } } diff --git a/encoding/geojson/geojson_test.go b/encoding/geojson/geojson_test.go index 41314381..9da9cfe4 100644 --- a/encoding/geojson/geojson_test.go +++ b/encoding/geojson/geojson_test.go @@ -6,7 +6,6 @@ import ( "testing" "github.com/go-spatial/geom" - "github.com/go-spatial/geom/encoding" "github.com/go-spatial/geom/encoding/geojson" ) @@ -109,7 +108,7 @@ func TestFeatureMarshalJSON(t *testing.T) { geom: nil, expectedErr: json.MarshalerError{ Type: reflect.TypeOf(geojson.Geometry{}), - Err: encoding.ErrUnknownGeometry{nil}, + Err: geom.ErrUnknownGeometry{nil}, }, }, } diff --git a/encoding/wkb/wkb.go b/encoding/wkb/wkb.go index d6bf9c59..4298113d 100644 --- a/encoding/wkb/wkb.go +++ b/encoding/wkb/wkb.go @@ -17,14 +17,6 @@ import ( "github.com/go-spatial/geom/encoding/wkb/internal/encode" ) -type ErrUnknownGeometry struct { - Geom geom.Geometry -} - -func (e ErrUnknownGeometry) Error() string { - return fmt.Sprintf("Unknown Geometry! %v", e.Geom) -} - type ErrUnknownGeometryType struct { Typ uint32 } @@ -108,7 +100,7 @@ func _encode(en *encode.Encoder, g geom.Geometry) error { } } default: - return ErrUnknownGeometry{g} + return geom.ErrUnknownGeometry{g} } return en.Err() } diff --git a/encoding/wkt/wkt.go b/encoding/wkt/wkt.go index 3a542b7e..686a5e0f 100644 --- a/encoding/wkt/wkt.go +++ b/encoding/wkt/wkt.go @@ -104,14 +104,6 @@ use to take a tagola.Geometry and convert it to a wkt string. It will, also, contain functions to parse a wkt string into a wkb.Geometry. */ -type ErrUnknownGeometry struct { - Geom geom.Geometry -} - -func (e ErrUnknownGeometry) Error() string { - return fmt.Sprintf("Unknown Geometry! %v", e.Geom) -} - func _encode(geo geom.Geometry) string { switch g := geo.(type) { @@ -173,7 +165,7 @@ func _encode(geo geom.Geometry) string { func Encode(geo geom.Geometry) (string, error) { switch g := geo.(type) { default: - return "", ErrUnknownGeometry{geo} + return "", geom.ErrUnknownGeometry{geo} case geom.Pointer: // POINT( 10 10) if isNil(g) { diff --git a/encoding/wkt/wkt_test.go b/encoding/wkt/wkt_test.go index f876663f..85e7596d 100644 --- a/encoding/wkt/wkt_test.go +++ b/encoding/wkt/wkt_test.go @@ -33,7 +33,7 @@ func TestEncode(t *testing.T) { tests := map[string]map[string]tcase{ "Point": { "empty nil": { - Err: ErrUnknownGeometry{nil}, + Err: geom.ErrUnknownGeometry{nil}, }, "empty": { Geom: (*geom.Point)(nil), diff --git a/errors.go b/errors.go new file mode 100644 index 00000000..7fead4be --- /dev/null +++ b/errors.go @@ -0,0 +1,12 @@ +package geom + +import "fmt" + +// ErrUnknownGeometry represents an objects that is not a known geom geometry. +type ErrUnknownGeometry struct { + Geom Geometry +} + +func (e ErrUnknownGeometry) Error() string { + return fmt.Sprintf("unknown geometry: %T", e.Geom) +} diff --git a/geom.go b/geom.go index f89fd810..703f532a 100644 --- a/geom.go +++ b/geom.go @@ -1,11 +1,6 @@ // Package geom describes geometry interfaces. package geom -import "errors" - -// ErrUnknownGeometry is returned when the geometry type is unknown or unsupported. -var ErrUnknownGeometry = errors.New("unknown geometry") - // Geometry is an object with a spatial reference. // if a method accepts a Geometry type it's only expected to support the geom types in this package type Geometry interface{} @@ -62,7 +57,7 @@ func getCoordinates(g Geometry, pts *[]Point) error { default: - return ErrUnknownGeometry + return ErrUnknownGeometry{g} case Pointer: diff --git a/planar/planar.go b/planar/planar.go new file mode 100644 index 00000000..b48b346e --- /dev/null +++ b/planar/planar.go @@ -0,0 +1,40 @@ +package planar + +import ( + "math" +) + +const Rad = math.Pi / 180 + +type PointLineDistanceFunc func(line [2][2]float64, point [2]float64) float64 + +// PerpendicularDistance provides the distance between a line and a point in Euclidean space. +// ref: https://en.wikipedia.org/wiki/Distance_from_a_point_to_a_line#Line_defined_by_two_points +func PerpendicularDistance(line [2][2]float64, point [2]float64) float64 { + + deltaX := line[1][0] - line[0][0] + deltaY := line[1][1] - line[0][1] + deltaXSq := deltaX * deltaX + deltaYSq := deltaY * deltaY + + num := math.Abs((deltaY * point[0]) - (deltaX * point[1]) + (line[1][0] * line[0][1]) - (line[1][1] * line[0][0])) + denom := math.Sqrt(deltaYSq + deltaXSq) + if denom == 0 { + return 0 + } + return num / denom +} + +// Slope — finds the Slope of a line +func Slope(line [2][2]float64) (m, b float64, defined bool) { + dx := line[1][0] - line[0][0] + dy := line[1][1] - line[0][1] + if dx == 0 || dy == 0 { + // if dx == 0 then m == 0; and the intercept is y. + // However if the lines are verticle then the slope is not defined. + return 0, line[0][1], dx != 0 + } + m = dy / dx + b = line[0][1] - (m * line[0][0]) + return m, b, true +} diff --git a/planar/planar_test.go b/planar/planar_test.go new file mode 100644 index 00000000..f050d0ac --- /dev/null +++ b/planar/planar_test.go @@ -0,0 +1,50 @@ +package planar + +import ( + "strconv" + "testing" +) + +func TestSlope(t *testing.T) { + type tcase struct { + line [2][2]float64 + m, b float64 + defined bool + } + + fn := func(t *testing.T, tc tcase) { + t.Parallel() + gm, gb, gd := Slope(tc.line) + if tc.defined != gd { + t.Errorf("sloped defined, expected %v got %v", tc.defined, gd) + return + } + // if the slope is not defined, line is verticle and m,b don't have good values. + if !tc.defined { + return + } + if tc.m != gm { + t.Errorf("sloped, expected %v got %v", tc.m, gm) + + } + if tc.b != gb { + t.Errorf("sloped intercept, expected %v got %v", tc.b, gb) + } + } + tests := []tcase{ + { + line: [2][2]float64{{0, 0}, {10, 10}}, + m: 1, + b: 0, + defined: true, + }, + { + line: [2][2]float64{{1, 7}, {1, 17}}, + defined: false, + }, + } + for i := range tests { + tc := tests[i] + t.Run(strconv.FormatInt(int64(i), 10), func(t *testing.T) { fn(t, tc) }) + } +} diff --git a/planar/simplify.go b/planar/simplify.go new file mode 100644 index 00000000..f72f57d8 --- /dev/null +++ b/planar/simplify.go @@ -0,0 +1,87 @@ +package planar + +import "github.com/go-spatial/geom" + +// Simplifer is an interface for Simplifying geometries. +type Simplifer interface { + Simplify(linestring [][2]float64, isClosed bool) ([][2]float64, error) +} + +func simplifyPolygon(simplifer Simplifer, plg [][][2]float64, isClosed bool) (ret [][][2]float64, err error) { + ret = make([][][2]float64, len(plg)) + for i := range plg { + ls, err := simplifer.Simplify(plg[i], isClosed) + if err != nil { + return nil, err + } + ret[i] = ls + } + return ret, nil + +} + +// Simplify will simplify the provided geometry using the provided simplifer. +// If the simplifer is nil, no simplification will be attempted. +func Simplify(simplifer Simplifer, geometry geom.Geometry) (geom.Geometry, error) { + + if simplifer == nil { + return geometry, nil + } + + switch gg := geometry.(type) { + + case geom.Collectioner: + + geos := gg.Geometries() + coll := make([]geom.Geometry, len(geos)) + for i := range geos { + geo, err := Simplify(simplifer, geos[i]) + if err != nil { + return nil, err + } + coll[i] = geo + } + return geom.Collection(coll), nil + + case geom.MultiPolygoner: + + plys := gg.Polygons() + mply := make([][][][2]float64, len(plys)) + for i := range plys { + ply, err := simplifyPolygon(simplifer, plys[i], true) + if err != nil { + return nil, err + } + mply[i] = ply + } + return geom.MultiPolygon(mply), nil + + case geom.Polygoner: + + ply, err := simplifyPolygon(simplifer, gg.LinearRings(), true) + if err != nil { + return nil, err + } + return geom.Polygon(ply), nil + + case geom.MultiLineStringer: + + mls, err := simplifyPolygon(simplifer, gg.LineStrings(), false) + if err != nil { + return nil, err + } + return geom.MultiLineString(mls), nil + + case geom.LineStringer: + + ls, err := simplifer.Simplify(gg.Verticies(), false) + if err != nil { + return nil, err + } + return geom.LineString(ls), nil + + default: // Points, MutliPoints or anything else. + return geometry, nil + + } +} diff --git a/slippy/tile.go b/slippy/tile.go index ba37d447..152dbb78 100644 --- a/slippy/tile.go +++ b/slippy/tile.go @@ -58,7 +58,6 @@ func Lat2Tile(zoom uint, lat float64) (y uint) { } func Lon2Tile(zoom uint, lon float64) (x uint) { - return uint(math.Exp2(float64(zoom)) * (lon + 180.0) / 360.0) }