Skip to content

Commit

Permalink
Fixed #14318 -- Added GEOSGeometry.valid_reason property. Thanks, R…
Browse files Browse the repository at this point in the history
…ob Coup.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@14447 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information
jbronn committed Nov 4, 2010
1 parent 877033b commit cabc21c
Show file tree
Hide file tree
Showing 5 changed files with 54 additions and 7 deletions.
17 changes: 13 additions & 4 deletions django/contrib/gis/geos/geometry.py
Expand Up @@ -275,6 +275,15 @@ def valid(self):
"This property tests the validity of this Geometry."
return capi.geos_isvalid(self.ptr)

@property
def valid_reason(self):
"""
Returns a string containing the reason for any invalidity.
"""
if not GEOS_PREPARE:
raise GEOSException('Upgrade GEOS to 3.1 to get validity reason.')
return capi.geos_isvalidreason(self.ptr)

#### Binary predicates. ####
def contains(self, other):
"Returns true if other.within(this) returns true."
Expand Down Expand Up @@ -376,7 +385,7 @@ def hex(self):
"""
Returns the WKB of this Geometry in hexadecimal form. Please note
that the SRID and Z values are not included in this representation
because it is not a part of the OGC specification (use the `hexewkb`
because it is not a part of the OGC specification (use the `hexewkb`
property instead).
"""
# A possible faster, all-python, implementation:
Expand All @@ -386,14 +395,14 @@ def hex(self):
@property
def hexewkb(self):
"""
Returns the EWKB of this Geometry in hexadecimal form. This is an
extension of the WKB specification that includes SRID and Z values
Returns the EWKB of this Geometry in hexadecimal form. This is an
extension of the WKB specification that includes SRID and Z values
that are a part of this geometry.
"""
if self.hasz:
if not GEOS_PREPARE:
# See: http://trac.osgeo.org/geos/ticket/216
raise GEOSException('Upgrade GEOS to 3.1 to get valid 3D HEXEWKB.')
raise GEOSException('Upgrade GEOS to 3.1 to get valid 3D HEXEWKB.')
return ewkb_w3d().write_hex(self)
else:
return ewkb_w().write_hex(self)
Expand Down
2 changes: 1 addition & 1 deletion django/contrib/gis/geos/prototypes/__init__.py
Expand Up @@ -18,7 +18,7 @@
to_hex, to_wkb, to_wkt

# Miscellaneous routines.
from django.contrib.gis.geos.prototypes.misc import geos_area, geos_distance, geos_length
from django.contrib.gis.geos.prototypes.misc import *

# Predicates
from django.contrib.gis.geos.prototypes.predicates import geos_hasz, geos_isempty, \
Expand Down
15 changes: 13 additions & 2 deletions django/contrib/gis/geos/prototypes/misc.py
Expand Up @@ -3,10 +3,13 @@
ones that return the area, distance, and length.
"""
from ctypes import c_int, c_double, POINTER
from django.contrib.gis.geos.libgeos import GEOM_PTR
from django.contrib.gis.geos.prototypes.errcheck import check_dbl
from django.contrib.gis.geos.libgeos import GEOM_PTR, GEOS_PREPARE
from django.contrib.gis.geos.prototypes.errcheck import check_dbl, check_string
from django.contrib.gis.geos.prototypes.geom import geos_char_p
from django.contrib.gis.geos.prototypes.threadsafe import GEOSFunc

__all__ = ['geos_area', 'geos_distance', 'geos_length']

### ctypes generator function ###
def dbl_from_geom(func, num_geom=1):
"""
Expand All @@ -26,3 +29,11 @@ def dbl_from_geom(func, num_geom=1):
geos_area = dbl_from_geom(GEOSFunc('GEOSArea'))
geos_distance = dbl_from_geom(GEOSFunc('GEOSDistance'), num_geom=2)
geos_length = dbl_from_geom(GEOSFunc('GEOSLength'))

# Validity reason; only in GEOS 3.1+
if GEOS_PREPARE:
geos_isvalidreason = GEOSFunc('GEOSisValidReason')
geos_isvalidreason.argtypes = [GEOM_PTR]
geos_isvalidreason.restype = geos_char_p
geos_isvalidreason.errcheck = check_string
__all__.append('geos_isvalidreason')
21 changes: 21 additions & 0 deletions django/contrib/gis/geos/tests/test_geos.py
@@ -1,6 +1,7 @@
import ctypes, random, unittest, sys
from django.contrib.gis.geos import *
from django.contrib.gis.geos.base import gdal, numpy, GEOSBase
from django.contrib.gis.geos.libgeos import GEOS_PREPARE
from django.contrib.gis.geometry.test_data import TestDataMixin

class GEOSTest(unittest.TestCase, TestDataMixin):
Expand Down Expand Up @@ -917,6 +918,26 @@ def test26_line_merge(self):
for geom, merged in zip(ref_geoms, ref_merged):
self.assertEqual(merged, geom.merged)

def test27_valid_reason(self):
"Testing IsValidReason support"
# Skipping tests if GEOS < v3.1.
if not GEOS_PREPARE: return

g = GEOSGeometry("POINT(0 0)")
self.assert_(g.valid)
self.assert_(isinstance(g.valid_reason, basestring))
self.assertEqual(g.valid_reason, "Valid Geometry")

print "\nBEGIN - expecting GEOS_NOTICE; safe to ignore.\n"

g = GEOSGeometry("LINESTRING(0 0, 0 0)")

self.assert_(not g.valid)
self.assert_(isinstance(g.valid_reason, basestring))
self.assertEqual(g.valid_reason, "Too few points in geometry component[0 0]")

print "\nEND - expecting GEOS_NOTICE; safe to ignore.\n"

def suite():
s = unittest.TestSuite()
s.addTest(unittest.makeSuite(GEOSTest))
Expand Down
6 changes: 6 additions & 0 deletions docs/ref/contrib/gis/geos.txt
Expand Up @@ -219,6 +219,12 @@ definition.

Returns a boolean indicating whether the geometry is valid.

.. attribute:: GEOSGeometry.valid_reason

.. versionadded:: 1.3

Returns a string describing the reason why a geometry is invalid.

.. attribute:: GEOSGeometry.srid

Property that may be used to retrieve or set the SRID associated with the
Expand Down

0 comments on commit cabc21c

Please sign in to comment.