Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Geointerface conversions #156

Merged
merged 8 commits into from
Apr 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/LibGEOS.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ using GeoInterfaceRecipes
using Extents
using CEnum

const GI = GeoInterface

export Point,
MultiPoint,
LineString,
Expand Down
125 changes: 103 additions & 22 deletions src/geo_interface.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
GeoInterface.isgeometry(::Type{<:AbstractGeometry}) = true

geointerface_geomtype(::PointTrait) = Point
geointerface_geomtype(::MultiPointTrait) = MultiPoint
geointerface_geomtype(::LineStringTrait) = LineString
geointerface_geomtype(::MultiLineStringTrait) = MultiLineString
geointerface_geomtype(::LinearRingTrait) = LinearRing
geointerface_geomtype(::PolygonTrait) = Polygon
geointerface_geomtype(::MultiPolygonTrait) = MultiPolygon
geointerface_geomtype(::GeometryCollectionTrait) = GeometryCollection

GeoInterface.geomtrait(::Point) = PointTrait()
GeoInterface.geomtrait(::MultiPoint) = MultiPointTrait()
GeoInterface.geomtrait(::LineString) = LineStringTrait()
Expand Down Expand Up @@ -44,6 +53,10 @@ GeoInterface.getgeom(t::AbstractGeometryTrait, geom::PreparedGeometry, i) =
GeoInterface.getgeom(t, geom.ownedby, i)
GeoInterface.getgeom(t::AbstractPointTrait, geom::PreparedGeometry, i) = 0

GeoInterface.x(::AbstractPointTrait, geom::AbstractGeometry) = getX(geom.ptr, 1, get_context(geom))
GeoInterface.y(::AbstractPointTrait, geom::AbstractGeometry) = getY(geom.ptr, 1, get_context(geom))
GeoInterface.z(::AbstractPointTrait, geom::AbstractGeometry) = getZ(geom.ptr, 1, get_context(geom))

GeoInterface.ncoord(::AbstractGeometryTrait, geom::AbstractGeometry) =
isEmpty(geom) ? 0 : getCoordinateDimension(geom)
GeoInterface.getcoord(::AbstractGeometryTrait, geom::AbstractGeometry, i) =
Expand All @@ -60,40 +73,69 @@ function GeoInterface.extent(::AbstractGeometryTrait, geom::AbstractGeometry)
return Extent(X = (getXMin(env), getXMax(env)), Y = (getYMin(env), getYMax(env)))
end

function Base.convert(::Type{T}, geom::T) where {T<:AbstractGeometry}
return geom
end

function Base.convert(::Type{T}, geom::X) where {T<:AbstractGeometry,X}
return Base.convert(T, GeoInterface.geomtrait(geom), geom)
GI.convert(::Type{Point}, ::PointTrait, geom::Point; context=nothing) = geom
function GI.convert(::Type{Point}, ::PointTrait, geom; context=get_global_context())
if GI.is3d(geom)
return Point(GI.x(geom), GI.y(geom), GI.z(geom), context)
else
return Point(GI.x(geom), GI.y(geom), context)
end
end

function Base.convert(::Type{Point}, type::PointTrait, geom)
return Point(GeoInterface.coordinates(geom))
GI.convert(::Type{MultiPoint}, ::MultiPointTrait, geom::MultiPoint; context=nothing) = geom
function GI.convert(::Type{MultiPoint}, t::MultiPointTrait, geom; context=get_global_context())
points = Point[GI.convert(Point, PointTrait(), p) for p in GI.getpoint(t, geom)]
return MultiPoint(points, context)
end
function Base.convert(::Type{MultiPoint}, type::MultiPointTrait, geom)
return MultiPoint(GeoInterface.coordinates(geom))
GI.convert(::Type{LineString}, ::LineStringTrait, geom::LineString; context=nothing) = geom
function GI.convert(::Type{LineString}, ::LineStringTrait, geom; context=get_global_context())
# Faster to make a CoordSeq directly here
seq = _geom_to_coord_seq(geom, context)
return LineString(createLineString(seq, context), context)
end
function Base.convert(::Type{LineString}, type::LineStringTrait, geom)
return LineString(GeoInterface.coordinates(geom))
GI.convert(::Type{LinearRing}, ::LinearRingTrait, geom::LinearRing; context=nothing) = geom
function GI.convert(::Type{LinearRing}, ::LinearRingTrait, geom; context=get_global_context())
# Faster to make a CoordSeq directly here
seq = _geom_to_coord_seq(geom, context)
return LinearRing(createLinearRing(seq, context), context)
end
function Base.convert(::Type{MultiLineString}, type::MultiLineStringTrait, geom)
return MultiLineString(GeoInterface.coordinates(geom))
GI.convert(::Type{MultiLineString}, ::MultiLineStringTrait, geom::MultiLineString; context=nothing) = geom
function GI.convert(::Type{MultiLineString}, ::MultiLineStringTrait, geom; context=get_global_context())
linestrings = LineString[GI.convert(LineString, LineStringTrait(), g; context) for g in getgeom(geom)]
return MultiLineString(linestrings)
end
function Base.convert(::Type{Polygon}, type::PolygonTrait, geom)
return Polygon(GeoInterface.coordinates(geom))
GI.convert(::Type{Polygon}, ::PolygonTrait, geom::Polygon; context=nothing) = geom
function GI.convert(::Type{Polygon}, ::PolygonTrait, geom; context=get_global_context())
exterior = GI.convert(LinearRing, GI.LinearRingTrait(), GI.getexterior(geom); context)
holes = LinearRing[GI.convert(LinearRing, GI.LinearRingTrait(), g; context) for g in GI.gethole(geom)]
return Polygon(exterior, holes)
end
function Base.convert(::Type{MultiPolygon}, type::MultiPolygonTrait, geom)
return MultiPolygon(GeoInterface.coordinates(geom))
GI.convert(::Type{MultiPolygon}, ::MultiPolygonTrait, geom::MultiPolygon; context=nothing) = geom
function GI.convert(::Type{MultiPolygon}, ::MultiPolygonTrait, geom; context=get_global_context())
polygons = Polygon[GI.convert(Polygon, PolygonTrait(), g; context) for g in GI.getgeom(geom)]
return MultiPolygon(polygons)
end

