Skip to content

Instantly share code, notes, and snippets.

@bpartridge
Created December 19, 2021 04:08
Show Gist options
  • Save bpartridge/26a11b28415d706bfb9993fc28767d68 to your computer and use it in GitHub Desktop.
Save bpartridge/26a11b28415d706bfb9993fc28767d68 to your computer and use it in GitHub Desktop.
Django GEOS patch for macOS arm64
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
@ezheidtmann
Copy link

Thank you! Notes for future readers -- this is now fixed in django>=4.0.1 and the monkey-patch works (for me at least) in django==3.2.13

@runekaagaard
Copy link

This is gold. Can confirm it works on Django==3.2.7.

@klausfrandsen
Copy link

Can confirm that this patch even works on Django 2.0.2, in case anyone is dragging their feet as much as me :-o

@atstudio
Copy link

atstudio commented Jul 31, 2023

Thanks you so much!
works pretty good!
👍
Django==3.2.20

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment