Repository: GEOSwift/GEOSwift (swift)

Issue:
Support for subtracting geometries from others?
There are usually 4 ways of combining different polygons, which have different names depending on who you ask. Here are some examples:

![image](https://user-images.githubusercontent.com/1865635/145707654-4c1f9b40-4f65-4574-8f04-2cc2e26b6f34.png)

![image](https://user-images.githubusercontent.com/1865635/145707660-c13cdb24-9c3c-4741-a41f-242ef58c772c.png)

![image](https://user-images.githubusercontent.com/1865635/145707673-82348ab0-70f5-4306-a2f4-f3cfb3b71eb1.png)

In GEOSwift, we have **Union**, **Intersection** and **Difference**. But as far as I know, we don't have **Subtract**.

Is there a particular reason that Subtract is missing, or if not, can it be added?

Pull request description:
Fixes #230

Patch:
diff --git a/Sources/GEOSwift/GEOS/GeometryConvertible+GEOS.swift b/Sources/GEOSwift/GEOS/GeometryConvertible+GEOS.swift
index ef74b601..988f08e6 100644
--- a/Sources/GEOSwift/GEOS/GeometryConvertible+GEOS.swift
+++ b/Sources/GEOSwift/GEOS/GeometryConvertible+GEOS.swift
@@ -306,6 +306,16 @@ public extension GeometryConvertible {
         }
     }
 
+    func symmetricDifference(with geometry: GeometryConvertible) throws -> Geometry? {
+        do {
+            return try performBinaryTopologyOperation(GEOSSymDifference_r, geometry: geometry)
+        } catch GEOSwiftError.tooFewPoints {
+            return nil
+        } catch {
+            throw error
+        }
+    }
+
     func union(with geometry: GeometryConvertible) throws -> Geometry {
         try performBinaryTopologyOperation(GEOSUnion_r, geometry: geometry)
     }


Test patch:
diff --git a/Tests/GEOSwiftTests/GEOS/GeometryConvertible+GEOSTests.swift b/Tests/GEOSwiftTests/GEOS/GeometryConvertible+GEOSTests.swift
index 9a2dc15c..f76e5e1f 100644
--- a/Tests/GEOSwiftTests/GEOS/GeometryConvertible+GEOSTests.swift
+++ b/Tests/GEOSwiftTests/GEOS/GeometryConvertible+GEOSTests.swift
@@ -741,6 +741,38 @@ final class GeometryConvertible_GEOSTests: XCTestCase {
         }
     }
 
+    func testSymmetricDifferencePolygons() throws {
+        let poly = try! Polygon(exterior: Polygon.LinearRing(points: [
+            Point(x: 0.5, y: 0),
+            Point(x: 1.5, y: 0),
+            Point(x: 1.5, y: 1),
+            Point(x: 0.5, y: 1),
+            Point(x: 0.5, y: 0)]))
+        let expected = try! MultiPolygon(polygons: [
+            Polygon(exterior: Polygon.LinearRing(points: [
+                Point(x: 1, y: 0),
+                Point(x: 1.5, y: 0),
+                Point(x: 1.5, y: 1),
+                Point(x: 1, y: 1),
+                Point(x: 1, y: 0)])),
+            Polygon(exterior: Polygon.LinearRing(points: [
+                Point(x: 0, y: 0),
+                Point(x: 0.5, y: 0),
+                Point(x: 0.5, y: 1),
+                Point(x: 0, y: 1),
+                Point(x: 0, y: 0)]))])
+
+        let result = try XCTUnwrap(poly.symmetricDifference(with: unitPoly))
+
+        XCTAssertTrue(try result.isTopologicallyEquivalent(to: expected))
+    }
+
+    func testSymmetricDifferenceAllPairs() {
+        for (g1, g2) in geometryConvertibles.allPairs {
+            XCTAssertNoThrow(try g1.symmetricDifference(with: g2))
+        }
+    }
+
     func testUnionPointAndLine() {
         let point = Point(x: 2, y: 0)
         XCTAssertEqual(try? lineString1.union(with: point),