function Base.convert(t::Type{<:AbstractGeometry}, type::AbstractGeometryTrait, geom)
function GI.convert(t::Type{<:AbstractGeometry}, ::AbstractGeometryTrait, geom; context=nothing)
error(
"Cannot convert an object of $(typeof(geom)) with the $(typeof(type)) trait to a $t (yet). Please report an issue.",
"Cannot convert an object of $(of(geom)) with the $(of()) trait to a $t (yet). Please report an issue.",
rafaqz marked this conversation as resolved.
Show resolved Hide resolved
)
return f(GeoInterface.coordinates(geom))
end

function _geom_to_coord_seq(geom, context)
npoint = GI.npoint(geom)
ndim = GI.is3d(geom) ? 3 : 2
seq = createCoordSeq(npoint, context; ndim)
for (i, p) in enumerate(GI.getpoint(geom))
if ndim == 2
setCoordSeq!(seq, i, (GI.x(p), GI.y(p)), context)
else
setCoordSeq!(seq, i, (GI.x(p), GI.y(p), GI.z(p)), context)
end
end
return seq
end


GeoInterface.distance(
::AbstractGeometryTrait,
::AbstractGeometryTrait,
Expand Down Expand Up @@ -180,3 +222,42 @@ GeoInterface.union(
) = union(a, b)

GeoInterfaceRecipes.@enable_geo_plots AbstractGeometry


# -----
# LibGeos operations for any GeoInterface.jl compatible geometries
# -----

# Internal convert method that avoids the overhead of `convert(LibGEOS, geom)`
to_geos(geom) = to_geos(GI.geomtrait(geom), geom)
to_geos(trait, geom) = GI.convert(geointerface_geomtype(trait), trait, geom)

# These methods are all the same with 1 or two geometries, some arguments, and maybe keywords.
# We define them with `@eval` to avoid all the boilerplate code.

buffer(obj, dist::Real, args...; kw...) = buffer(to_geos(obj), dist::Real, args...; kw...)
bufferWithStyle(obj, dist::Real; kw...) = bufferWithStyle(to_geos(obj), dist; kw...)

# 1 geom methods
for f in (
:area, :geomLength, :envelope, :minimumRotatedRectangle, :convexhull, :boundary,
:unaryUnion, :pointOnSurface, :centroid, :node, :simplify, :topologyPreserveSimplify, :uniquePoints,
:delaunayTriangulationEdges, :delaunayTriangulation, :constrainedDelaunayTriangulation,
)
# We convert the geometry to a GEOS geometry and forward it to the geos method
@eval $f(geom, args...; kw...) = $f(to_geos(geom), args...; kw...)
@eval $f(geom::AbstractGeometry, args...; kw...) =
throw(MethodError($f, (geom, args...)))
end

# 2 geom methods
for f in (
:project, :projectNormalized, :intersection, :difference, :symmetricDifference, :union, :sharedPaths,
:snap, :distance, :hausdorffdistance, :nearestPoints, :disjoint, :touches, :intersects, :crosses,
:within, :contains, :overlaps, :equalsexact, :covers, :coveredby, :equals,
)
# We convert the geometries to GEOS geometries and forward them to the geos method
@eval $f(geom1, geom2, args...; kw...) = $f(to_geos(geom1), to_geos(geom2), args...; kw...)
@eval $f(geom1::AbstractGeometry, geom2::AbstractGeometry, args...; kw...) =
throw(MethodError($f, (geom1, geom2, args...)))
end
23 changes: 15 additions & 8 deletions src/geos_functions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -145,10 +145,11 @@ function getDimensions(ptr::GEOSCoordSeq, context::GEOSContext = get_global_cont
end

# convenience functions
# Use Tuple where possible
function setCoordSeq!(
ptr::GEOSCoordSeq,
i::Integer,
coords::Vector{Float64},
coords::Union{Vector{<:Real},Tuple},
context::GEOSContext = get_global_context(),
)
ndim = length(coords)
Expand All @@ -158,9 +159,9 @@ function setCoordSeq!(
"LibGEOS: i=$i is out of bounds for CoordSeq with size=$(getSize(ptr, context))",
)
end
setX!(ptr, i, coords[1], context)
setY!(ptr, i, coords[2], context)
ndim >= 3 && setZ!(ptr, i, coords[3], context)
setX!(ptr, i, Float64(coords[1]), context)
setY!(ptr, i, Float64(coords[2]), context)
ndim >= 3 && setZ!(ptr, i, Float64(coords[3]), context)
ptr
end

Expand Down Expand Up @@ -233,7 +234,7 @@ end

function getX(ptr::GEOSCoordSeq, context::GEOSContext = get_global_context())
ncoords = getSize(ptr, context)
xcoords = Array{Float64}(undef, ncoords)
xcoords = Vector{Float64}(undef, ncoords)
start = pointer(xcoords)
floatsize = sizeof(Float64)
for i = 0:ncoords-1
Expand All @@ -258,7 +259,7 @@ end

function getY(ptr::GEOSCoordSeq, context::GEOSContext = get_global_context())
ncoords = getSize(ptr, context)
ycoords = Array{Float64}(undef, ncoords)
ycoords = Vector{Float64}(undef, ncoords)
start = pointer(ycoords)
floatsize = sizeof(Float64)
for i = 0:ncoords-1
Expand Down Expand Up @@ -777,7 +778,10 @@ function within(obj1::Geometry, obj2::Geometry, context::GEOSContext = get_conte
result != 0x00
end

function Base.contains(obj1::Geometry, obj2::Geometry, context::GEOSContext = get_context(obj1))
Base.contains(obj1::Geometry, obj2::Geometry, context::GEOSContext = get_context(obj1)) =
contains(obj1, obj2, context)

function contains(obj1::Geometry, obj2::Geometry, context::GEOSContext = get_context(obj1))
result = GEOSContains_r(context, obj1, obj2)
if result == 0x02
error("LibGEOS: Error in GEOSContains")
Expand Down Expand Up @@ -842,7 +846,10 @@ function destroyPreparedGeom(obj::PreparedGeometry, context::GEOSContext = get_g
GEOSPreparedGeom_destroy_r(context, obj)
end

function Base.contains(
Base.contains(obj1::PreparedGeometry, obj2::Geometry, context::GEOSContext = get_context(obj1)) =
contains(obj1, obj2, context)

function contains(
obj1::PreparedGeometry,
obj2::Geometry,
context::GEOSContext = get_context(obj1)
Expand Down
23 changes: 21 additions & 2 deletions src/geos_types.jl
Original file line number Diff line number Diff line change
Expand Up @@ -89,12 +89,17 @@ mutable struct LineString <: AbstractGeometry
finalizer(destroyGeom, line)
line
end
#create a linestring from a list of coordinates
# create a linestring from a vector of points
function LineString(coords::Vector{Vector{Float64}}, context::GEOSContext = get_global_context())
line = new(createLineString(coords, context), context)
finalizer(destroyGeom, line)
line
end
function LineString(coords::Vector{Point}, context::GEOSContext = get_global_context())
line = new(createLineString(coords, context), context)
finalizer(destroyGeom, line)
line
end
end

mutable struct MultiLineString <: AbstractGeometry
Expand Down Expand Up @@ -124,6 +129,13 @@ mutable struct MultiLineString <: AbstractGeometry
GEOSGeom[createLineString(coords, context) for coords in multiline],
context),
context)
MultiLineString(multiline::Vector{LineString}, context::GEOSContext = get_global_context()) =
MultiLineString(
createCollection(
GEOS_MULTILINESTRING,
GEOSGeom[ls.ptr for ls in multiline],
context),
context)
end

mutable struct LinearRing <: AbstractGeometry
Expand Down Expand Up @@ -256,6 +268,13 @@ mutable struct GeometryCollection <: AbstractGeometry
collection,
context),
context)
GeometryCollection(collection::Vector{<:AbstractGeometry}, context::GEOSContext = get_global_context()) =
GeometryCollection(
createCollection(
GEOS_GEOMETRYCOLLECTION,
GEOSGeom[geom.ptr for geom in collection],
context),
context)
end

const Geometry = Union{
Expand Down Expand Up @@ -434,7 +453,7 @@ typesalt(::Type{Polygon} ) = 0xa5c895d62ef56723

function Base.hash(geo::AbstractGeometry, h::UInt)::UInt
h = hash(typesalt(typeof(geo)), h)
if has_coord_seq(geo)
if has_coord_seq(geo)
return hash_coord_seq(geo, h)
else
for i in 1:ngeom(geo)
Expand Down
Loading