Implementing GeoInterface

GeoInterface requires six functions to be defined for a custom geometry. On top of that it could be useful to also implement some optional methods if they apply or are faster than the Fallbacks.

If your package also supports geospatial operations on geometries–such as intersections–, please also implement those interfaces where applicable.

Last but not least, we also provide an interface for rasters and features–geometries with properties–if applicable.

Required for Geometry

GeoInterface.isgeometry(geom::customgeom)::Bool = true
GeoInterface.geomtrait(geom::customgeom)::DataType = XTrait() # <: AbstractGeometryTrait
# for PointTraits
GeoInterface.ncoord(geomtrait(geom), geom::customgeom)::Integer
GeoInterface.getcoord(geomtrait(geom), geom::customgeom, i)::Real
# for non PointTraits
GeoInterface.ngeom(geomtrait(geom), geom::customgeom)::Integer
GeoInterface.getgeom(geomtrait(geom), geom::customgeom, i)

Where the getgeom and getcoord could be an iterator (without the i) as well. It will return a new geom with the correct geomtrait. This means that a call to getgeom on a geometry that has a LineStringTrait should return something that implements the PointTrait. This hierarchy can be checked programmatically with subtrait. You read more about the geomtrait in the Type hierarchy.

The ngeom and getgeom are aliases for their geom specific counterparts, such as npoints and getpoint for LineStringTraits.

Optional for Geometry

There are also optional generic methods that could help with locating this geometry.

GeoInterface.crs(trait(geom), geom::customgeom)::GeoFormatTypes.CoordinateReferenceSystem
GeoInterface.extent(trait(geom), geom::customgeom)::Extents.Extent

For extents, Extents.extent(geom::customgeom) is the fallback method for GeoInterface.extent, and can be used instead here for wider interoperability. If neither is defined, GeoInterface.extent will calculate the extent from the points of the geometry.

And lastly, there are many other optional functions for each specific geometry. GeoInterface provides fallback implementations based on the generic functions above, but these are not optimized. These are detailed in Fallbacks.

Conversion

It is useful if others can convert any custom geometry into your geometry type, if their custom geometry supports GeoInterface as well. This requires the following methods, where the implementation should be defined in terms of GeoInterface methods like ngeom, getgeom, or just coordinates calls.

# This method will get called on GeoInterface.convert(::Type{T}, geom)
# if geomtrait(geom) == LineStringTrait()
GeoInterface.convert(::Type{CustomLineString}, ::LineStringTrait, geom) = ...
GeoInterface.convert(::Type{CustomPolygon}, ::PolygonTrait, geom) = ...

Required for Rasters

A Raster is something that is an AbstractArray

GeoInterface.israster(raster::customraster)::Bool = true
GeoInterface.trait(::customraster) = RasterTrait()
GeoInterface.extent(::RasterTrait, raster::customraster)::Extents.Extent
GeoInterface.crs(::RasterTrait, raster::customraster)::GeoFormatTypes.CoordinateReferenceSystem

Required for Feature(Collection)s

A Feature is a geometry with properties, and in modern parlance, a row in table. A FeatureCollection is thus a Vector of Features, often represented as a table.

A Feature implements the following:

GeoInterface.isfeature(feat::customfeat)::Bool = true
GeoInterface.properties(feat::customfeat)
GeoInterface.geometry(feat::customfeat)

While a FeatureCollection implements the following:

GeoInterface.isfeaturecollection(::Type{customcollection}) = true
GeoInterface.getfeature(trait(::customcollection), ::customcollection, i)
GeoInterface.nfeature(trait(::customcollection), ::customcollection)
GeoInterface.geometrycolumns(::customcollection) = (:geometry,)  # can be multiple!

The geometrycolumns enables other packages to know which field in a row, or column in a table, contains the geometry or geometries.

It's important to note that the geometrycolumns should always return a Tuple of Symbols. However, it does have a fallback method that uses DataAPI.jl metadata, if it exists, to retrieve the geometry columns. This relies on the "GEOINTERFACE:geometrycolumns" metadata key. GeoInterface.jl compatible writers may set this metadata key if writing to a format that does not have its own mechanism to store known geometry columns, like Arrow.

Optionally, the crs method can also be implemented:

GeoInterface.crs(::customcollection)

This should return a GeoFormatTypes.CoordinateReferenceSystem type, such as EPSG(code::Int), WellKnownText(GeoFormatTypes.CRS(), wkt::String), or ProjString(p4::String). See GeoFormatTypes.jl for more information.

The crs method also has a fallback that uses DataAPI.jl metadata, if it exists, to retrieve the CRS. GeoInterface searches for the "GEOINTERFACE:crs" metadata key to retrieve the CRS.

Geospatial Operations

distance(geomtrait(a), geomtrait(b), a, b)
buffer(geomtrait(geom), geom, distance)
convexhull(geomtrait(geom), geom)

Geospatial Relations

These functions are used to describe the relations between geometries as defined in the Dimensionally Extended 9-Intersection Model (DE-9IM).

