Skip to content

Commit

Permalink
Merge pull request #526 from peterstace/no_validation_in_geos_wrapper
Browse files Browse the repository at this point in the history
Don't validate results from GEOS
  • Loading branch information
peterstace committed Sep 26, 2023
2 parents 211b84c + a962780 commit 67ef7a0
Show file tree
Hide file tree
Showing 7 changed files with 49 additions and 55 deletions.
19 changes: 1 addition & 18 deletions geos/benchmark_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,31 +33,14 @@ func regularPolygon(center geom.XY, radius float64, sides int) geom.Polygon {
return poly
}

func BenchmarkIntersectionWithoutValidation(b *testing.B) {
for _, sz := range []int{10, 100, 1000, 10000} {
b.Run(fmt.Sprintf("n=%d", sz), func(b *testing.B) {
inputA := regularPolygon(geom.XY{X: 0, Y: 0}, 1.0, sz).AsGeometry()
inputB := regularPolygon(geom.XY{X: 1, Y: 0}, 1.0, sz).AsGeometry()
b.ResetTimer()

for i := 0; i < b.N; i++ {
_, err := Intersection(inputA, inputB, geom.DisableAllValidations)
if err != nil {
b.Fatal(err)
}
}
})
}
}

func BenchmarkNoOp(b *testing.B) {
for _, sz := range []int{10, 100, 1000, 10000} {
b.Run(fmt.Sprintf("n=%d", sz), func(b *testing.B) {
input := regularPolygon(geom.XY{X: 0, Y: 0}, 1.0, sz).AsGeometry()
b.ResetTimer()

for i := 0; i < b.N; i++ {
_, err := noop(input, geom.DisableAllValidations)
_, err := noop(input)
if err != nil {
b.Fatal(err)
}
Expand Down
4 changes: 4 additions & 0 deletions geos/doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
// Its purpose is to provide functionality that has been implemented in GEOS,
// but is not yet available natively in the simplefeatures library.
//
// Results from functions in this package are returned from GEOS unvalidated
// and as-is. Users may call the Validate method on results if they wish to
// check result validity.
//
// The operations in this package ignore Z and M values if they are present.
//
// To use this package, you will need to install the GEOS library.
Expand Down
61 changes: 35 additions & 26 deletions geos/entrypoints.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,8 +179,10 @@ func Overlaps(a, b geom.Geometry) (bool, error) {
// Union returns a geometry that is the union of the input geometries.
// Formally, the returned geometry will contain a particular point X if and
// only if X is present in either geometry (or both).
func Union(a, b geom.Geometry, opts ...geom.ConstructorOption) (geom.Geometry, error) {
g, err := binaryOpG(a, b, opts, func(ctx C.GEOSContextHandle_t, a, b *C.GEOSGeometry) *C.GEOSGeometry {
//
// The validity of the result is not checked.
func Union(a, b geom.Geometry) (geom.Geometry, error) {
g, err := binaryOpG(a, b, func(ctx C.GEOSContextHandle_t, a, b *C.GEOSGeometry) *C.GEOSGeometry {
return C.GEOSUnion_r(ctx, a, b)
})
return g, wrap(err, "executing GEOSUnion_r")
Expand All @@ -189,8 +191,10 @@ func Union(a, b geom.Geometry, opts ...geom.ConstructorOption) (geom.Geometry, e
// Intersection returns a geometry that is the intersection of the input
// geometries. Formally, the returned geometry will contain a particular point
// X if and only if X is present in both geometries.
func Intersection(a, b geom.Geometry, opts ...geom.ConstructorOption) (geom.Geometry, error) {
g, err := binaryOpG(a, b, opts, func(ctx C.GEOSContextHandle_t, a, b *C.GEOSGeometry) *C.GEOSGeometry {
//
// The validity of the result is not checked.
func Intersection(a, b geom.Geometry) (geom.Geometry, error) {
g, err := binaryOpG(a, b, func(ctx C.GEOSContextHandle_t, a, b *C.GEOSGeometry) *C.GEOSGeometry {
return C.GEOSIntersection_r(ctx, a, b)
})
return g, wrap(err, "executing GEOSIntersection_r")
Expand All @@ -204,7 +208,6 @@ type bufferOptionSet struct {
endCapStyle int
joinStyle int
mitreLimit float64
ctorOpts []geom.ConstructorOption
}

func newBufferOptionSet(opts []BufferOption) bufferOptionSet {
Expand Down Expand Up @@ -278,19 +281,13 @@ func BufferJoinStyleBevel() BufferOption {
}
}

// BufferConstructorOption sets constructor option that are used when
// reconstructing the buffered geometry that is returned from the GEOS lib.
func BufferConstructorOption(opts ...geom.ConstructorOption) BufferOption {
return func(bos *bufferOptionSet) {
bos.ctorOpts = append(bos.ctorOpts, opts...)
}
}

// Buffer returns a geometry that contains all points within the given radius
// of the input geometry.
//
// The validity of the result is not checked.
func Buffer(g geom.Geometry, radius float64, opts ...BufferOption) (geom.Geometry, error) {
optSet := newBufferOptionSet(opts)
result, err := unaryOpG(g, optSet.ctorOpts, func(ctx C.GEOSContextHandle_t, gh *C.GEOSGeometry) *C.GEOSGeometry {
result, err := unaryOpG(g, func(ctx C.GEOSContextHandle_t, gh *C.GEOSGeometry) *C.GEOSGeometry {
return C.GEOSBufferWithStyle_r(
ctx, gh, C.double(radius),
C.int(optSet.quadSegments),
Expand All @@ -306,26 +303,32 @@ func Buffer(g geom.Geometry, radius float64, opts ...BufferOption) (geom.Geometr
// Douglas-Peucker algorithm. Topological invariants may not be maintained,
// e.g. polygons can collapse into linestrings, and holes in polygons may be
// lost.
func Simplify(g geom.Geometry, tolerance float64, opts ...geom.ConstructorOption) (geom.Geometry, error) {
result, err := unaryOpG(g, opts, func(ctx C.GEOSContextHandle_t, gh *C.GEOSGeometry) *C.GEOSGeometry {
//
// The validity of the result is not checked.
func Simplify(g geom.Geometry, tolerance float64) (geom.Geometry, error) {
result, err := unaryOpG(g, func(ctx C.GEOSContextHandle_t, gh *C.GEOSGeometry) *C.GEOSGeometry {
return C.GEOSSimplify_r(ctx, gh, C.double(tolerance))
})
return result, wrap(err, "executing GEOSSimplify_r")
}

// Difference returns the geometry that represents the parts of input geometry
// A that are not part of input geometry B.
func Difference(a, b geom.Geometry, opts ...geom.ConstructorOption) (geom.Geometry, error) {
result, err := binaryOpG(a, b, opts, func(ctx C.GEOSContextHandle_t, a, b *C.GEOSGeometry) *C.GEOSGeometry {
//
// The validity of the result is not checked.
func Difference(a, b geom.Geometry) (geom.Geometry, error) {
result, err := binaryOpG(a, b, func(ctx C.GEOSContextHandle_t, a, b *C.GEOSGeometry) *C.GEOSGeometry {
return C.GEOSDifference_r(ctx, a, b)
})
return result, wrap(err, "executing GEOSDifference_r")
}

// SymmetricDifference returns the geometry that represents the parts of the
// input geometries that are not part of the other input geometry.
func SymmetricDifference(a, b geom.Geometry, opts ...geom.ConstructorOption) (geom.Geometry, error) {
result, err := binaryOpG(a, b, opts, func(ctx C.GEOSContextHandle_t, a, b *C.GEOSGeometry) *C.GEOSGeometry {
//
// The validity of the result is not checked.
func SymmetricDifference(a, b geom.Geometry) (geom.Geometry, error) {
result, err := binaryOpG(a, b, func(ctx C.GEOSContextHandle_t, a, b *C.GEOSGeometry) *C.GEOSGeometry {
return C.GEOSSymDifference_r(ctx, a, b)
})
return result, wrap(err, "executing GEOSSymDifference_r")
Expand All @@ -336,13 +339,15 @@ func SymmetricDifference(a, b geom.Geometry, opts ...geom.ConstructorOption) (ge
// 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) {
//
// The validity of the result is not checked.
func MakeValid(g geom.Geometry) (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 {
result, err := unaryOpG(g, func(ctx C.GEOSContextHandle_t, g *C.GEOSGeometry) *C.GEOSGeometry {
return C.GEOSMakeValid_r(ctx, g)
})
return result, wrap(err, "executing GEOSMakeValid_r")
Expand All @@ -362,13 +367,15 @@ func MakeValid(g geom.Geometry, opts ...geom.ConstructorOption) (geom.Geometry,
// and return an error, but this is not guaranteed, and an invalid result may
// be returned without an error. It is the responsibility of the caller to
// ensure that the constraints are met before using this function.
func CoverageUnion(g geom.Geometry, opts ...geom.ConstructorOption) (geom.Geometry, error) {
//
// The validity of the result is not checked.
func CoverageUnion(g geom.Geometry) (geom.Geometry, error) {
if C.COVERAGE_UNION_MISSING != 0 {
return geom.Geometry{}, unsupportedGEOSVersionError{
C.COVERAGE_UNION_MIN_VERSION, "CoverageUnion",
}
}
result, err := unaryOpG(g, opts, func(ctx C.GEOSContextHandle_t, g *C.GEOSGeometry) *C.GEOSGeometry {
result, err := unaryOpG(g, func(ctx C.GEOSContextHandle_t, g *C.GEOSGeometry) *C.GEOSGeometry {
return C.GEOSCoverageUnion_r(ctx, g)
})
return result, wrap(err, "executing GEOSCoverageUnion_r")
Expand All @@ -377,8 +384,10 @@ func CoverageUnion(g geom.Geometry, opts ...geom.ConstructorOption) (geom.Geomet
// UnaryUnion is a single argument version of Union. It is most useful when
// supplied with a GeometryCollection, resulting in the union of the
// GeometryCollection's child geometries.
func UnaryUnion(g geom.Geometry, opts ...geom.ConstructorOption) (geom.Geometry, error) {
result, err := unaryOpG(g, opts, func(ctx C.GEOSContextHandle_t, g *C.GEOSGeometry) *C.GEOSGeometry {
//
// The validity of the result is not checked.
func UnaryUnion(g geom.Geometry) (geom.Geometry, error) {
result, err := unaryOpG(g, func(ctx C.GEOSContextHandle_t, g *C.GEOSGeometry) *C.GEOSGeometry {
return C.GEOSUnaryUnion_r(ctx, g)
})
return result, wrap(err, "executing GEOSUnaryUnion_r")
Expand Down
2 changes: 1 addition & 1 deletion geos/entrypoints_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -579,7 +579,7 @@ type BinaryOperationTestCase struct {
Out string
}

func RunBinaryOperationTest(t *testing.T, fn func(a, b geom.Geometry, opts ...geom.ConstructorOption) (geom.Geometry, error), cases []BinaryOperationTestCase) {
func RunBinaryOperationTest(t *testing.T, fn func(a, b geom.Geometry) (geom.Geometry, error), cases []BinaryOperationTestCase) {
for i, c := range cases {
t.Run(strconv.Itoa(i), func(t *testing.T) {
run := func(rev bool) func(t *testing.T) {
Expand Down
6 changes: 2 additions & 4 deletions geos/generic_operations.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ func binaryOpE(

func binaryOpG(
g1, g2 geom.Geometry,
opts []geom.ConstructorOption,
op func(C.GEOSContextHandle_t, *C.GEOSGeometry, *C.GEOSGeometry) *C.GEOSGeometry,
) (geom.Geometry, error) {
var result geom.Geometry
Expand All @@ -54,7 +53,7 @@ func binaryOpG(
}
defer C.GEOSGeom_destroy(resultGH)
var err error
result, err = h.decode(resultGH, opts)
result, err = h.decode(resultGH)
return wrap(err, "decoding result")
})
return result, err
Expand Down Expand Up @@ -95,7 +94,6 @@ func unaryOpE(g geom.Geometry, op func(*handle, *C.GEOSGeometry) error) error {

func unaryOpG(
g geom.Geometry,
opts []geom.ConstructorOption,
op func(C.GEOSContextHandle_t, *C.GEOSGeometry) *C.GEOSGeometry,
) (geom.Geometry, error) {
var result geom.Geometry
Expand All @@ -111,7 +109,7 @@ func unaryOpG(
defer C.GEOSGeom_destroy(resultGH)
}
var err error
result, err = h.decode(resultGH, opts)
result, err = h.decode(resultGH)
return wrap(err, "decoding result")
})
return result, err
Expand Down
10 changes: 5 additions & 5 deletions geos/handle.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ import (
// noop returns the geometry unaltered, via conversion to and from GEOS. This
// function is only for benchmarking purposes, hence it is not exported or used
// outside of benchmark tests.
func noop(g geom.Geometry, opts ...geom.ConstructorOption) (geom.Geometry, error) {
result, err := unaryOpG(g, opts, func(ctx C.GEOSContextHandle_t, g *C.GEOSGeometry) *C.GEOSGeometry {
func noop(g geom.Geometry) (geom.Geometry, error) {
result, err := unaryOpG(g, func(ctx C.GEOSContextHandle_t, g *C.GEOSGeometry) *C.GEOSGeometry {
return C.noop(ctx, g)
})
return result, wrap(err, "executing noop")
Expand Down Expand Up @@ -158,7 +158,7 @@ func (h *handle) boolErr(c C.char) (bool, error) {
}
}

func (h *handle) decode(gh *C.GEOSGeometry, opts []geom.ConstructorOption) (geom.Geometry, error) {
func (h *handle) decode(gh *C.GEOSGeometry) (geom.Geometry, error) {
var (
isWKT C.char
size C.size_t
Expand All @@ -171,10 +171,10 @@ func (h *handle) decode(gh *C.GEOSGeometry, opts []geom.ConstructorOption) (geom

if isWKT != 0 {
wkt := C.GoStringN(serialised, C.int(size))
g, err := geom.UnmarshalWKT(wkt, opts...)
g, err := geom.UnmarshalWKT(wkt, geom.DisableAllValidations)
return g, wrap(err, "failed to unmarshal GEOS WKT result")
}
wkb := C.GoBytes(unsafe.Pointer(serialised), C.int(size))
g, err := geom.UnmarshalWKB(wkb, opts...)
g, err := geom.UnmarshalWKB(wkb, geom.DisableAllValidations)
return g, wrap(err, "failed to unmarshal GEOS WKB result")
}
2 changes: 1 addition & 1 deletion internal/perf/set_op_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ func BenchmarkSetOperation(b *testing.B) {
}

func adaptGEOSSetOp(
setOp func(_, _ geom.Geometry, _ ...geom.ConstructorOption) (geom.Geometry, error),
setOp func(_, _ geom.Geometry) (geom.Geometry, error),
) func(_, _ geom.Geometry) (geom.Geometry, error) {
return func(g1, g2 geom.Geometry) (geom.Geometry, error) {
return setOp(g1, g2)
Expand Down

0 comments on commit 67ef7a0

Please sign in to comment.