#34406: Add support for curved geometries in GeoDjango
-------------------------------------+-------------------------------------
     Reporter:  Fabien Le Frapper    |                    Owner:  Fabien Le
                                     |  Frapper
         Type:  New feature          |                   Status:  assigned
    Component:  GIS                  |                  Version:  4.1
     Severity:  Normal               |               Resolution:
     Keywords:  geodjango gdal       |             Triage Stage:  Accepted
    Has patch:  0                    |      Needs documentation:  0
  Needs tests:  0                    |  Patch needs improvement:  0
Easy pickings:  0                    |                    UI/UX:  0
-------------------------------------+-------------------------------------
Description changed by Fabien Le Frapper:

Old 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 :
> - 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 :
> - Link to the snippet with syntax highlighting
> https://gist.github.com/fabienheureux/2dd4dad8f7c4fedef708154eea1470f9
> - 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 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)
> }}}

New 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 :
 - 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 :
 - Link to the snippet with syntax highlighting
 https://gist.github.com/fabienheureux/2dd4dad8f7c4fedef708154eea1470f9
 - 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)
 }}}

--

-- 
Ticket URL: <https://code.djangoproject.com/ticket/34406#comment:5>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

-- 
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/django-updates/01070186d0bc85e4-6be427aa-45ac-49d4-858f-4b4d580ce997-000000%40eu-central-1.amazonses.com.

Reply via email to