Repository: JuliaGeo/LibGEOS.jl
Pull request #136 (2022-09-14 01:26:25)

Update Constructors with new functionality and test memory safety
This finishes addressing issue #129 and issue #133. All constructors have been updated and tested. New functionality has been added for a few of the constructors. For example, MultiPolygons can now be constructed using lists of Polygons. 

The one thing I wanted someone else to take a look at is my use of cloneGeom when making Multi* objects from lists of other objects. For example, MultiPoint on lines 46-50. I know that when using createCollection, the ownership of the elements that make up the collection is transferred to the collection object. So the ownership of the point objects is transferred to the collection. However, the collection is local in scope, and is cloned when it is made into a MultiPoint object. Therefore, I think the points should retain ownership over themselves when we exit from the constructor. However, I wanted someone else to check my logic. If that isn't true, then I think we need to clone each of the elements that make up the collection rather than the collection itself. 

Also I may have gone overboard on testing since it is relatively simple code once the memory issue was figured out. Let me know if I should remove any.

Thanks!

skygering: > There might be some ideas around improving the performance of constructing the geometries by dispatching on the `geomTypeId`, but those can be explored as future enhancements without breaking the API and doesn't have to be part of this PR.

Are you thinking of making subtypes of GEOSGeom for each `geomTypeId`?
skygering: Realized that this also addresses issue #87 
yeesian: > > There might be some ideas around improving the performance of constructing the geometries by dispatching on the `geomTypeId`, but those can be explored as future enhancements without breaking the API and doesn't have to be part of this PR.
> 
> Are you thinking of making subtypes of GEOSGeom for each `geomTypeId`?

