Opened 21 months ago

Last modified 4 weeks ago

#34406 closed New feature

Add support for curved geometries in GeoDjango — at Version 5

Reported by: Fabien Le Frapper Owned by: Fabien Le Frapper
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 (last modified by Fabien Le Frapper)

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 full snippet

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 (5)

comment:1 by Claude Paroz, 21 months ago

At first, I'd say that we are open to extend OGR recognition of those types. However, I wonder if the missing GEOS type counterpart may prevent proper usage of those new types in GeoDjango. At the very least, we can recognize these new types and error out with a proper error message if we don't support them in the framework. Accepting on that base.

comment:2 by Claude Paroz, 21 months ago

Triage Stage: UnreviewedAccepted

in reply to:  1 comment:3 by Fabien Le Frapper, 21 months ago

Thanks for your reply.
Do we have existing geodjango features that are gdal-only ?
Would a gdal-only contribution erroring in case of GEOS be accepted ?
Edit : sorry this is my first time in the Django bug tracker, I just noticed that you updated the ticket status. I will work on a PR then.

Last edited 21 months ago by Fabien Le Frapper (previous) (diff)

comment:4 by Fabien Le Frapper, 21 months ago

Owner: changed from nobody to Fabien Le Frapper
Status: newassigned

comment:5 by Fabien Le Frapper, 21 months ago

Description: modified (diff)
Note: See TracTickets for help on using tickets.
Back to Top