﻿id	summary	reporter	owner	description	type	status	component	version	severity	resolution	keywords	cc	stage	has_patch	needs_docs	needs_tests	needs_better_patch	easy	ui_ux
34406	Add support for curved geometries in GeoDjango	Fabien Le Frapper	David Smith	"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 : 
- Currently supported geometries https://github.com/django/django/blob/main/django/contrib/gis/gdal/geometries.py#L727
- Corresponding geometries in `gdal` https://gdal.org/doxygen/ogr__core_8h.html#a800236a0d460ef66e687b7b65610f12a 

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 : 
- It mainly adds keys for new geometries and classes inheriting for `OGRGeometry` to handle these in GeoDjango
- It shows how we could make these geometries backward compatible (from curved geometries to more standard geometries) using a simple call to existing gdal methods

**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 

{{{#!python
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)
}}}"	New feature	closed	GIS	dev	Normal	fixed	geodjango gdal	Anthony Ricaud Claude Paroz	Accepted	0	0	0	0	0	0