I think the idea will be along the lines of dispatching on ["geometrytraits"](https://juliageo.org/GeoInterface.jl/stable/guides/developer/) (like in https://github.com/yeesian/ArchGDAL.jl/pull/316).

Patch:
diff --git a/src/geos_types.jl b/src/geos_types.jl
index 32a0a79..6094ee5 100644
--- a/src/geos_types.jl
+++ b/src/geos_types.jl
@@ -2,55 +2,93 @@ abstract type AbstractGeometry end
 
 mutable struct Point <: AbstractGeometry
     ptr::GEOSGeom
-
+    # create a point from a pointer - only makese sense if it is a pointer to a point, otherwise error
     function Point(ptr::GEOSGeom)
-        point = new(ptr)
-        finalizer(destroyGeom, point)
-        point
+        id = LibGEOS.geomTypeId(ptr)
+        if id == GEOS_POINT
+            point = new(cloneGeom(ptr))
+            finalizer(destroyGeom, point)
+            point
+        else
+            error("LibGEOS: Can't convert a pointer to an element with a GeomType ID of $id to a point (yet). Please open an issue if you think this conversion makes sense.")
+        end
     end
+    # create a point from a vector of floats
     Point(coords::Vector{Float64}) = Point(createPoint(coords))
     Point(x::Real, y::Real) = Point(createPoint(x, y))
     Point(x::Real, y::Real, z::Real) = Point(createPoint(x, y, z))
-
 end
 
 mutable struct MultiPoint <: AbstractGeometry
     ptr::GEOSGeom
-
+    # create a multipoint from a pointer - only makes sense if it is a pointer to a multipoint or to a point, otherwise error
     function MultiPoint(ptr::GEOSGeom)
-        multipoint = new(ptr)
+        id = LibGEOS.geomTypeId(ptr)
+        if id == GEOS_MULTIPOINT
+            multipoint = new(cloneGeom(ptr))
+        elseif id == GEOS_POINT
+            multipoint = new(createCollection(GEOS_MULTIPOINT, 
+                                        GEOSGeom[cloneGeom(ptr)]))
+        else
+            error("LibGEOS: Can't convert a pointer to an element with a GeomType ID of $id to a multipoint (yet). Please open an issue if you think this conversion makes sense.")
+        end
         finalizer(destroyGeom, multipoint)
         multipoint
     end
+    # create a multipoint frome a vector of vector coordinates
     MultiPoint(multipoint::Vector{Vector{Float64}}) = MultiPoint(
         createCollection(
             GEOS_MULTIPOINT,
             GEOSGeom[createPoint(coords) for coords in multipoint],
         ),
     )
-
+    # create a multipoint from a list of points
+    MultiPoint(points::Vector{LibGEOS.Point}) = MultiPoint(
+        createCollection(
+            GEOS_MULTIPOINT,
+            GEOSGeom[point.ptr for point in points],
+        ),
+    )
 end
 
 mutable struct LineString <: AbstractGeometry
     ptr::GEOSGeom
-
+    # create a linestring from a linestring pointer, otherwise error
     function LineString(ptr::GEOSGeom)
-        line = new(ptr)
+        id = LibGEOS.geomTypeId(ptr)
+        if id == GEOS_LINESTRING
+            line = new(cloneGeom(ptr))
+            finalizer(destroyGeom, line)
+            line
+        else
+            error("LibGEOS: Can't convert a pointer to an element with a GeomType ID of $id to a linestring (yet). Please open an issue if you think this conversion makes sense.")
+        end
+    end
+    #create a linestring from a list of coordiantes
+    function LineString(coords::Vector{Vector{Float64}})
+        line = new(createLineString(coords))
         finalizer(destroyGeom, line)
         line
     end
-    LineString(line::Vector{Vector{Float64}}) = LineString(createLineString(line))
-
 end
 
 mutable struct MultiLineString <: AbstractGeometry
     ptr::GEOSGeom
-
+    # create a multiline string from a multilinestring or a linestring pointer, else error
     function MultiLineString(ptr::GEOSGeom)
-        multiline = new(ptr)
+        id = LibGEOS.geomTypeId(ptr)
+        if id == GEOS_MULTILINESTRING
+            multiline = new(cloneGeom(ptr))
+        elseif id == GEOS_LINESTRING
+            multiline = new(createCollection(GEOS_MULTILINESTRING, 
+                                        GEOSGeom[cloneGeom(ptr)]))
+        else
+            error("LibGEOS: Can't convert a pointer to an element with a GeomType ID of $id to a multi-linestring (yet). Please open an issue if you think this conversion makes sense.")
+        end
         finalizer(destroyGeom, multiline)
         multiline
     end
+    # create a multilinestring from a list of linestring coordiantes
     MultiLineString(multiline::Vector{Vector{Vector{Float64}}}) = MultiLineString(
         createCollection(
             GEOS_MULTILINESTRING,
@@ -62,13 +100,24 @@ end
 
 mutable struct LinearRing <: AbstractGeometry
     ptr::GEOSGeom
-
+    # create a linear ring from a linear ring pointer, otherwise error
     function LinearRing(ptr::GEOSGeom)
-        ring = new(ptr)
+        id = LibGEOS.geomTypeId(ptr)
+        if id == GEOS_LINEARRING
+            ring = new(cloneGeom(ptr))
+            finalizer(destroyGeom, ring)
+            ring
+        else
+            error("LibGEOS: Can't convert a pointer to an element with a GeomType ID of $id to a linear ring (yet). Please open an issue if you think this conversion makes sense.")
+        end
+    end
+    # create linear ring from a list of coordinates - 
+    # first and last coordinates must be the same
+    function LinearRing(coords::Vector{Vector{Float64}})
+        ring = new(createLinearRing(coords))
         finalizer(destroyGeom, ring)
         ring
     end
-    LinearRing(ring::Vector{Vector{Float64}}) = LinearRing(createLinearRing(ring))
 
 end
 
@@ -82,7 +131,7 @@ mutable struct Polygon <: AbstractGeometry
         elseif id == GEOS_LINEARRING
             polygon = new(cloneGeom(createPolygon(ptr)))
         else
-            error("LibGEOS: Can't convert a pointer to an element with a GeomType ID of $id to a polygon (yet). Please open an issue if you think this conversion makes sense. ")
+            error("LibGEOS: Can't convert a pointer to an element with a GeomType ID of $id to a polygon (yet). Please open an issue if you think this conversion makes sense.")
         end
         finalizer(destroyGeom, polygon)
         polygon
@@ -106,12 +155,29 @@ end
 
 mutable struct MultiPolygon <: AbstractGeometry
     ptr::GEOSGeom
-
+    # create multipolygon using a multipolygon or polygon pointer, else error
     function MultiPolygon(ptr::GEOSGeom)
-        multipolygon = new(ptr)
+        id = LibGEOS.geomTypeId(ptr)
+        if id == GEOS_MULTIPOLYGON
+            multipolygon = new(cloneGeom(ptr))
+        elseif id == GEOS_POLYGON
+            multipolygon = new(createCollection(
+                                GEOS_MULTIPOLYGON,
+                                GEOSGeom[cloneGeom(ptr)]))
+        else
+            error("LibGEOS: Can't convert a pointer to an element with a GeomType ID of $id to a multi-polygon (yet). Please open an issue if you think this conversion makes sense.")    
+        end
         finalizer(destroyGeom, multipolygon)
         multipolygon
     end
+
+    # create multipolygon from list of Polygon objects
+    MultiPolygon(polygons::Vector{Polygon}) = MultiPolygon(
+        createCollection(
+            GEOS_MULTIPOLYGON,
+            GEOSGeom[poly.ptr for poly in polygons],))
+
+    # create multipolygon using list of polygon coordinates - note that each polygon can have holes as explained above in Polygon comments
     MultiPolygon(multipolygon::Vector{Vector{Vector{Vector{Float64}}}}) = MultiPolygon(
         createCollection(
             GEOS_MULTIPOLYGON,
@@ -123,17 +189,22 @@ mutable struct MultiPolygon <: AbstractGeometry
             ],
         ),
     )
-
 end
 
 mutable struct GeometryCollection <: AbstractGeometry
     ptr::GEOSGeom
-
+    # create a geometric collection from a pointer to a geometric collection, else error
     function GeometryCollection(ptr::GEOSGeom)
-        geometrycollection = new(ptr)
-        finalizer(destroyGeom, geometrycollection)
-        geometrycollection
+        id = LibGEOS.geomTypeId(ptr)
+        if id == GEOS_GEOMETRYCOLLECTION
+            geometrycollection = new(cloneGeom(ptr))
+            finalizer(destroyGeom, geometrycollection)
+            geometrycollection
+        else
+            error("LibGEOS: Can't convert a pointer to an element with a GeomType ID of $id to a geometry collection (yet). Please open an issue if you think this conversion makes sense.")
+        end
     end
+    # create a geometric collection from a list of pointers to geometric objects
     GeometryCollection(collection::Vector{GEOSGeom}) =
         GeometryCollection(createCollection(GEOS_GEOMETRYCOLLECTION, collection))
 end


Test patch:
diff --git a/test/test_geos_types.jl b/test/test_geos_types.jl
index 0755747..4911655 100644
--- a/test/test_geos_types.jl
+++ b/test/test_geos_types.jl
@@ -1,14 +1,176 @@
+
+# Function to test if a geomerty is valid and if its type matches the geometry ID and has the correct dimensions
+function testValidTypeDims(geom::LibGEOS.Geometry, typeid::LibGEOS.GEOSGeomTypes, dims::Integer)
+    @test LibGEOS.isValid(geom)
+    @test LibGEOS.geomTypeId(geom.ptr) == typeid
+    @test LibGEOS.getGeomDimensions(geom.ptr) == dims
+end
+testValidTypeDims(point::LibGEOS.Point) = 
+    testValidTypeDims(point, LibGEOS.GEOS_POINT, 0)
+testValidTypeDims(multipoint::LibGEOS.MultiPoint) =
+    testValidTypeDims(multipoint, LibGEOS.GEOS_MULTIPOINT, 0)
+testValidTypeDims(linestring::LibGEOS.LineString) =
+    testValidTypeDims(linestring, LibGEOS.GEOS_LINESTRING, 1) 
+testValidTypeDims(multilinestring::LibGEOS.MultiLineString) =
+    testValidTypeDims(multilinestring, LibGEOS.GEOS_MULTILINESTRING, 1)
+testValidTypeDims(ring::LibGEOS.LinearRing) =
+    testValidTypeDims(ring, LibGEOS.GEOS_LINEARRING, 1)
+testValidTypeDims(poly::LibGEOS.Polygon) = 
+    testValidTypeDims(poly, LibGEOS.GEOS_POLYGON, 2)
+testValidTypeDims(multipoly::LibGEOS.MultiPolygon) =
+    testValidTypeDims(multipoly, LibGEOS.GEOS_MULTIPOLYGON, 2)
+
 @testset "GEOS types" begin
+    @testset "Point" begin
+        # Test 2D point made from real x and y
+        point2D = LibGEOS.Point(0.0, 0.0)
+        testValidTypeDims(point2D)
+        @test LibGEOS.area(point2D) == 0.0
+        @test LibGEOS.GeoInterface.coordinates(point2D) == [0.0, 0.0]
+
+        # Test 3D point made from real x, y, and z
+        point3D = LibGEOS.Point(0.0, 0.0, 2.0)
+        testValidTypeDims(point3D)
+        @test LibGEOS.area(point3D) == 0.0
+        @test LibGEOS.GeoInterface.coordinates(point3D) == [0.0, 0.0, 2.0]
+
+        # Test point made from vector 
+        point_coord = LibGEOS.Point([0.0, 0.0, 0.2])
+        testValidTypeDims(point_coord, LibGEOS.GEOS_POINT, 0)
+        @test LibGEOS.equals(point_coord, point3D)
+
+        # Test point made from point pointer
+        point_ptr = LibGEOS.Point(point2D.ptr)
+        testValidTypeDims(point_ptr)
+        @test LibGEOS.equals(point_ptr, point2D)
+
+        # Test point made from polygon pointer --> should not work
+        @test_throws ErrorException LibGEOS.Point(LibGEOS.Polygon(
+            [[[-2.0, -2.0], [2.0, 2.0],[-2.0,2.0], [-2.0, -2.0]]]).ptr)
+
+        LibGEOS.destroyGeom(point2D)
+        LibGEOS.destroyGeom(point3D)
+        LibGEOS.destroyGeom(point_coord)
+        LibGEOS.destroyGeom(point_ptr)
+    end
+
+    @testset "MultiPoint" begin
+        # Test MultiPoint made from vector of coordinates
+        mpoint_coord = LibGEOS.MultiPoint([[0.0, 0.0], [1.0, 1.0]])
+        point1 = Point(0.0, 0.0)
+        point2 = Point(1.0, 1.0)
+        testValidTypeDims(mpoint_coord)
+        @test GeoInterface.coordinates(mpoint_coord) == [[0.0, 0.0], [1.0, 1.0]]
+        @test LibGEOS.numGeometries(mpoint_coord) == 2
+        @test LibGEOS.equals(LibGEOS.getGeometry(mpoint_coord, 1), 
+                             point1)
+        @test LibGEOS.equals(LibGEOS.getGeometry(mpoint_coord, 2),
+                            point2)
+
+        # Test MultiPoint made from MultiPoint pointer
+        multipoint_ptr = LibGEOS.MultiPoint(mpoint_coord.ptr)
+        testValidTypeDims(multipoint_ptr)
+        @test LibGEOS.equals(multipoint_ptr, mpoint_coord)
+        @test LibGEOS.equals(LibGEOS.getGeometry(mpoint_coord, 1), 
+                             LibGEOS.getGeometry(multipoint_ptr, 1))
+        @test LibGEOS.equals(LibGEOS.getGeometry(mpoint_coord, 2), 
+                             LibGEOS.getGeometry(multipoint_ptr, 2))
+
+        # Test MultiPoint made from Point pointer
+        mpoint_ptr = LibGEOS.MultiPoint(
+                             LibGEOS.getGeometry(mpoint_coord, 1).ptr)
+        testValidTypeDims(mpoint_ptr)
+        @test LibGEOS.equals(LibGEOS.getGeometry(mpoint_ptr, 1), 
+                             Point(0.0, 0.0))
+
+        # Test MultiPoint made from Polygon pointer
+        @test_throws ErrorException LibGEOS.MultiPoint(LibGEOS.Polygon(
+            [[[-2.0, -2.0], [2.0, 2.0],[-2.0,2.0], [-2.0, -2.0]]]).ptr)
+
+        # Test MultiPoint made from vector of 1 point and 2 points
+        point_coord1 = LibGEOS.MultiPoint([point1])
+        point_coord2 = LibGEOS.MultiPoint([point1, point2])
+        testValidTypeDims(point_coord1)
+        testValidTypeDims(point_coord2)
+        @test LibGEOS.numGeometries(point_coord1) == 1
+        @test LibGEOS.equals(point_coord2, mpoint_coord)
+
+        LibGEOS.destroyGeom(mpoint_coord)
+        LibGEOS.destroyGeom(point1)
+        LibGEOS.destroyGeom(point2)
+        LibGEOS.destroyGeom(multipoint_ptr)
+        LibGEOS.destroyGeom(mpoint_ptr)
+        LibGEOS.destroyGeom(point_coord1)
+        LibGEOS.destroyGeom(point_coord2)
+    end
+
+    @testset "LineString" begin
+        # Test LineString made from vectors
+        ls_coord = LibGEOS.LineString([[0.0, 0.0], [1.0, 1.0], [1.0,0.0]])
+        testValidTypeDims(ls_coord)
+        @test LibGEOS.numCoordinates(ls_coord.ptr) == 3
+        
+        # Test LineString made from linestring pointer
+        ls_ptr = LibGEOS.LineString(ls_coord.ptr)
+        testValidTypeDims(ls_ptr)
+        @test LibGEOS.equals(ls_coord, ls_ptr)
+
+        # Test LineString made from polygon pointer
+        @test_throws ErrorException LibGEOS.LineString(LibGEOS.Polygon(
+            [[[-2.0, -2.0], [2.0, 2.0],[-2.0,2.0], [-2.0, -2.0]]]).ptr)
+
+        LibGEOS.destroyGeom(ls_coord)
+        LibGEOS.destroyGeom(ls_ptr)
+    end
+
+    @testset "MultiLineString" begin
+        # Test MultiLineString made from vectors
+        mls_coord = LibGEOS.MultiLineString([[[0.0, 0.0], [1.0, 1.0], [1.0,0.0]], [[-2.0, -2.0], [2.0, 2.0],[-2.0,2.0]]])
+        testValidTypeDims(mls_coord)
+        @test LibGEOS.numGeometries(mls_coord) == 2
+
+        # Test MultiLineString made from MultiLineString pointer
+        mls_ptr = LibGEOS.MultiLineString(mls_coord.ptr)
+        testValidTypeDims(mls_ptr)
+        @test LibGEOS.equals(mls_coord, mls_ptr)
+
+        # Test MultiLineString made from polygon pointer
+        @test_throws ErrorException LibGEOS.MultiLineString(LibGEOS.Polygon(
+            [[[-2.0, -2.0], [2.0, 2.0],[-2.0,2.0], [-2.0, -2.0]]]).ptr)
+
+        LibGEOS.destroyGeom(mls_coord)
+        LibGEOS.destroyGeom(mls_ptr)
+    end
+
+    @testset "LinearRing" begin
+        # Test LinearRing made from vectors
+        lr_coord = LibGEOS.LinearRing([[-2.0, -2.0], [2.0, -2.0], [2.0, 2.0],
+        [-2.0, 2.0], [-2.0, -2.0]])
+        testValidTypeDims(lr_coord)
+        @test LibGEOS.numCoordinates(lr_coord.ptr) == 5
+
+        # Test LinearRing made from LinearRing pointer
+        lr_ptr = LibGEOS.LinearRing(lr_coord.ptr)
+        testValidTypeDims(lr_ptr)
+        @test LibGEOS.equals(lr_ptr, lr_coord)
+
+        # Test LinearRing made from polygon pointer
+        @test_throws ErrorException LibGEOS.LinearRing(LibGEOS.Polygon(
+            [[[-2.0, -2.0], [2.0, 2.0],[-2.0,2.0], [-2.0, -2.0]]]).ptr)
+
+        LibGEOS.destroyGeom(lr_coord)
+        LibGEOS.destroyGeom(lr_ptr)
+    end
+
+
     @testset "Polygon" begin
         # Test polygon made from vectors
         poly_vec = LibGEOS.Polygon([
             [[-2.0, -2.0], [2.0, -2.0], [2.0, 2.0], [-2.0,2.0], [-2.0, -2.0]],
             [[0.0, 0.0], [1.0, 1.0], [1.0,0.0], [0.0, 0.0]]])
-        @test LibGEOS.isValid(poly_vec)
+        testValidTypeDims(poly_vec)
         @test LibGEOS.area(poly_vec) == 15.5
         @test !LibGEOS.isEmpty(poly_vec)
-        @test LibGEOS.geomTypeId(poly_vec.ptr) == 3
-        @test LibGEOS.getGeomDimensions(poly_vec.ptr) == 2
         @test LibGEOS.GeoInterface.coordinates(poly_vec) == 
             [[[-2.0, -2.0], [2.0, -2.0], [2.0, 2.0], [-2.0,2.0], [-2.0, -2.0]],
              [[0.0, 0.0], [1.0, 1.0], [1.0,0.0], [0.0, 0.0]]]
@@ -16,25 +178,22 @@
 
         # Test polygon made from  polygon pointer
         poly_ptr = LibGEOS.Polygon(poly_vec.ptr)
-        @test LibGEOS.isValid(poly_ptr)
+        testValidTypeDims(poly_ptr)
         @test LibGEOS.equals(poly_vec, poly_ptr) # same area and coordinates
-        @test LibGEOS.geomTypeId(poly_vec.ptr) == 3
-        @test LibGEOS.getGeomDimensions(poly_vec.ptr) == 2
         @test length(LibGEOS.interiorRings(poly_vec)) == 1
 
         # Test polygon made from linear ring pointer
         ring_ext = LibGEOS.LinearRing([[-2.0, -2.0], [2.0, -2.0], [2.0, 2.0],
                                        [-2.0, 2.0], [-2.0, -2.0]])
         poly_ringptr = LibGEOS.Polygon(ring_ext.ptr)
-        # These change when made into polygon - compare below
-        @test LibGEOS.geomTypeId(ring_ext.ptr) == 2
-        @test LibGEOS.getGeomDimensions(ring_ext.ptr) == 1
 
-        @test LibGEOS.isValid(poly_ringptr)
+        # tests that ring's geomTypeID is linear ring and dimensions is 1
+        testValidTypeDims(ring_ext) 
+
+        # tests that the geomTypeID is polygon and dimensions is 2
+        testValidTypeDims(poly_ringptr) 
         @test LibGEOS.area(poly_ringptr) == 16
         @test !LibGEOS.isEmpty(poly_ringptr)
-        @test LibGEOS.geomTypeId(poly_ringptr.ptr) == 3
-        @test LibGEOS.getGeomDimensions(poly_ringptr.ptr) == 2
 
         # Test polygon made from point pointer
         point = LibGEOS.Point(1.0, 2.0)
@@ -42,11 +201,9 @@
 
         # Test polygon made from 1 linear ring
         poly_ring = LibGEOS.Polygon(ring_ext)
-        @test LibGEOS.isValid(poly_ring)
+        testValidTypeDims(poly_ring)
         @test LibGEOS.area(poly_ring) == 16
         @test !LibGEOS.isEmpty(poly_ring)
-        @test LibGEOS.geomTypeId(poly_ring.ptr) == 3
-        @test LibGEOS.getGeomDimensions(poly_ring.ptr) == 2
         @test LibGEOS.GeoInterface.coordinates(poly_ring) == 
             [[[-2.0, -2.0], [2.0, -2.0], [2.0, 2.0], [-2.0, 2.0], [-2.0, -2.0]]]
         @test length(LibGEOS.interiorRings(poly_ring)) == 0
@@ -59,25 +216,90 @@
 
         #1 ring
         poly_rings1 = LibGEOS.Polygon(ring_ext, [ring_int1])
-        @test LibGEOS.isValid(poly_rings1)
-        @test LibGEOS.geomTypeId(poly_rings1.ptr) == 3
-        @test LibGEOS.getGeomDimensions(poly_rings1.ptr) == 2
+        testValidTypeDims(poly_rings1)
         @test length(LibGEOS.interiorRings(poly_rings1)) == 1
         @test LibGEOS.equals(poly_rings1, poly_vec)
 
         #2 rings
         poly_rings2 = LibGEOS.Polygon(ring_ext, [ring_int1, ring_int2])
-
-        @test LibGEOS.isValid(poly_rings2)
+        testValidTypeDims(poly_rings2)
         @test LibGEOS.area(poly_rings2) == 14.5
         @test !LibGEOS.isEmpty(poly_rings2)
-        @test LibGEOS.geomTypeId(poly_rings2.ptr) == 3
-        @test LibGEOS.getGeomDimensions(poly_rings2.ptr) == 2
         @test LibGEOS.GeoInterface.coordinates(poly_rings2) == 
             [[[-2.0, -2.0], [2.0, -2.0], [2.0, 2.0], [-2.0, 2.0], [-2.0, -2.0]],
             [[0.0, 0.0], [1.0, 1.0], [1.0,0.0],[0.0, 0.0]],
              [[0.0, 0.0], [0.0, -1.0], [-1.0, -1.0],[-1.0, 0.0], [0.0, 0.0]]]
         @test length(LibGEOS.interiorRings(poly_rings2)) == 2
+
+        LibGEOS.destroyGeom(poly_vec)
+        LibGEOS.destroyGeom(poly_ptr)
+        LibGEOS.destroyGeom(ring_ext)
+        LibGEOS.destroyGeom(poly_ringptr)
+        LibGEOS.destroyGeom(point)
+        LibGEOS.destroyGeom(poly_ring)
+        LibGEOS.destroyGeom(ring_int1)
+        LibGEOS.destroyGeom(ring_int2)
+        LibGEOS.destroyGeom(poly_rings1)
+        LibGEOS.destroyGeom(poly_rings2)
+    end
+
+    @testset "MultiPolygon" begin
+        poly1 = LibGEOS.Polygon([[[-2.0, -2.0], [2.0, 2.0], 
+                                  [-2.0, 2.0], [-2.0, -2.0]]])
+        poly2 = LibGEOS.Polygon([[[0.0, 0.0], [1.0, -1.0], 
+                                  [1.0, 0.0], [0.0, 0.0]]])
+        # Test multipolygon made from vectors
+        mpoly_coord = LibGEOS.MultiPolygon([
+            [[[-2.0, -2.0], [2.0, 2.0], [-2.0, 2.0], [-2.0, -2.0]]], 
+            [[[0.0, 0.0], [1.0, -1.0], [1.0, 0.0], [0.0, 0.0]]]])
+        testValidTypeDims(mpoly_coord)
+        @test LibGEOS.numGeometries(mpoly_coord) == 2
+        @test LibGEOS.equals(LibGEOS.getGeometry(mpoly_coord, 1), poly1)
+
+        # Test multipolygon made from multipolygon pointer
+        mpoly_ptr = LibGEOS.MultiPolygon(mpoly_coord.ptr)
+        testValidTypeDims(mpoly_coord)
+        @test LibGEOS.equals(mpoly_ptr, mpoly_coord)
+
+        # Test multipolygon made from polygon pointer
+        mpoly_poly_ptr = LibGEOS.MultiPolygon(poly1.ptr)
+        testValidTypeDims(mpoly_poly_ptr)
+        @test LibGEOS.equals(LibGEOS.getGeometry(mpoly_poly_ptr, 1), poly1) 
+
+        # Test multipolygon made from point pointer --> should finalizer
+        @test_throws ErrorException LibGEOS.MultiPolygon(LibGEOS.Point(1.0, 2.0).ptr)
+
+        # Test multipolygon made from list of polygons
+        mpoly_polylist = LibGEOS.MultiPolygon([poly1, poly2])
+        testValidTypeDims(mpoly_polylist)
+        @test LibGEOS.equals(mpoly_polylist, mpoly_coord)
+
+        LibGEOS.destroyGeom(poly1)
+        LibGEOS.destroyGeom(poly2)
+        LibGEOS.destroyGeom(mpoly_coord)
+        LibGEOS.destroyGeom(mpoly_ptr)
+        LibGEOS.destroyGeom(mpoly_poly_ptr)
+        LibGEOS.destroyGeom(mpoly_polylist)
+    end
+
+    @testset "GeometryCollections" begin
+        point = LibGEOS.Point(0.0, 0.0)
+        poly = LibGEOS.Polygon([[[-2.0, -2.0], [2.0, 2.0], 
+                                  [-2.0, 2.0], [-2.0, -2.0]]])
+        # Test GeometryCollection from list of geometry pointers
+        geomcol_ptr_list = LibGEOS.GeometryCollection([point.ptr, poly.ptr])
+        @test LibGEOS.isValid(geomcol_ptr_list)
+        @test LibGEOS.geomTypeId(geomcol_ptr_list.ptr) == LibGEOS.GEOS_GEOMETRYCOLLECTION
+        #test GeometryCollection from GeometryCollection pointer
+        geomcollection_ptr = LibGEOS.GeometryCollection(geomcol_ptr_list.ptr)
+        @test LibGEOS.isValid(geomcollection_ptr)
+        @test LibGEOS.geomTypeId(geomcollection_ptr.ptr) == LibGEOS.GEOS_GEOMETRYCOLLECTION
+        @test LibGEOS.equals(geomcol_ptr_list, geomcollection_ptr)
+
+        LibGEOS.destroyGeom(point)
+        LibGEOS.destroyGeom(poly)
+        LibGEOS.destroyGeom(geomcol_ptr_list)
+        LibGEOS.destroyGeom(geomcollection_ptr)
     end
 
 end
\ No newline at end of file