Created
December 19, 2021 04:08
-
-
Save bpartridge/26a11b28415d706bfb9993fc28767d68 to your computer and use it in GitHub Desktop.
Django GEOS patch for macOS arm64
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
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
This is gold. Can confirm it works on Django==3.2.7
.
Can confirm that this patch even works on Django 2.0.2, in case anyone is dragging their feet as much as me :-o
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
And another