Skip to content

Commit

Permalink
Merge 65c2837 into cbd8b13
Browse files Browse the repository at this point in the history
  • Loading branch information
peterstace committed Nov 21, 2021
2 parents cbd8b13 + 65c2837 commit 85b1c07
Show file tree
Hide file tree
Showing 5 changed files with 85 additions and 3 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ __Special thanks to Albert Teoh and Sameera Perera for contributing to this rele
- Fixes a bug in Simplify where invalid interior rings would be omitted rather
than producing an error.

- Adds a wrapper in the `geos` package for the `GEOSMakeValid_r` function
(exposed as `MakeValid`).

## v0.34.0

2021-11-02
Expand Down
27 changes: 27 additions & 0 deletions geos/entrypoints.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,17 @@ package geos
#cgo LDFLAGS: -lgeos_c
#cgo CFLAGS: -Wall
#include "geos_c.h"
#define MAKE_VALID_MIN_VERSION "3.8.0"
#define MAKE_VALID_MISSING ( \
GEOS_VERSION_MAJOR < 3 || \
(GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR < 8) \
)
#if MAKE_VALID_MISSING
// This stub implementation always fails:
GEOSGeometry *GEOSMakeValid_r(GEOSContextHandle_t handle, const GEOSGeometry* g) { return NULL; }
#endif
*/
import "C"

Expand Down Expand Up @@ -309,3 +320,19 @@ func SymmetricDifference(a, b geom.Geometry, opts ...geom.ConstructorOption) (ge
})
return result, wrap(err, "executing GEOSSymDifference_r")
}

// MakeValid can be used to convert an invalid geometry into a valid geometry.
// It does this by keeping the original control points and constructing a new
// geometry that is valid and similar (but not the same as) the original
// invalid geometry. If the input geometry is valid, then it is returned
// unaltered.
func MakeValid(g geom.Geometry, opts ...geom.ConstructorOption) (geom.Geometry, error) {
if C.MAKE_VALID_MISSING != 0 {
return geom.Geometry{}, unsupportedGEOSVersionError{
C.MAKE_VALID_MIN_VERSION, "MakeValid"}
}
result, err := unaryOpG(g, opts, func(ctx C.GEOSContextHandle_t, g *C.GEOSGeometry) *C.GEOSGeometry {
return C.GEOSMakeValid_r(ctx, g)
})
return result, wrap(err, "executing GEOSMakeValid_r")
}
36 changes: 34 additions & 2 deletions geos/entrypoints_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import (
"github.com/peterstace/simplefeatures/geom"
)

func geomFromWKT(t *testing.T, wkt string) geom.Geometry {
func geomFromWKT(t *testing.T, wkt string, opts ...geom.ConstructorOption) geom.Geometry {
t.Helper()
geom, err := geom.UnmarshalWKT(wkt)
geom, err := geom.UnmarshalWKT(wkt, opts...)
if err != nil {
t.Fatalf("could not unmarshal WKT:\n wkt: %s\n err: %v", wkt, err)
}
Expand All @@ -23,6 +23,13 @@ func expectNoErr(t *testing.T, err error) {
}
}

func expectErr(t *testing.T, err error) {
t.Helper()
if err == nil {
t.Fatal("unexpected error but got nil")
}
}

func expectGeomEq(t *testing.T, got, want geom.Geometry, opts ...geom.ExactEqualsOption) {
t.Helper()
if !geom.ExactEquals(got, want, opts...) {
Expand Down Expand Up @@ -767,3 +774,28 @@ func TestSymmetricDifference(t *testing.T) {

expectGeomEq(t, got, want, geom.IgnoreOrder)
}

func TestMakeValid(t *testing.T) {
for i, tt := range []struct {
input string
wantOutput string
}{
{
"POLYGON((0 0,2 2,2 0,0 2,0 0))",
"MULTIPOLYGON(((0 0,0 2,1 1,0 0)),((2 0,1 1,2 2,2 0)))",
},
} {
t.Run(strconv.Itoa(i), func(t *testing.T) {
_, err := geom.UnmarshalWKT(tt.input)
expectErr(t, err)
in := geomFromWKT(t, tt.input, geom.DisableAllValidations)
gotGeom, err := MakeValid(in)
if _, ok := err.(unsupportedGEOSVersionError); ok {
t.Skip(err)
}
expectNoErr(t, err)
wantGeom := geomFromWKT(t, tt.wantOutput)
expectGeomEq(t, gotGeom, wantGeom)
})
}
}
20 changes: 20 additions & 0 deletions geos/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,29 @@ package geos

import "fmt"

// #include "geos_c.h"
import "C"

func wrap(err error, format string, args ...interface{}) error {
if err == nil {
return nil
}
return fmt.Errorf(format+": %w", append(args, err)...)
}

var currentGEOSVersion = fmt.Sprintf(
"%d.%d.%d",
C.GEOS_VERSION_MAJOR,
C.GEOS_VERSION_MINOR,
C.GEOS_VERSION_PATCH,
)

type unsupportedGEOSVersionError struct {
requiredGEOSVersion string
operation string
}

func (e unsupportedGEOSVersionError) Error() string {
return fmt.Sprintf("%s is unsupported in GEOS %s, requires at least GEOS %s",
e.operation, currentGEOSVersion, e.requiredGEOSVersion)
}
2 changes: 1 addition & 1 deletion geos/handle.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ char *marshal(GEOSContextHandle_t handle, const GEOSGeometry *g, size_t *size, c
GEOSGeometry const *noop(GEOSContextHandle_t handle, const GEOSGeometry *g) {
return g;
}
*/
import "C"

Expand Down Expand Up @@ -62,6 +61,7 @@ func newHandle() (*handle, error) {
h.release()
return nil, errors.New("malloc failed")
}
C.memset((unsafe.Pointer)(h.errBuf), 0, 1024)

h.context = C.sf_init(unsafe.Pointer(h.errBuf))
if h.context == nil {
Expand Down

0 comments on commit 85b1c07

Please sign in to comment.