equals(geomtrait(a), geomtrait(b), a, b)
disjoint(geomtrait(a), geomtrait(b), a, b)
intersects(geomtrait(a), geomtrait(b), a, b)
touches(geomtrait(a), geomtrait(b), a, b)
within(geomtrait(a), geomtrait(b), a, b)
contains(geomtrait(a), geomtrait(b), a, b)
overlaps(geomtrait(a), geomtrait(b), a, b)
crosses(geomtrait(a), geomtrait(b), a, b)
relate(geomtrait(a), geomtrait(b), a, b, relationmatrix)

Geospatial Sets

symdifference(geomtrait(a), geomtrait(b), a, b)
difference(geomtrait(a), geomtrait(b), a, b)
intersection(geomtrait(a), geomtrait(b), a, b)
union(geomtrait(a), geomtrait(b), a, b)

Testing the interface

GeoInterface provides a Testsuite for a geom type to check whether the required functions that have been correctly implemented and work as expected.

GeoInterface.testgeometry(geom)
GeoInterface.testfeature(geom)

Examples

All custom geometries implement

GeoInterface.isgeometry(geom::customgeom)::Bool = true

A geom::customgeom with "Point"-like traits implements

GeoInterface.geomtrait(geom::customgeom)::DataType = PointTrait()
GeoInterface.ncoord(::PointTrait, geom::customgeom)::Integer
GeoInterface.getcoord(::PointTrait, geom::customgeom, i)::Real

# Defaults
GeoInterface.ngeom(::PointTrait, geom)::Integer = 0
GeoInterface.getgeom(::PointTrait, geom::customgeom, i) = nothing

A geom::customgeom with "LineString"-like traits implements the following methods:

GeoInterface.geomtrait(geom::customgeom)::DataType = LineStringTrait()
GeoInterface.ncoord(::LineStringTrait, geom::customgeom)::Integer

# These alias for npoint and getpoint
GeoInterface.ngeom(::LineStringTrait, geom::customgeom)::Integer
GeoInterface.getgeom(::LineStringTrait, geom::customgeom, i) # of geomtrait Point

# Optional
GeoInterface.isclosed(::LineStringTrait, geom::customgeom)::Bool
GeoInterface.issimple(::LineStringTrait, geom::customgeom)::Bool
GeoInterface.length(::LineStringTrait, geom::customgeom)::Real

A geom::customgeom with "Polygon"-like traits can implement the following methods:

GeoInterface.geomtrait(geom::customgeom)::DataType = PolygonTrait()
GeoInterface.ncoord(::PolygonTrait, geom::customgeom)::Integer

# These alias for nring and getring
GeoInterface.ngeom(::PolygonTrait, geom::customgeom)::Integer
GeoInterface.getgeom(::PolygonTrait, geom::customgeom, i)::"LineStringTrait"

# Optional
GeoInterface.area(::PolygonTrait, geom::customgeom)::Real
GeoInterface.centroid(::PolygonTrait, geom::customgeom)::"PointTrait"
GeoInterface.pointonsurface(::PolygonTrait, geom::customgeom)::"PointTrait"
GeoInterface.boundary(::PolygonTrait, geom::customgeom)::"LineStringTrait"

A geom::customgeom with "GeometryCollection"-like traits has to implement the following methods:

GeoInterface.geomtrait(geom::customgeom) = GeometryCollectionTrait()
GeoInterface.ncoord(::GeometryCollectionTrait, geom::customgeom)::Integer
GeoInterface.ngeom(::GeometryCollectionTrait, geom::customgeom)::Integer
GeoInterface.getgeom(::GeometryCollectionTrait,geom::customgeomm, i)::"GeometryTrait"

A geom::customgeom with "MultiPoint"-like traits has to implement the following methods:

GeoInterface.geomtrait(geom::customgeom) = MultiPointTrait()
GeoInterface.ncoord(::MultiPointTrait, geom::customgeom)::Integer

# These alias for npoint and getpoint
GeoInterface.ngeom(::MultiPointTrait, geom::customgeom)::Integer
GeoInterface.getgeom(::MultiPointTrait, geom::customgeom, i)::"PointTrait"

A geom::customgeom with "MultiLineString"-like traits has to implement the following methods:

GeoInterface.geomtrait(geom::customgeom) = MultiLineStringTrait()
GeoInterface.ncoord(::MultiLineStringTrait, geom::customgeom)::Integer

# These alias for nlinestring and getlinestring
GeoInterface.ngeom(::MultiLineStringTrait, geom::customgeom)::Integer
GeoInterface.getgeom(::MultiLineStringTrait,geom::customgeomm, i)::"LineStringTrait"

A geom::customgeom with "MultiPolygon"-like traits has to implement the following methods:

GeoInterface.geomtrait(geom::customgeom) = MultiPolygonTrait()
GeoInterface.ncoord(::MultiPolygonTrait, geom::customgeom)::Integer

# These alias for npolygon and getpolygon
GeoInterface.ngeom(::MultiPolygonTrait, geom::customgeom)::Integer
GeoInterface.getgeom(::MultiPolygonTrait, geom::customgeom, i)::"PolygonTrait"