diff --git a/django/contrib/gis/geos/geometry.py b/django/contrib/gis/geos/geometry.py index 51666bc4d4cf5..34b126d777f4b 100644 --- a/django/contrib/gis/geos/geometry.py +++ b/django/contrib/gis/geos/geometry.py @@ -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." @@ -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: @@ -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) diff --git a/django/contrib/gis/geos/prototypes/__init__.py b/django/contrib/gis/geos/prototypes/__init__.py index 2355928774580..23735f5e9b6ad 100644 --- a/django/contrib/gis/geos/prototypes/__init__.py +++ b/django/contrib/gis/geos/prototypes/__init__.py @@ -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, \ diff --git a/django/contrib/gis/geos/prototypes/misc.py b/django/contrib/gis/geos/prototypes/misc.py index 5b3b658fc0124..fd4f78a01175e 100644 --- a/django/contrib/gis/geos/prototypes/misc.py +++ b/django/contrib/gis/geos/prototypes/misc.py @@ -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): """ @@ -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') diff --git a/django/contrib/gis/geos/tests/test_geos.py b/django/contrib/gis/geos/tests/test_geos.py index 3cd021e8b8267..421e8c5f5ec99 100644 --- a/django/contrib/gis/geos/tests/test_geos.py +++ b/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): @@ -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)) diff --git a/docs/ref/contrib/gis/geos.txt b/docs/ref/contrib/gis/geos.txt index 06a88a88ece67..088ea6cdbeef7 100644 --- a/docs/ref/contrib/gis/geos.txt +++ b/docs/ref/contrib/gis/geos.txt @@ -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