Skip to content

Commit

Permalink
Refs #36036 -- Added support for GEOSHasM.
Browse files Browse the repository at this point in the history
  • Loading branch information
andharris authored and sarahboyce committed Feb 3, 2025
1 parent 198b301 commit 5f30fd2
Show file tree
Hide file tree
Showing 7 changed files with 70 additions and 8 deletions.
9 changes: 8 additions & 1 deletion django/contrib/gis/geos/geometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -252,9 +252,16 @@ def empty(self):

@property
def hasz(self):
"Return whether the geometry has a 3D dimension."
"Return whether the geometry has a Z dimension."
return capi.geos_hasz(self.ptr)

@property
def hasm(self):
"Return whether the geometry has a M dimension."
if geos_version_tuple() < (3, 12):
raise GEOSException("GEOSGeometry.hasm requires GEOS >= 3.12.0.")
return capi.geos_hasm(self.ptr)

@property
def ring(self):
"Return whether or not the geometry is a ring."
Expand Down
1 change: 1 addition & 0 deletions django/contrib/gis/geos/prototypes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
geos_equals,
geos_equalsexact,
geos_equalsidentical,
geos_hasm,
geos_hasz,
geos_intersects,
geos_isclosed,
Expand Down
1 change: 1 addition & 0 deletions django/contrib/gis/geos/prototypes/predicates.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ class BinaryPredicate(UnaryPredicate):

# ## Unary Predicates ##
geos_hasz = UnaryPredicate("GEOSHasZ")
geos_hasm = UnaryPredicate("GEOSHasM")
geos_isclosed = UnaryPredicate("GEOSisClosed")
geos_isempty = UnaryPredicate("GEOSisEmpty")
geos_isring = UnaryPredicate("GEOSisRing")
Expand Down
9 changes: 8 additions & 1 deletion docs/ref/contrib/gis/geos.txt
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,14 @@ Properties

.. attribute:: GEOSGeometry.hasz

Returns a boolean indicating whether the geometry is three-dimensional.
Returns a boolean indicating whether the geometry has the Z dimension.

.. attribute:: GEOSGeometry.hasm

.. versionadded:: 6.0

Returns a boolean indicating whether the geometry has the M dimension.
Requires GEOS 3.12.

.. attribute:: GEOSGeometry.ring

Expand Down
3 changes: 2 additions & 1 deletion docs/releases/6.0.txt
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@ Minor features
:mod:`django.contrib.gis`
~~~~~~~~~~~~~~~~~~~~~~~~~

* ...
* The new :attr:`.GEOSGeometry.hasm` property checks whether the geometry has
the M dimension.

:mod:`django.contrib.messages`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Expand Down
19 changes: 14 additions & 5 deletions tests/gis_tests/gdal_tests/test_geom.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import json
import pickle
from unittest import mock, skipIf

from django.contrib.gis.gdal import (
CoordTransform,
Expand All @@ -10,6 +11,7 @@
)
from django.contrib.gis.gdal.geometries import CircularString, CurvePolygon
from django.contrib.gis.geos import GEOSException
from django.contrib.gis.geos.libgeos import geos_version_tuple
from django.template import Context
from django.template.engine import Engine
from django.test import SimpleTestCase
Expand Down Expand Up @@ -871,12 +873,19 @@ def test_point_m_dimension_types(self):
self.assertEqual(geom.geom_type.name, "PointM")
self.assertEqual(geom.geom_type.num, 2001)

@skipIf(geos_version_tuple() < (3, 12), "GEOS >= 3.12.0 is required")
def test_point_m_dimension_geos(self):
"""GEOSGeometry does not yet support the M dimension."""
geom = OGRGeometry("POINT ZM (1 2 3 4)")
self.assertEqual(geom.geos.wkt, "POINT Z (1 2 3)")
geom = OGRGeometry("POINT M (1 2 3)")
self.assertEqual(geom.geos.wkt, "POINT (1 2)")
geo_zm = OGRGeometry("POINT ZM (1 2 3 4)")
self.assertEqual(geo_zm.geos.wkt, "POINT ZM (1 2 3 4)")
geo_m = OGRGeometry("POINT M (1 2 3)")
self.assertEqual(geo_m.geos.wkt, "POINT M (1 2 3)")

@mock.patch("django.contrib.gis.geos.libgeos.geos_version", lambda: b"3.11.0")
def test_point_m_dimension_geos_version(self):
geo_zm = OGRGeometry("POINT ZM (1 2 3 4)")
self.assertEqual(geo_zm.geos.wkt, "POINT Z (1 2 3)")
geo_m = OGRGeometry("POINT M (1 2 3)")
self.assertEqual(geo_m.geos.wkt, "POINT (1 2)")

def test_centroid(self):
point = OGRGeometry("POINT (1 2 3)")
Expand Down
36 changes: 36 additions & 0 deletions tests/gis_tests/geos_tests/test_geos.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,22 @@ def test_hexewkb(self):
# Redundant sanity check.
self.assertEqual(4326, GEOSGeometry(hexewkb_2d).srid)

@skipIf(geos_version_tuple() < (3, 12), "GEOS >= 3.12.0 is required")
def test_4d_hexewkb(self):
ogc_hex_4d = (
b"01010000C00000000000000000000000000000"
b"F03F00000000000000400000000000000000"
)
hexewkb_4d = (
b"01010000E0E61000000000000000000000000000000000"
b"F03F00000000000000400000000000000000"
)
pnt_4d = Point(0, 1, 2, 0, srid=4326)
self.assertEqual(ogc_hex_4d, pnt_4d.hex)
self.assertEqual(hexewkb_4d, pnt_4d.hexewkb)
self.assertIs(GEOSGeometry(hexewkb_4d).hasm, True)
self.assertEqual(memoryview(a2b_hex(hexewkb_4d)), pnt_4d.ewkb)

def test_kml(self):
"Testing KML output."
for tg in self.geometries.wkt_out:
Expand Down Expand Up @@ -311,6 +327,20 @@ def test_equals_identical_geos_version(self):
with self.assertRaisesMessage(GEOSException, msg):
g1.equals_identical(g2)

@skipIf(geos_version_tuple() < (3, 12), "GEOS >= 3.12.0 is required")
def test_hasm(self):
pnt_xym = fromstr("POINT M (5 23 8)")
self.assertTrue(pnt_xym.hasm)
pnt_xyzm = fromstr("POINT (5 23 8 0)")
self.assertTrue(pnt_xyzm.hasm)

@mock.patch("django.contrib.gis.geos.libgeos.geos_version", lambda: b"3.11.0")
def test_hasm_geos_version(self):
p = fromstr("POINT (1 2 3)")
msg = "GEOSGeometry.hasm requires GEOS >= 3.12.0."
with self.assertRaisesMessage(GEOSException, msg):
p.hasm

def test_points(self):
"Testing Point objects."
prev = fromstr("POINT(0 0)")
Expand Down Expand Up @@ -1255,6 +1285,12 @@ def test_gdal(self):
self.assertEqual(g2.hex, g2.ogr.hex)
self.assertEqual("WGS 84", g2.srs.name)

@skipIf(geos_version_tuple() < (3, 12), "GEOS >= 3.12.0 is required")
def test_gdal_4d(self):
g1_4d = fromstr("POINT(5 23 8 0)")
self.assertIsInstance(g1_4d.ogr, gdal.OGRGeometry)
self.assertEqual(g1_4d.ogr.m, 0)

def test_copy(self):
"Testing use with the Python `copy` module."
import copy
Expand Down

0 comments on commit 5f30fd2

Please sign in to comment.