Skip to content

Instantly share code, notes, and snippets.

@CHANG-CHING-CHUNG
Forked from bpartridge/patch_geos.py
Created February 23, 2022 02:38
Show Gist options
  • Select an option

  • Save CHANG-CHING-CHUNG/7f0df0c872918784e4b2b46672fa13a9 to your computer and use it in GitHub Desktop.

Select an option

Save CHANG-CHING-CHUNG/7f0df0c872918784e4b2b46672fa13a9 to your computer and use it in GitHub Desktop.

Revisions

  1. @bpartridge bpartridge created this gist Dec 19, 2021.
    95 changes: 95 additions & 0 deletions patch_geos.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,95 @@
    def patch_geos_signatures():
    """
    Patch GEOS to function on macOS arm64 and presumably
    other odd architectures by ensuring that call signatures
    are explicit, and that Django 4 bugfixes are backported.
    Should work on Django 2.2+, minimally tested, caveat emptor.
    """
    import logging

    from ctypes import POINTER, c_uint, c_int
    from django.contrib.gis.geos import GeometryCollection, Polygon
    from django.contrib.gis.geos import prototypes as capi
    from django.contrib.gis.geos.prototypes import GEOM_PTR
    from django.contrib.gis.geos.prototypes.geom import GeomOutput
    from django.contrib.gis.geos.libgeos import geos_version, lgeos
    from django.contrib.gis.geos.linestring import LineString

    logger = logging.getLogger("geos_patch")

    _geos_version = geos_version()
    logger.debug("GEOS: %s %s", _geos_version, repr(lgeos))

    # Backport https://code.djangoproject.com/ticket/30274
    def new_linestring_iter(self):
    for i in range(len(self)):
    yield self[i]

    LineString.__iter__ = new_linestring_iter

    # macOS arm64 requires that we have explicit argtypes for cffi calls.
    # Patch in argtypes for `create_polygon` and `create_collection`,
    # and then ensure their prep functions do NOT use byref so that the
    # arrays (`(GEOM_PTR * length)(...)`) auto-convert into `Geometry**`.
    # create_empty_polygon doesn't need to be patched as it takes no args.

    # Geometry*
    # GEOSGeom_createPolygon_r(GEOSContextHandle_t extHandle,
    # Geometry* shell, Geometry** holes, unsigned int nholes)
    capi.create_polygon = GeomOutput(
    "GEOSGeom_createPolygon", argtypes=[GEOM_PTR, POINTER(GEOM_PTR), c_uint]
    )

    # Geometry*
    # GEOSGeom_createCollection_r(GEOSContextHandle_t extHandle,
    # int type, Geometry** geoms, unsigned int ngeoms)
    capi.create_collection = GeomOutput(
    "GEOSGeom_createCollection", argtypes=[c_int, POINTER(GEOM_PTR), c_uint]
    )

    # The below implementations are taken directly from Django 2.2.25 source;
    # the only changes are unwrapping calls to byref().

    def new_create_polygon(self, length, items):
    # Instantiate LinearRing objects if necessary, but don't clone them yet
    # _construct_ring will throw a TypeError if a parameter isn't a valid ring
    # If we cloned the pointers here, we wouldn't be able to clean up
    # in case of error.
    if not length:
    return capi.create_empty_polygon()

    rings = []
    for r in items:
    if isinstance(r, GEOM_PTR):
    rings.append(r)
    else:
    rings.append(self._construct_ring(r))

    shell = self._clone(rings.pop(0))

    n_holes = length - 1
    if n_holes:
    holes = (GEOM_PTR * n_holes)(*[self._clone(r) for r in rings])
    holes_param = holes
    else:
    holes_param = None

    return capi.create_polygon(shell, holes_param, c_uint(n_holes))

    Polygon._create_polygon = new_create_polygon

    # Need to patch to not call byref so that we can cast to a pointer
    def new_create_collection(self, length, items):
    # Creating the geometry pointer array.
    geoms = (GEOM_PTR * length)(
    *[
    # this is a little sloppy, but makes life easier
    # allow GEOSGeometry types (python wrappers) or pointer types
    capi.geom_clone(getattr(g, "ptr", g))
    for g in items
    ]
    )
    return capi.create_collection(c_int(self._typeid), geoms, c_uint(length))

    GeometryCollection._create_collection = new_create_collection