diff --git a/cmp/compare.go b/cmp/compare.go index d6757335..4e66e935 100644 --- a/cmp/compare.go +++ b/cmp/compare.go @@ -7,11 +7,11 @@ import ( "github.com/go-spatial/geom" ) -// Compare holds the tolerances for the comparsion functions +// Compare holds the tolerances for the comparison functions type Compare struct { // Tolerance is the epsilon value used in comparing floats with zero Tolerance float64 - // BitTolerance is the epsilon value for comaparing float bit-patterns. + // BitTolerance is the epsilon value for comparing float bit-patterns. BitTolerance int64 } @@ -81,7 +81,7 @@ func (cmp Compare) FloatSlice(f1, f2 []float64) bool { return true } -// Extent will check to see if the Extents's are the same. +// Extent will check to see if the Extents' are the same. func (cmp Compare) Extent(extent1, extent2 [4]float64) bool { return cmp.Float(extent1[0], extent2[0]) && cmp.Float(extent1[1], extent2[1]) && cmp.Float(extent1[2], extent2[2]) && cmp.Float(extent1[3], extent2[3]) @@ -275,7 +275,7 @@ func (cmp Compare) MultiPolygonerEqual(geo1, geo2 geom.MultiPolygoner) bool { } // CollectionerEqual will check if the two collections are equal based on length -// then if each geometry inside is equal. Therefor order matters. +// then if each geometry inside is equal. Therefore order matters. func (cmp Compare) CollectionerEqual(col1, col2 geom.Collectioner) bool { if colNil, col2Nil := col1 == NilCollection, col2 == NilCollection; colNil || col2Nil { return colNil && col2Nil @@ -328,3 +328,9 @@ func (cmp Compare) GeometryEqual(g1, g2 geom.Geometry) bool { } return false } + +// Empty functions +func (Compare) IsEmptyPoint(pt [2]float64) bool { return IsEmptyPoint(pt) } +func (Compare) IsEmptyPoints(pts [][2]float64) bool { return IsEmptyPoints(pts) } +func (Compare) IsEmptyLines(lns [][][2]float64) bool { return IsEmptyLines(lns) } +func (Compare) IsEmptyGeo(geo geom.Geometry) bool { return IsEmptyGeo(geo) } diff --git a/cmp/empty.go b/cmp/empty.go index b188b258..eea83783 100644 --- a/cmp/empty.go +++ b/cmp/empty.go @@ -1,9 +1,6 @@ package cmp import ( - "fmt" - "reflect" - "github.com/go-spatial/geom" ) @@ -31,124 +28,114 @@ func IsEmptyLines(lns [][][2]float64) bool { return true } -func IsNil(a interface{}) bool { - defer func() { recover() }() - return a == nil || reflect.ValueOf(a).IsNil() -} - -func IsEmptyGeo(geo geom.Geometry) (isEmpty bool, err error) { - if IsNil(geo) { - return true, nil +func IsEmptyGeo(geo geom.Geometry) (isEmpty bool) { + if geom.IsNil(geo) { + return true } switch g := geo.(type) { case [2]float64: - return IsEmptyPoint(g), nil + return IsEmptyPoint(g) case geom.Point: - return IsEmptyPoint(g.XY()), nil + return IsEmptyPoint(g.XY()) case *geom.Point: if g == nil { - return true, nil + return true } - return IsEmptyPoint(g.XY()), nil + return IsEmptyPoint(g.XY()) case [][2]float64: - return IsEmptyPoints(g), nil + return IsEmptyPoints(g) case geom.MultiPoint: - return IsEmptyPoints(g.Points()), nil + return IsEmptyPoints(g.Points()) case *geom.MultiPoint: if g == nil { - return true, nil + return true } - return IsEmptyPoints(g.Points()), nil + return IsEmptyPoints(g.Points()) case geom.LineString: - return IsEmptyPoints(g.Vertices()), nil + return IsEmptyPoints(g.Vertices()) case *geom.LineString: if g == nil { - return true, nil + return true } - return IsEmptyPoints(g.Vertices()), nil + return IsEmptyPoints(g.Vertices()) case geom.MultiLineString: - return IsEmptyLines(g.LineStrings()), nil + return IsEmptyLines(g.LineStrings()) case *geom.MultiLineString: if g == nil { - return true, nil + return true } - return IsEmptyLines(g.LineStrings()), nil - + return IsEmptyLines(g.LineStrings()) case geom.Polygon: - return IsEmptyLines(g.LinearRings()), nil + return IsEmptyLines(g.LinearRings()) case *geom.Polygon: if g == nil { - return true, nil + return true } - return IsEmptyLines(g.LinearRings()), nil + return IsEmptyLines(g.LinearRings()) case geom.MultiPolygon: for _, v := range g.Polygons() { if !IsEmptyLines(v) { - return false, nil + return false } } - return true, nil + return true case *geom.MultiPolygon: if g == nil { - return true, nil + return true } for _, v := range g.Polygons() { if !IsEmptyLines(v) { - return false, nil + return false } } - return true, nil + return true case geom.Collection: + // if one item in the geometries list is not empty + // then the whole list is not empty for _, v := range g.Geometries() { - isEmpty, err := IsEmptyGeo(v) - if err != nil { - return false, err - } - if !isEmpty { - return false, nil + if !IsEmptyGeo(v) { + return false } } - return true, nil + return true case *geom.Collection: if g == nil { - return true, nil + return true } for _, v := range g.Geometries() { - isEmpty, err := IsEmptyGeo(v) - if err != nil { - return false, err - } - if !isEmpty { - return false, nil + // if one item in the geometries list is not empty + // then the whole list is not empty + if !IsEmptyGeo(v) { + return false } } - return true, nil + return true default: - return false, fmt.Errorf("unknown geometry %T", geo) + return false } } diff --git a/cmp/empty_test.go b/cmp/empty_test.go index de7d0828..64a322b0 100644 --- a/cmp/empty_test.go +++ b/cmp/empty_test.go @@ -11,22 +11,11 @@ func TestIsEmptyGeo(t *testing.T) { type tcase struct { geo geom.Geometry isEmpty bool - err string } fn := func(tc tcase) func(t *testing.T) { return func(t *testing.T) { - isEmpty, err := IsEmptyGeo(tc.geo) - if err != nil { - if err.Error() != tc.err { - t.Errorf("expected error %v, got %v", tc.err, err) - } - return - } else if tc.err != "" { - t.Errorf("expected error %v, got nil", tc.err) - return - } - + isEmpty := IsEmptyGeo(tc.geo) if isEmpty != tc.isEmpty { t.Errorf("expected isEmpty %v, got %v", tc.isEmpty, isEmpty) } @@ -43,23 +32,23 @@ func TestIsEmptyGeo(t *testing.T) { isEmpty: true, }, "nil point": { - geo: (*geom.Point)(nil), + geo: (*geom.Point)(nil), isEmpty: true, }, "non-nil point": { - geo: &geom.Point{}, + geo: &geom.Point{}, isEmpty: false, }, "multipoint": { - geo: geom.MultiPoint{geom.Point{}}, + geo: geom.MultiPoint{geom.Point{}}, isEmpty: false, }, "empty multipoint": { - geo: geom.MultiPoint{}, + geo: geom.MultiPoint{}, isEmpty: true, }, "empty multipoint 1": { - geo: geom.MultiPoint{geom.Point{math.NaN(), math.NaN()}}, + geo: geom.MultiPoint{geom.Point{math.NaN(), math.NaN()}}, isEmpty: true, }, "non empty multipoint": { @@ -70,19 +59,19 @@ func TestIsEmptyGeo(t *testing.T) { isEmpty: false, }, "nil multipoint": { - geo: (*geom.MultiPoint)(nil), + geo: (*geom.MultiPoint)(nil), isEmpty: true, }, "linestring": { - geo: geom.LineString{geom.Point{}}, + geo: geom.LineString{geom.Point{}}, isEmpty: false, }, "empty linestring": { - geo: geom.LineString{}, + geo: geom.LineString{}, isEmpty: true, }, "empty linestring 1": { - geo: geom.LineString{geom.Point{math.NaN(), math.NaN()}}, + geo: geom.LineString{geom.Point{math.NaN(), math.NaN()}}, isEmpty: true, }, "non empty linestring": { @@ -93,7 +82,7 @@ func TestIsEmptyGeo(t *testing.T) { isEmpty: false, }, "nil linestring": { - geo: (*geom.LineString)(nil), + geo: (*geom.LineString)(nil), isEmpty: true, }, "multilinestring": { @@ -105,7 +94,7 @@ func TestIsEmptyGeo(t *testing.T) { isEmpty: false, }, "empty multilinestring": { - geo: geom.MultiLineString{}, + geo: geom.MultiLineString{}, isEmpty: true, }, "empty multilinestring 1": { @@ -155,7 +144,7 @@ func TestIsEmptyGeo(t *testing.T) { isEmpty: false, }, "nil multilinestring": { - geo: (*geom.MultiLineString)(nil), + geo: (*geom.MultiLineString)(nil), isEmpty: true, }, "polygon": { @@ -167,7 +156,7 @@ func TestIsEmptyGeo(t *testing.T) { isEmpty: false, }, "empty polygon": { - geo: geom.Polygon{}, + geo: geom.Polygon{}, isEmpty: true, }, "empty polygon 1": { @@ -217,7 +206,7 @@ func TestIsEmptyGeo(t *testing.T) { isEmpty: false, }, "nil polygon": { - geo: (*geom.Polygon)(nil), + geo: (*geom.Polygon)(nil), isEmpty: true, }, "multipolygon": { @@ -231,7 +220,7 @@ func TestIsEmptyGeo(t *testing.T) { isEmpty: false, }, "empty multipolygon": { - geo: geom.MultiPolygon{}, + geo: geom.MultiPolygon{}, isEmpty: true, }, "empty multipolygon 1": { @@ -338,7 +327,7 @@ func TestIsEmptyGeo(t *testing.T) { isEmpty: false, }, "nil multipolygon": { - geo: (*geom.Polygon)(nil), + geo: (*geom.Polygon)(nil), isEmpty: true, }, "collection": { @@ -348,7 +337,7 @@ func TestIsEmptyGeo(t *testing.T) { isEmpty: false, }, "empty collection": { - geo: geom.Collection{}, + geo: geom.Collection{}, isEmpty: true, }, "empty collection 1": { @@ -390,20 +379,21 @@ func TestIsEmptyGeo(t *testing.T) { isEmpty: false, }, "nil collection": { - geo: (*geom.Collection)(nil), + geo: (*geom.Collection)(nil), isEmpty: true, }, "type check": { - geo: int(0), - err: "unknown geometry int", + // unknown geometry is always return that false. + geo: 0, + isEmpty: false, }, // non-nil pointers "*point": { - geo: &geom.Point{}, + geo: &geom.Point{}, isEmpty: false, }, "empty *point": { - geo: &geom.Point{math.NaN(), math.NaN()}, + geo: &geom.Point{math.NaN(), math.NaN()}, isEmpty: true, }, "*multipoint": { diff --git a/encoding/gpkg/binary_header.go b/encoding/gpkg/binary_header.go index 7cf0591a..e220213e 100644 --- a/encoding/gpkg/binary_header.go +++ b/encoding/gpkg/binary_header.go @@ -9,6 +9,8 @@ import ( "fmt" "math" + "github.com/go-spatial/geom/cmp" + "github.com/gdey/errors" "github.com/go-spatial/geom" @@ -328,7 +330,7 @@ func (sb StandardBinary) Encode() ([]byte, error) { func NewBinary(srs int32, geo geom.Geometry) (*StandardBinary, error) { var ( - emptyGeo = geom.IsEmpty(geo) + emptyGeo = cmp.IsEmptyGeo(geo) err error extent = []float64{nan, nan, nan, nan} h *BinaryHeader @@ -358,7 +360,7 @@ func (sb *StandardBinary) Extent() *geom.Extent { if sb == nil { return nil } - if geom.IsEmpty(sb.Geometry) { + if cmp.IsEmptyGeo(sb.Geometry) { return nil } extent, err := geom.NewExtentFromGeometry(sb.Geometry) diff --git a/encoding/gpkg/gpkg.go b/encoding/gpkg/gpkg.go index 890ae523..522e2db0 100644 --- a/encoding/gpkg/gpkg.go +++ b/encoding/gpkg/gpkg.go @@ -9,6 +9,7 @@ import ( "strings" "github.com/go-spatial/geom" + "github.com/go-spatial/geom/cmp" _ "github.com/mattn/go-sqlite3" ) @@ -448,7 +449,7 @@ func (h *Handle) CalculateGeometryExtent(tablename string) (*geom.Extent, error) for rows.Next() { rows.Scan(&sb) - if geom.IsEmpty(sb.Geometry) { + if cmp.IsEmptyGeo(sb.Geometry) { continue } if ext == nil { diff --git a/geom.go b/geom.go index be8ef1b3..53bb1eba 100644 --- a/geom.go +++ b/geom.go @@ -537,53 +537,13 @@ func ExtractLines(g Geometry) (lines []Line, err error) { return lines, err } -// helper function to check it the given interface is nil, or the +// IsNil is a helper function to check it the given interface is nil, or the // value store in it is nil -func isNil(a interface{}) bool { +func IsNil(a interface{}) bool { defer func() { recover() }() return a == nil || reflect.ValueOf(a).IsNil() } -// IsEmpty returns if the geometry represents an empty geometry -func IsEmpty(geo Geometry) bool { - if isNil(geo) { - return true - } - switch g := geo.(type) { - case Point: - return g[0] == nan && g[1] == nan - case Pointer: - xy := g.XY() - return xy[0] == nan && xy[1] == nan - case LineString: - return len(g) == 0 - case LineStringer: - return len(g.Vertices()) == 0 - case Polygon: - return len(g) == 0 - case Polygoner: - return len(g.LinearRings()) == 0 - case MultiPoint: - return len(g) == 0 - case MultiPointer: - return len(g.Points()) == 0 - case MultiLineString: - return len(g) == 0 - case MultiLineStringer: - return len(g.LineStrings()) == 0 - case MultiPolygon: - return len(g) == 0 - case MultiPolygoner: - return len(g.Polygons()) == 0 - case Collection: - return len(g) == 0 - case Collectioner: - return len(g.Geometries()) == 0 - default: - return true - } -} - // RoundToPrec will round the given value to the precision value. // The precision value should be a power of 10. func RoundToPrec(v float64, prec int) float64 { diff --git a/planar/makevalid/makevalid_test.go b/planar/makevalid/makevalid_test.go index e6c4f505..f3791bc4 100644 --- a/planar/makevalid/makevalid_test.go +++ b/planar/makevalid/makevalid_test.go @@ -88,14 +88,14 @@ func checkMakeValid(tb testing.TB) { if !cmp.MultiPolygonerEqual(tc.ExpectedMultiPolygon, mp) { //t.Logf("input: \n%v", wkt.MustEncode(tc.MultiPolygon)) t.Errorf("multipolygon, expected \n%v\n got \n%v", wkt.MustEncode(tc.ExpectedMultiPolygon), wkt.MustEncode(mp)) - if !geom.IsEmpty(tc.ExpectedMultiPolygon) { + if !cmp.IsEmptyGeo(tc.ExpectedMultiPolygon) { for p, ply := range tc.ExpectedMultiPolygon.Polygons() { for l, ln := range ply { t.Logf("expected windorder %v:%v: %v", p, l, order.OfPoints(ln...)) } } } - if !geom.IsEmpty(mp) { + if !cmp.IsEmptyGeo(mp) { for p, ply := range mp.Polygons() { for l, ln := range ply { t.Logf("got windorder %v:%v: %v", p, l, order.OfPoints(ln...)) @@ -117,7 +117,7 @@ func checkMakeValid(tb testing.TB) { didClip: true, }, "issue#70_full": { - ClipBox: webMercatorTileExtent(13, 8054, 2677).ExpandBy(64.0), + ClipBox: webMercatorTileExtent(13, 8054, 2677).ExpandBy(64.0), MultiPolygon: must.MPPointer(must.ReadMultiPolygon("testdata/issue/70/multipolygon_full_input.wkt")), ExpectedMultiPolygon: must.MPPointer(must.ReadMultiPolygon("testdata/issue/70/multipolygon_full_expected.wkt")), didClip: true, diff --git a/planar/triangulate/delaunay/subdivision/geom.go b/planar/triangulate/delaunay/subdivision/geom.go index 8a0d37cf..2a331189 100644 --- a/planar/triangulate/delaunay/subdivision/geom.go +++ b/planar/triangulate/delaunay/subdivision/geom.go @@ -37,7 +37,7 @@ func NewSubdivisionFromGeomLines(lines []geom.Line, order winding.Order) *Subdiv for i := range lines { orig, dest := geom.Point(lines[i][0]), geom.Point(lines[i][1]) - if geom.IsEmpty(orig) || geom.IsEmpty(dest) { + if cmp.IsEmptyGeo(orig) || cmp.IsEmptyGeo(dest) { log.Printf("orig %v or dest %v is empty", orig, dest) } if ext == nil {