From 72f751ba25c3708359b23df41f98cd4b47ab8a36 Mon Sep 17 00:00:00 2001 From: Jaakko Ruohio Date: Mon, 13 Jan 2025 13:40:14 +0200 Subject: [PATCH 1/4] Basic support for curved geometries --- src/LibGEOS.jl | 7 +- src/geos_types.jl | 137 ++++++++++++++++++++++++++++++++++++++++ test/test_geos_types.jl | 20 ++++++ 3 files changed, 163 insertions(+), 1 deletion(-) diff --git a/src/LibGEOS.jl b/src/LibGEOS.jl index 691c2cd..b02fd19 100644 --- a/src/LibGEOS.jl +++ b/src/LibGEOS.jl @@ -8,12 +8,17 @@ using CEnum const GI = GeoInterface -export GeometryCollection, +export CircularString, + CompoundCurve, + CurvePolygon, + GeometryCollection, LineString, LinearRing, + MultiCurve, MultiLineString, MultiPoint, MultiPolygon, + MultiSurface, Point, Polygon, STRtree diff --git a/src/geos_types.jl b/src/geos_types.jl index bca2a1c..e25d709 100644 --- a/src/geos_types.jl +++ b/src/geos_types.jl @@ -390,6 +390,113 @@ mutable struct GeometryCollection <: AbstractMultiGeometry ) end +mutable struct CircularString <: AbstractGeometry + ptr::GEOSGeom + context::GEOSContext + function CircularString( + obj::CircularString, + context::GEOSContext = get_global_context(), + ) + circularstring = new(cloneGeom(obj, context), context) + finalizer(destroyGeom, circularstring) + circularstring + end + # create a circularstring from a circularstring pointer, otherwise error + function CircularString(obj::GEOSGeom, context::GEOSContext = get_global_context()) + id = LibGEOS.geomTypeId(obj, context) + circularstring = if id == GEOS_CIRCULARSTRING + new(obj, context) + else + open_issue_if_conversion_makes_sense(CircularString, id) + end + finalizer(destroyGeom, circularstring) + circularstring + end +end + +mutable struct CompoundCurve <: AbstractGeometry + ptr::GEOSGeom + context::GEOSContext + function CompoundCurve(obj::CompoundCurve, context::GEOSContext = get_global_context()) + compoundcurve = new(cloneGeom(obj, context), context) + finalizer(destroyGeom, compoundcurve) + compoundcurve + end + # create a compoundcurve from a compoundcurve pointer, otherwise error + function CompoundCurve(obj::GEOSGeom, context::GEOSContext = get_global_context()) + id = LibGEOS.geomTypeId(obj, context) + compoundcurve = if id == GEOS_COMPOUNDCURVE + new(obj, context) + else + open_issue_if_conversion_makes_sense(CompoundCurve, id) + end + finalizer(destroyGeom, compoundcurve) + compoundcurve + end +end + +mutable struct CurvePolygon <: AbstractGeometry + ptr::GEOSGeom + context::GEOSContext + function CurvePolygon(obj::CurvePolygon, context::GEOSContext = get_global_context()) + curvepolygon = new(cloneGeom(obj, context), context) + finalizer(destroyGeom, curvepolygon) + curvepolygon + end + function CurvePolygon(obj::GEOSGeom, context::GEOSContext = get_global_context()) + id = LibGEOS.geomTypeId(obj, context) + curvepolygon = if id == GEOS_CURVEPOLYGON + new(obj, context) + else + open_issue_if_conversion_makes_sense(CurvePolygon, id) + end + finalizer(destroyGeom, curvepolygon) + curvepolygon + end +end + +mutable struct MultiCurve <: AbstractGeometry + ptr::GEOSGeom + context::GEOSContext + function MultiCurve(obj::MultiCurve, context::GEOSContext = get_global_context()) + multicurve = new(cloneGeom(obj, context), context) + finalizer(destroyGeom, multicurve) + multicurve + end + # create a compoundcurve from a compoundcurve pointer, otherwise error + function MultiCurve(obj::GEOSGeom, context::GEOSContext = get_global_context()) + id = LibGEOS.geomTypeId(obj, context) + multicurve = if id == GEOS_MULTICURVE + new(obj, context) + else + open_issue_if_conversion_makes_sense(MultiCurve, id) + end + finalizer(destroyGeom, multicurve) + multicurve + end +end + +mutable struct MultiSurface <: AbstractGeometry + ptr::GEOSGeom + context::GEOSContext + function MultiSurface(obj::MultiSurface, context::GEOSContext = get_global_context()) + multisurface = new(cloneGeom(obj, context), context) + finalizer(destroyGeom, multisurface) + multisurface + end + # create a multisurface from a multisurface, otherwise error + function MultiSurface(obj::GEOSGeom, context::GEOSContext = get_global_context()) + id = LibGEOS.geomTypeId(obj, context) + multisurface = if id == GEOS_MULTISURFACE + new(obj, context) + else + open_issue_if_conversion_makes_sense(MultiSurface, id) + end + finalizer(destroyGeom, multisurface) + multisurface + end +end + const Geometry = Union{ Point, MultiPoint, @@ -399,6 +506,11 @@ const Geometry = Union{ Polygon, MultiPolygon, GeometryCollection, + CircularString, + CompoundCurve, + CurvePolygon, + MultiCurve, + MultiSurface, } """ @@ -439,6 +551,11 @@ const geomtypes = [ MultiLineString, MultiPolygon, GeometryCollection, + CircularString, + CompoundCurve, + CurvePolygon, + MultiCurve, + MultiSurface, ] const HasCoordSeq = Union{LineString,LinearRing,Point} @@ -573,6 +690,11 @@ typesalt(::Type{MultiPoint}) = 0x6213e67dbfd3b570 typesalt(::Type{MultiPolygon}) = 0xff2f957b4cdb5832 typesalt(::Type{Point}) = 0x4b5c101d3843160e typesalt(::Type{Polygon}) = 0xa5c895d62ef56723 +typesalt(::Type{CircularString}) = 0x78ba4f50813d8da9 +typesalt(::Type{CompoundCurve}) = 0x44258e194220dc61 +typesalt(::Type{CurvePolygon}) = 0xdd0a2660d0239f1d +typesalt(::Type{MultiCurve}) = 0x0641e0678af4d103 +typesalt(::Type{MultiSurface}) = 0x9f6a2cba51468ea5 function Base.hash(geo::AbstractGeometry, h::UInt)::UInt h = hash(typesalt(typeof(geo)), h) @@ -617,6 +739,11 @@ const GEOMTYPE = Dict{GEOSGeomTypes,Symbol}( GEOS_MULTILINESTRING => :MultiLineString, GEOS_MULTIPOLYGON => :MultiPolygon, GEOS_GEOMETRYCOLLECTION => :GeometryCollection, + GEOS_CIRCULARSTRING => :CircularString, + GEOS_COMPOUNDCURVE => :CompoundCurve, + GEOS_CURVEPOLYGON => :CurvePolygon, + GEOS_MULTICURVE => :MultiCurve, + GEOS_MULTISURFACE => :MultiSurface, ) function geomFromGEOS( @@ -640,6 +767,16 @@ function geomFromGEOS( return MultiPolygon(ptr, context) elseif id == GEOS_GEOMETRYCOLLECTION return GeometryCollection(ptr, context) + elseif id == GEOS_CIRCULARSTRING + return CircularString(ptr, context) + elseif id == GEOS_COMPOUNDCURVE + return CompoundCurve(ptr, context) + elseif id == GEOS_CURVEPOLYGON + return CurvePolygon(ptr, context) + elseif id == GEOS_MULTICURVE + return MultiCurve(ptr, context) + elseif id == GEOS_MULTISURFACE + return MultiSurface(ptr, context) else throw(ErrorException("Geometric type with code $id not implemented.")) end diff --git a/test/test_geos_types.jl b/test/test_geos_types.jl index 73f61f7..a38c3a2 100644 --- a/test/test_geos_types.jl +++ b/test/test_geos_types.jl @@ -442,3 +442,23 @@ end @test LibGEOS.equals(g1, g2) end end + +@testset "Curved geometry types" begin + geos = [ + readgeom("CIRCULARSTRING(0 0,1 1,2 0)") + readgeom("CIRCULARSTRING (0 0, 1 1, 2 0, 3 -1, 4 0, 2 2, 0 0)") + readgeom("CURVEPOLYGON ((0 0, 2 0, 0 2, 0 0))") + readgeom("CURVEPOLYGON (CIRCULARSTRING (0 0, 1 1, 2 0, 3 -1, 4 0, 2 2, 0 0))") + readgeom("COMPOUNDCURVE (CIRCULARSTRING (0 0, 1 1, 2 0), (2 0, 2 -1, 0 -1, 0 0))") + readgeom( + "CURVEPOLYGON (COMPOUNDCURVE (CIRCULARSTRING (0 0, 1 1, 2 0), (2 0, 2 -1, 0 -1, 0 0)))", + ) + readgeom( + "MULTISURFACE (CURVEPOLYGON (CIRCULARSTRING (0 0, 1 1, 2 0, 3 -1, 4 0, 2 2, 0 0)))", + ) + readgeom("MULTICURVE((1 0, 2 0))") + ] + + @test all(geomLength.(geos) .≈ [pi, 4pi, 4 + 2 * sqrt(2), 4pi, pi + 4, pi + 4, 4pi, 1]) + @test all(area.(geos) .≈ [0, 0, 2, 2pi, 0, pi / 2 + 2, 2pi, 0]) +end From d08a21126829ac0817ea610d2fbb0415e2724b3c Mon Sep 17 00:00:00 2001 From: Jaakko Ruohio Date: Mon, 13 Jan 2025 18:26:35 +0200 Subject: [PATCH 2/4] Add curved geometry definitions to geo_interface.jl --- src/geo_interface.jl | 26 +++++++++++++++++++++++-- src/geos_functions.jl | 43 +++++++++++++++++++++++++++++++++++++---- test/test_geos_types.jl | 14 ++++++++++++++ 3 files changed, 77 insertions(+), 6 deletions(-) diff --git a/src/geo_interface.jl b/src/geo_interface.jl index 32214e1..fb5c5e9 100644 --- a/src/geo_interface.jl +++ b/src/geo_interface.jl @@ -8,6 +8,11 @@ geointerface_geomtype(::LinearRingTrait) = LinearRing geointerface_geomtype(::PolygonTrait) = Polygon geointerface_geomtype(::MultiPolygonTrait) = MultiPolygon geointerface_geomtype(::GeometryCollectionTrait) = GeometryCollection +geointerface_geomtype(::CircularStringTrait) = CircularString +geointerface_geomtype(::CompoundCurveTrait) = CompoundCurve +geointerface_geomtype(::CurvePolygonTrait) = CurvePolygon +geointerface_geomtype(::MultiSurfaceTrait) = MultiSurface +geointerface_geomtype(::MultiCurveTrait) = MultiCurve GeoInterface.geomtrait(::Point) = PointTrait() GeoInterface.geomtrait(::MultiPoint) = MultiPointTrait() @@ -17,6 +22,11 @@ GeoInterface.geomtrait(::LinearRing) = LinearRingTrait() GeoInterface.geomtrait(::Polygon) = PolygonTrait() GeoInterface.geomtrait(::MultiPolygon) = MultiPolygonTrait() GeoInterface.geomtrait(::GeometryCollection) = GeometryCollectionTrait() +GeoInterface.geomtrait(::CircularString) = CircularStringTrait() +GeoInterface.geomtrait(::CompoundCurve) = CompoundCurveTrait() +GeoInterface.geomtrait(::CurvePolygon) = CurvePolygonTrait() +GeoInterface.geomtrait(::MultiSurface) = MultiSurfaceTrait() +GeoInterface.geomtrait(::MultiCurve) = MultiCurveTrait() GeoInterface.geomtrait(geom::PreparedGeometry) = GeoInterface.geomtrait(geom.ownedby) GeoInterface.isempty(::AbstractGeometryTrait, geom::AbstractGeometry) = isEmpty(geom) @@ -24,16 +34,21 @@ GeoInterface.isempty(::AbstractGeometryTrait, geom::AbstractGeometry) = isEmpty( GeoInterface.ngeom(::AbstractGeometryCollectionTrait, geom::AbstractMultiGeometry) = isEmpty(geom) ? 0 : Int(numGeometries(geom)) GeoInterface.ngeom(::LineStringTrait, geom::LineString) = Int(numPoints(geom)) +GeoInterface.ngeom(::CircularStringTrait, geom::CircularString) = Int(numPoints(geom)) GeoInterface.ngeom(::LinearRingTrait, geom::LinearRing) = Int(numPoints(geom)) GeoInterface.ngeom(::PolygonTrait, geom::Polygon) = Int(numInteriorRings(geom)) + 1 +GeoInterface.ngeom(::CurvePolygonTrait, geom::CurvePolygon) = + Int(numInteriorRings(geom)) + 1 GeoInterface.ngeom(t::AbstractGeometryTrait, geom::PreparedGeometry) = GeoInterface.ngeom(t, geom.ownedby) GeoInterface.ngeom(::AbstractPointTrait, geom::Point) = 0 GeoInterface.ngeom(::AbstractPointTrait, geom::PreparedGeometry) = 0 GI.is3d(::AbstractGeometryTrait, geom::AbstractGeometry) = hasZ(geom) -GI.getexterior(::AbstractPolygonTrait, geom::Polygon) = exteriorRing(geom) -GI.gethole(::AbstractPolygonTrait, geom::Polygon, n) = interiorRing(geom, n) +GI.getexterior(::AbstractPolygonTrait, geom::Union{Polygon,CurvePolygon}) = + exteriorRing(geom) +GI.gethole(::AbstractPolygonTrait, geom::Union{Polygon,CurvePolygon}, n) = + interiorRing(geom, n) function GeoInterface.getgeom( ::AbstractGeometryCollectionTrait, @@ -60,6 +75,13 @@ function GeoInterface.getgeom(::PolygonTrait, geom::Polygon, i::Int) interiorRing(geom, i - 1) end end +function GeoInterface.getgeom(::CurvePolygonTrait, geom::CurvePolygon, i::Int) + if i == 1 + exteriorRing(geom) + else + interiorRing(geom, i - 1) + end +end GeoInterface.getgeom(t::AbstractGeometryTrait, geom::PreparedGeometry, i) = GeoInterface.getgeom(t, geom.ownedby, i) GeoInterface.getgeom(t::AbstractPointTrait, geom::PreparedGeometry, i) = 0 diff --git a/src/geos_functions.jl b/src/geos_functions.jl index ce8233f..cd844ab 100644 --- a/src/geos_functions.jl +++ b/src/geos_functions.jl @@ -1336,7 +1336,10 @@ function normalize!(obj::Geometry, context::GEOSContext = get_context(obj)) end # Return -1 on exception -function numInteriorRings(obj::Polygon, context::GEOSContext = get_context(obj)) +function numInteriorRings( + obj::Union{Polygon,CurvePolygon}, + context::GEOSContext = get_context(obj), +) result = GEOSGetNumInteriorRings_r(context, obj) if result == -1 error("LibGEOS: Error in GEOSGetNumInteriorRings") @@ -1346,7 +1349,7 @@ end # Call only on LINESTRING (returns -1 on exception) function numPoints( - obj::Union{LineString,LinearRing}, + obj::Union{LineString,LinearRing,CircularString}, context::GEOSContext = get_context(obj), ) result = GEOSGeomGetNumPoints_r(context, obj) @@ -1408,14 +1411,46 @@ function interiorRings(obj::Polygon, context::GEOSContext = get_context(obj)) end end +# Polygon rings are of type LinearRingcan, and can be inferred, but CurvePolygon rings can be Union{LinearRing, CompoundCurve} +function interiorRing( + obj::CurvePolygon, + n::Integer, + context::GEOSContext = get_context(obj), +) + if !(0 < n <= numInteriorRings(obj, context)) + error( + "LibGEOS: n=$n is out of bounds for CurvePolygon with $(numInteriorRings(obj, context)) interior ring(s)", + ) + end + result = GEOSGetInteriorRingN_r(context, obj, n - 1) + if result == C_NULL + error("LibGEOS: Error in GEOSGetInteriorRingN") + end + geomFromGEOS(cloneGeom(result, context), context) +end + +function interiorRings(obj::CurvePolygon, context::GEOSContext = get_context(obj)) + n = numInteriorRings(obj, context) + if n == 0 + return LinearRing[] + else + return [interiorRing(obj, i, context) for i = 1:n] + end +end + + # Return NULL on exception, Geometry must be a Polygon. # Returned object is a pointer to internal storage: it must NOT be destroyed directly. -function exteriorRing(obj::Polygon, context::GEOSContext = get_context(obj)) +function exteriorRing( + obj::Union{Polygon,CurvePolygon}, + context::GEOSContext = get_context(obj), +)::Union{LinearRing,CompoundCurve} result = GEOSGetExteriorRing_r(context, obj) if result == C_NULL error("LibGEOS: Error in GEOSGetExteriorRing") end - LinearRing(cloneGeom(result, context), context) + geomFromGEOS(cloneGeom(result, context), context) + # LinearRing(cloneGeom(result, context), context) end # Return -1 on exception diff --git a/test/test_geos_types.jl b/test/test_geos_types.jl index a38c3a2..9652407 100644 --- a/test/test_geos_types.jl +++ b/test/test_geos_types.jl @@ -461,4 +461,18 @@ end @test all(geomLength.(geos) .≈ [pi, 4pi, 4 + 2 * sqrt(2), 4pi, pi + 4, pi + 4, 4pi, 1]) @test all(area.(geos) .≈ [0, 0, 2, 2pi, 0, pi / 2 + 2, 2pi, 0]) + + cp = readgeom( + "CURVEPOLYGON (COMPOUNDCURVE (CIRCULARSTRING (0 0, 1 1, 2 0), (2 0, 2 -1, 0 -1, 0 0)))", + ) + @test_broken isequal(cp, CurvePolygon(cp)) + + cp = readgeom( + "CURVEPOLYGON (COMPOUNDCURVE (CIRCULARSTRING (0 0, 2 2, 4 0), (4 0, 4 -1, 0 -1, 0 0)), LINEARRING (1 0, 3 0, 3 1, 1 1, 1 0))", + ) + @test area(cp) ≈ 2pi + 2 + @test geomLength(cp) ≈ 2pi + 12 + @test length(interiorRings(cp)) == 1 + @test typeof(exteriorRing(cp)) == CompoundCurve + @test typeof(interiorRings(cp)[1]) == LinearRing end From e3f9293c8abbd6d51819b911ec207c5e6f7b6ff7 Mon Sep 17 00:00:00 2001 From: Jaakko Ruohio Date: Sun, 19 Jan 2025 11:43:07 +0200 Subject: [PATCH 3/4] Add some curved geometry constructors --- src/geos_functions.jl | 51 +++++++++++++++++++++++++++++++++++++++---- src/geos_types.jl | 47 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+), 4 deletions(-) diff --git a/src/geos_functions.jl b/src/geos_functions.jl index cd844ab..58fab4d 100644 --- a/src/geos_functions.jl +++ b/src/geos_functions.jl @@ -555,6 +555,38 @@ function createCollection( result end +function createCircularString(ptr::GEOSCoordSeq, context::GEOSContext = get_global_context()) + result = GEOSGeom_createCircularString_r(context, ptr) + if result == C_NULL + error("LibGEOS: Error in GEOSGeom_createCircularString") + end + result +end +createCircularString( + coords::Vector{Vector{Float64}}, + context::GEOSContext = get_global_context(), +) = GEOSGeom_createCircularString_r(context, createCoordSeq(coords, context)) + +# Second argument is an array of GEOSGeometry* objects. +# The caller remains owner of the array, but pointed-to +# objects become ownership of the returned GEOSGeometry. +function createCurvePolygon( + shell::Union{LinearRing,CircularString,CompoundCurve,GEOSGeom}, + holes::AbstractVector, + context::GEOSContext = get_global_context(), +) + result = GEOSGeom_createCurvePolygon_r(context, shell, holes, length(holes)) + if result == C_NULL + error("LibGEOS: Error in GEOSGeom_createCurvePolygon") + end + result +end +# convenience function to create polygon without holes +createCurvePolygon( + shell::Union{LinearRing,CircularString,CompoundCurve,GEOSGeom}, + context::GEOSContext = get_global_context(), +) = createCurvePolygon(shell, GEOSGeom[], context) + function createEmptyCollection( geomtype::GEOSGeomTypes, context::GEOSContext = get_global_context(), @@ -1411,7 +1443,7 @@ function interiorRings(obj::Polygon, context::GEOSContext = get_context(obj)) end end -# Polygon rings are of type LinearRingcan, and can be inferred, but CurvePolygon rings can be Union{LinearRing, CompoundCurve} +# Polygon rings are of type LinearRing, and can be inferred, but CurvePolygon rings can be Union{LinearRing,CircularString,CompoundCurve} function interiorRing( obj::CurvePolygon, n::Integer, @@ -1442,17 +1474,28 @@ end # Return NULL on exception, Geometry must be a Polygon. # Returned object is a pointer to internal storage: it must NOT be destroyed directly. function exteriorRing( - obj::Union{Polygon,CurvePolygon}, + obj::Polygon, + context::GEOSContext = get_context(obj), +)::LinearRing + result = GEOSGetExteriorRing_r(context, obj) + if result == C_NULL + error("LibGEOS: Error in GEOSGetExteriorRing") + end + LinearRing(cloneGeom(result, context), context) +end + +function exteriorRing( + obj::CurvePolygon, context::GEOSContext = get_context(obj), -)::Union{LinearRing,CompoundCurve} +)::Union{LinearRing,CircularString,CompoundCurve} result = GEOSGetExteriorRing_r(context, obj) if result == C_NULL error("LibGEOS: Error in GEOSGetExteriorRing") end geomFromGEOS(cloneGeom(result, context), context) - # LinearRing(cloneGeom(result, context), context) end + # Return -1 on exception function numCoordinates(obj::Geometry, context::GEOSContext = get_context(obj)) result = GEOSGetNumCoordinates_r(context, obj) diff --git a/src/geos_types.jl b/src/geos_types.jl index e25d709..8991cce 100644 --- a/src/geos_types.jl +++ b/src/geos_types.jl @@ -412,6 +412,20 @@ mutable struct CircularString <: AbstractGeometry finalizer(destroyGeom, circularstring) circularstring end + # create a circularstring from a vector of points + function CircularString( + coords::Vector{Vector{Float64}}, + context::GEOSContext = get_global_context(), + ) + circularstring = new(createCircularString(coords, context), context) + finalizer(destroyGeom, circularstring) + circularstring + end + function CircularString(coords::Vector{Point}, context::GEOSContext = get_global_context()) + circularstring = new(createCircularString(coords, context), context) + finalizer(destroyGeom, circularstring) + circularstring + end end mutable struct CompoundCurve <: AbstractGeometry @@ -443,16 +457,49 @@ mutable struct CurvePolygon <: AbstractGeometry finalizer(destroyGeom, curvepolygon) curvepolygon end + function CurvePolygon(obj::Union{LinearRing,CircularString,CompoundCurve}, context::GEOSContext = get_global_context()) + curvepolygon = new(createCurvePolygon(cloneGeom(obj, context), context), context) + finalizer(destroyGeom, curvepolygon) + curvepolygon + end function CurvePolygon(obj::GEOSGeom, context::GEOSContext = get_global_context()) id = LibGEOS.geomTypeId(obj, context) curvepolygon = if id == GEOS_CURVEPOLYGON new(obj, context) + elseif id in [GEOS_LINEARRING,GEOS_CIRCULARSTRING,GEOS_COMPOUNDCURVE] + new(createCurvePolygon(obj, context), context) else open_issue_if_conversion_makes_sense(CurvePolygon, id) end finalizer(destroyGeom, curvepolygon) curvepolygon end + # using vector of coordinates in following form: + # [[exterior], [hole1], [hole2], ...] where exterior and holeN are coordinates where the first and last point are the same + function CurvePolygon( + coords::Vector{Vector{Vector{Float64}}}, + context::GEOSContext = get_global_context(), + ) + exterior = createLinearRing(coords[1], context) + interiors = GEOSGeom[createLinearRing(lr, context) for lr in coords[2:end]] + curvepolygon = new(createCurvePolygon(exterior, interiors, context), context) + finalizer(destroyGeom, curvepolygon) + curvepolygon + end + # using multiple rings to form polygon with holes - exterior ring will be polygon boundary and list of interior rings will form holes + CurvePolygon( + exterior::Union{LinearRing,CircularString,CompoundCurve}, + holes::Vector{Union{LinearRing,CircularString,CompoundCurve}}, + context::GEOSContext = get_context(exterior), + ) = CurvePolygon( + createCurvePolygon( + cloneGeom(exterior, context), + cloneGeom.(holes, Ref(context)), + context, + ), + context, + ) + end mutable struct MultiCurve <: AbstractGeometry From 7f4ece5f413a40c85246afbb2c7ebfddfaf6cff2 Mon Sep 17 00:00:00 2001 From: Jaakko Ruohio Date: Sun, 19 Jan 2025 11:43:56 +0200 Subject: [PATCH 4/4] Add some tests for curved geometry constructors. Cleanup. --- test/test_geos_types.jl | 51 ++++++++++++++++++++++------------------- 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/test/test_geos_types.jl b/test/test_geos_types.jl index 9652407..60604b6 100644 --- a/test/test_geos_types.jl +++ b/test/test_geos_types.jl @@ -444,35 +444,38 @@ end end @testset "Curved geometry types" begin - geos = [ - readgeom("CIRCULARSTRING(0 0,1 1,2 0)") - readgeom("CIRCULARSTRING (0 0, 1 1, 2 0, 3 -1, 4 0, 2 2, 0 0)") - readgeom("CURVEPOLYGON ((0 0, 2 0, 0 2, 0 0))") - readgeom("CURVEPOLYGON (CIRCULARSTRING (0 0, 1 1, 2 0, 3 -1, 4 0, 2 2, 0 0))") - readgeom("COMPOUNDCURVE (CIRCULARSTRING (0 0, 1 1, 2 0), (2 0, 2 -1, 0 -1, 0 0))") - readgeom( - "CURVEPOLYGON (COMPOUNDCURVE (CIRCULARSTRING (0 0, 1 1, 2 0), (2 0, 2 -1, 0 -1, 0 0)))", - ) - readgeom( - "MULTISURFACE (CURVEPOLYGON (CIRCULARSTRING (0 0, 1 1, 2 0, 3 -1, 4 0, 2 2, 0 0)))", - ) - readgeom("MULTICURVE((1 0, 2 0))") + wkt = [ + "CIRCULARSTRING(0 0,1 1,2 0)" + "CIRCULARSTRING (0 0, 1 1, 2 0, 3 -1, 4 0, 2 2, 0 0)" + "CURVEPOLYGON ((0 0, 2 0, 0 2, 0 0))" + "CURVEPOLYGON (CIRCULARSTRING (0 0, 1 1, 2 0, 3 -1, 4 0, 2 2, 0 0))" + "COMPOUNDCURVE (CIRCULARSTRING (0 0, 1 1, 2 0), (2 0, 2 -1, 0 -1, 0 0))" + "CURVEPOLYGON (COMPOUNDCURVE (CIRCULARSTRING (0 0, 1 1, 2 0), (2 0, 2 -1, 0 -1, 0 0)))" + "MULTISURFACE (CURVEPOLYGON (CIRCULARSTRING (0 0, 1 1, 2 0, 3 -1, 4 0, 2 2, 0 0)))" + "MULTICURVE((1 0, 2 0))" + "CURVEPOLYGON (COMPOUNDCURVE (CIRCULARSTRING (0 0, 2 2, 4 0), (4 0, 4 -1, 0 -1, 0 0)), LINEARRING (1 0, 3 0, 3 1, 1 1, 1 0))" ] - @test all(geomLength.(geos) .≈ [pi, 4pi, 4 + 2 * sqrt(2), 4pi, pi + 4, pi + 4, 4pi, 1]) - @test all(area.(geos) .≈ [0, 0, 2, 2pi, 0, pi / 2 + 2, 2pi, 0]) + geom = readgeom.(wkt) + @test all(geomLength.(geom) .≈ [pi, 4pi, 4 + 2 * sqrt(2), 4pi, pi + 4, pi + 4, 4pi, 1, 2pi + 12]) + @test all(area.(geom) .≈ [0, 0, 2, 2pi, 0, pi / 2 + 2, 2pi, 0, 2pi + 2]) + + cp = geom[5] + @test_broken ngeom(cp) # not defined, not supported in GEOS 3.13.0 - cp = readgeom( - "CURVEPOLYGON (COMPOUNDCURVE (CIRCULARSTRING (0 0, 1 1, 2 0), (2 0, 2 -1, 0 -1, 0 0)))", - ) - @test_broken isequal(cp, CurvePolygon(cp)) + cp = geom[6] + @test_broken isequal(cp, CurvePolygon(cp)) # needs ngeom for compoundcurve - cp = readgeom( - "CURVEPOLYGON (COMPOUNDCURVE (CIRCULARSTRING (0 0, 2 2, 4 0), (4 0, 4 -1, 0 -1, 0 0)), LINEARRING (1 0, 3 0, 3 1, 1 1, 1 0))", - ) - @test area(cp) ≈ 2pi + 2 - @test geomLength(cp) ≈ 2pi + 12 + cp = geom[9] @test length(interiorRings(cp)) == 1 @test typeof(exteriorRing(cp)) == CompoundCurve @test typeof(interiorRings(cp)[1]) == LinearRing + + geom = [ + CircularString([[0.,0], [1,1], [2,0]]) + CircularString([[0.,0], [1,1], [2,0], [3,-1], [4,0], [2,2], [0,0]]) + CurvePolygon([[[0.,0], [2,0], [0,2], [0,0]]]) + CurvePolygon(CircularString([[0.,0], [1,1], [2,0], [3,-1], [4,0], [2,2], [0,0]])) + ] + @test ngeom.(geom) == [3, 7, 1, 1] end