Opened 20 months ago

Last modified 3 weeks ago

#34406 closed New feature

Add support for curved geometries in GeoDjango — at Initial Version

Reported by: Fabien Le Frapper Owned by: nobody
Component: GIS Version: dev
Severity: Normal Keywords: geodjango gdal
Cc: Anthony Ricaud, Claude Paroz Triage Stage: Accepted
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

I tried ingesting curved geometries in a GeometryField in GeoDjango.

At first I encountered some errors as these are not officially supported in GeoDjango, but I noticed that gdal is able to go from a geometry to another using converters :

I successfully ingested the following geometries for a project :

  • 9: "CompoundCurve"
  • 10: "CurvePolygon"
  • 11: "MultiCurve"
  • 12: "MultiSurface"

Below is a code snippet I used, subclassing OGRGeometry and OGRGeomType in order to bypass existing GeoDjango validation to add support for more geometries :

Is there a reason why it is not supported at the moment in GeoDjango ?
Would you consider a pull request adding support for these geometries ?

I could suggest a patch based on the snippet above, but I am not sure how to treat the transform part of it.
Should we keep this part ?
It seems that Postgis can handle these polygons https://postgis.net/docs/using_postgis_dbmanagement.html#CircularString


Here is the same snippet but without syntax highlighting

from django.contrib.gis.gdal.geometries import GEO_CLASSES, OGRGeometry
from django.contrib.gis.gdal.geomtype import OGRGeomType
from django.contrib.gis.gdal.libgdal import lgdal
from django.contrib.gis.gdal.prototypes import ds as capi
from django.contrib.gis.gdal.prototypes import geom as geom_api


class ExtendedOGRGeometry(OGRGeometry):
    def __init__(self, geom_input, srs=None):
        try:
            super().__init__(geom_input, srs)
        except KeyError:
            if (
                not isinstance(geom_input, self.ptr_type)
                and self.geom_type.num not in gdal_transform.keys()
            ):
                raise
                
             self.__class__ = EXTENDED_GEO_CLASSES[self.geom_type.num]
            
    @property
    def geom_type(self):
        "Return the Type for this Geometry."
        return ExtendedOGRGeomType(geom_api.get_geom_type(self.ptr))

class CurvePolygon(ExtendedOGRGeometry):
    pass

class CompoundCurve(ExtendedOGRGeometry):
    pass

class MultiSurface(ExtendedOGRGeometry):
    pass

class MultiCurve(ExtendedOGRGeometry):
    pass

EXTENDED_GEO_CLASSES = {
    **GEO_CLASSES,
    9: CompoundCurve,
    10: CurvePolygon,
    11: MultiCurve,
    12: MultiSurface,
}

class ExtendedOGRGeomType(OGRGeomType):
    # Copy paste of original types dictionnary from GeoDjango implementation
    # https://github.com/django/django/blob/main/django/contrib/gis/gdal/geomtype.py#L9
    _types = {
        0: "Unknown",
        1: "Point",
        2: "LineString",
        3: "Polygon",
        4: "MultiPoint",
        5: "MultiLineString",
        6: "MultiPolygon",
        7: "GeometryCollection",
        100: "None",
        101: "LinearRing",
        102: "PointZ",
        1 + OGRGeomType.wkb25bit: "Point25D",
        2 + OGRGeomType.wkb25bit: "LineString25D",
        3 + OGRGeomType.wkb25bit: "Polygon25D",
        4 + OGRGeomType.wkb25bit: "MultiPoint25D",
        5 + OGRGeomType.wkb25bit: "MultiLineString25D",
        6 + OGRGeomType.wkb25bit: "MultiPolygon25D",
        7 + OGRGeomType.wkb25bit: "GeometryCollection25D",
        # Extended geometry types
        9: "CompoundCurve",
        10: "CurvePolygon",
        11: "MultiCurve",
        12: "MultiSurface",
    }
 

# New bindings to existing GDAL methods
force_to_polygon = geom_output(lgdal.OGR_G_ForceToPolygon, [c_void_p])
force_to_multi_polygon = geom_output(lgdal.OGR_G_ForceToMultiPolygon, [c_void_p])
force_to_line = geom_output(lgdal.OGR_G_ForceToLineString, [c_void_p])
force_to_multi_line = geom_output(lgdal.OGR_G_ForceToMultiLineString, [c_void_p])

# The functions below need to be called on the corresponding geometry type 
# before saving it in the database to prevent errors in geodjango. 
gdal_transform = {
    9: force_to_line,
    10: force_to_polygon,
    11: force_to_multi_line,
    12: force_to_multi_polygon,
}

def ingest_curved_geometry(geom_ptr):
    transform = gdal_transform[geom.geom_type.num]
    geom = ExtendedOGRGeometry(transform(geom_api.clone_geom(geom_ptr)))

    # FIXME: for a yet unknown reason, the initial SRID is not kept when using ExtendedOGRGeometry 
    geom.srid = 3857
    geom.transform(4326)

Change History (0)

Note: See TracTickets for help on using tickets.
Back to Top