From ffdd6595ea2220f8e8a6fb3aacd3213b751d982f Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Sat, 22 Sep 2012 11:55:37 +0200 Subject: [PATCH] Fixed #18919 -- Stopped dropping Z attribute when transforming geometries Previously, the wkb of geometries was dropping the Z attribute. Thanks luizvital for the report and tests and georger.silva@gmail.com for the tests. --- django/contrib/gis/geos/geometry.py | 41 +++++++++------------- django/contrib/gis/geos/prototypes/io.py | 14 +++----- django/contrib/gis/geos/tests/test_geos.py | 29 +++++++++++---- django/contrib/gis/tests/geo3d/tests.py | 5 ++- docs/ref/contrib/gis/geos.txt | 20 +++++++---- docs/releases/1.5.txt | 2 ++ 6 files changed, 61 insertions(+), 50 deletions(-) diff --git a/django/contrib/gis/geos/geometry.py b/django/contrib/gis/geos/geometry.py index 079308bba8b..df396bdbd3a 100644 --- a/django/contrib/gis/geos/geometry.py +++ b/django/contrib/gis/geos/geometry.py @@ -25,7 +25,7 @@ from django.contrib.gis.geos import prototypes as capi # These functions provide access to a thread-local instance # of their corresponding GEOS I/O class. -from django.contrib.gis.geos.prototypes.io import wkt_r, wkt_w, wkb_r, wkb_w, ewkb_w, ewkb_w3d +from django.contrib.gis.geos.prototypes.io import wkt_r, wkt_w, wkb_r, wkb_w, ewkb_w # For recognizing geometry input. from django.contrib.gis.geometry.regex import hex_regex, wkt_regex, json_regex @@ -388,28 +388,24 @@ class GEOSGeometry(GEOSBase, ListMixin): 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` - property instead). + that the SRID is not included in this representation because it is not + a part of the OGC specification (use the `hexewkb` property instead). """ # A possible faster, all-python, implementation: # str(self.wkb).encode('hex') - return wkb_w().write_hex(self) + return wkb_w(self.hasz and 3 or 2).write_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 - that are a part of this geometry. + extension of the WKB specification that includes SRID value 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.') - return ewkb_w3d().write_hex(self) - else: - return ewkb_w().write_hex(self) + if self.hasz and not GEOS_PREPARE: + # See: http://trac.osgeo.org/geos/ticket/216 + raise GEOSException('Upgrade GEOS to 3.1 to get valid 3D HEXEWKB.') + return ewkb_w(self.hasz and 3 or 2).write_hex(self) @property def json(self): @@ -429,22 +425,19 @@ class GEOSGeometry(GEOSBase, ListMixin): as a Python buffer. SRID and Z values are not included, use the `ewkb` property instead. """ - return wkb_w().write(self) + return wkb_w(self.hasz and 3 or 2).write(self) @property def ewkb(self): """ Return the EWKB representation of this Geometry as a Python buffer. This is an extension of the WKB specification that includes any SRID - and Z values that are a part of this geometry. + value 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 EWKB.') - return ewkb_w3d().write(self) - else: - return ewkb_w().write(self) + if self.hasz and not GEOS_PREPARE: + # See: http://trac.osgeo.org/geos/ticket/216 + raise GEOSException('Upgrade GEOS to 3.1 to get valid 3D EWKB.') + return ewkb_w(self.hasz and 3 or 2).write(self) @property def kml(self): @@ -516,7 +509,7 @@ class GEOSGeometry(GEOSBase, ListMixin): raise GEOSException("GDAL library is not available to transform() geometry.") # Creating an OGR Geometry, which is then transformed. - g = gdal.OGRGeometry(self.wkb, srid) + g = self.ogr g.transform(ct) # Getting a new GEOS pointer ptr = wkb_r().read(g.wkb) diff --git a/django/contrib/gis/geos/prototypes/io.py b/django/contrib/gis/geos/prototypes/io.py index 1eeab60a4b3..1be7da88451 100644 --- a/django/contrib/gis/geos/prototypes/io.py +++ b/django/contrib/gis/geos/prototypes/io.py @@ -207,7 +207,6 @@ class ThreadLocalIO(threading.local): wkb_r = None wkb_w = None ewkb_w = None - ewkb_w3d = None thread_context = ThreadLocalIO() @@ -228,20 +227,15 @@ def wkb_r(): thread_context.wkb_r = _WKBReader() return thread_context.wkb_r -def wkb_w(): +def wkb_w(dim=2): if not thread_context.wkb_w: thread_context.wkb_w = WKBWriter() + thread_context.wkb_w.outdim = dim return thread_context.wkb_w -def ewkb_w(): +def ewkb_w(dim=2): if not thread_context.ewkb_w: thread_context.ewkb_w = WKBWriter() thread_context.ewkb_w.srid = True + thread_context.ewkb_w.outdim = dim return thread_context.ewkb_w - -def ewkb_w3d(): - if not thread_context.ewkb_w3d: - thread_context.ewkb_w3d = WKBWriter() - thread_context.ewkb_w3d.srid = True - thread_context.ewkb_w3d.outdim = 3 - return thread_context.ewkb_w3d diff --git a/django/contrib/gis/geos/tests/test_geos.py b/django/contrib/gis/geos/tests/test_geos.py index e10ac809827..cbe51367aea 100644 --- a/django/contrib/gis/geos/tests/test_geos.py +++ b/django/contrib/gis/geos/tests/test_geos.py @@ -92,6 +92,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin): "Testing (HEX)EWKB output." # For testing HEX(EWKB). ogc_hex = b'01010000000000000000000000000000000000F03F' + ogc_hex_3d = b'01010000800000000000000000000000000000F03F0000000000000040' # `SELECT ST_AsHEXEWKB(ST_GeomFromText('POINT(0 1)', 4326));` hexewkb_2d = b'0101000020E61000000000000000000000000000000000F03F' # `SELECT ST_AsHEXEWKB(ST_GeomFromEWKT('SRID=4326;POINT(0 1 2)'));` @@ -100,9 +101,9 @@ class GEOSTest(unittest.TestCase, TestDataMixin): pnt_2d = Point(0, 1, srid=4326) pnt_3d = Point(0, 1, 2, srid=4326) - # OGC-compliant HEX will not have SRID nor Z value. + # OGC-compliant HEX will not have SRID value. self.assertEqual(ogc_hex, pnt_2d.hex) - self.assertEqual(ogc_hex, pnt_3d.hex) + self.assertEqual(ogc_hex_3d, pnt_3d.hex) # HEXEWKB should be appropriate for its dimension -- have to use an # a WKBWriter w/dimension set accordingly, else GEOS will insert @@ -830,12 +831,17 @@ class GEOSTest(unittest.TestCase, TestDataMixin): def test_gdal(self): "Testing `ogr` and `srs` properties." g1 = fromstr('POINT(5 23)') - self.assertEqual(True, isinstance(g1.ogr, gdal.OGRGeometry)) - self.assertEqual(g1.srs, None) + self.assertIsInstance(g1.ogr, gdal.OGRGeometry) + self.assertIsNone(g1.srs) + + if GEOS_PREPARE: + g1_3d = fromstr('POINT(5 23 8)') + self.assertIsInstance(g1_3d.ogr, gdal.OGRGeometry) + self.assertEqual(g1_3d.ogr.z, 8) g2 = fromstr('LINESTRING(0 0, 5 5, 23 23)', srid=4326) - self.assertEqual(True, isinstance(g2.ogr, gdal.OGRGeometry)) - self.assertEqual(True, isinstance(g2.srs, gdal.SpatialReference)) + self.assertIsInstance(g2.ogr, gdal.OGRGeometry) + self.assertIsInstance(g2.srs, gdal.SpatialReference) self.assertEqual(g2.hex, g2.ogr.hex) self.assertEqual('WGS 84', g2.srs.name) @@ -848,7 +854,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin): self.assertNotEqual(poly._ptr, cpy1._ptr) self.assertNotEqual(poly._ptr, cpy2._ptr) - @unittest.skipUnless(gdal.HAS_GDAL, "gdal is required") + @unittest.skipUnless(gdal.HAS_GDAL, "gdal is required to transform geometries") def test_transform(self): "Testing `transform` method." orig = GEOSGeometry('POINT (-104.609 38.255)', 4326) @@ -873,6 +879,15 @@ class GEOSTest(unittest.TestCase, TestDataMixin): self.assertAlmostEqual(trans.x, p.x, prec) self.assertAlmostEqual(trans.y, p.y, prec) + @unittest.skipUnless(gdal.HAS_GDAL, "gdal is required to transform geometries") + def test_transform_3d(self): + p3d = GEOSGeometry('POINT (5 23 100)', 4326) + p3d.transform(2774) + if GEOS_PREPARE: + self.assertEqual(p3d.z, 100) + else: + self.assertIsNone(p3d.z) + def test_transform_noop(self): """ Testing `transform` method (SRID match) """ # transform() should no-op if source & dest SRIDs match, diff --git a/django/contrib/gis/tests/geo3d/tests.py b/django/contrib/gis/tests/geo3d/tests.py index 0aba38f5cab..f7590fe84aa 100644 --- a/django/contrib/gis/tests/geo3d/tests.py +++ b/django/contrib/gis/tests/geo3d/tests.py @@ -4,7 +4,7 @@ import os import re from django.contrib.gis.db.models import Union, Extent3D -from django.contrib.gis.geos import GEOSGeometry, Point, Polygon +from django.contrib.gis.geos import GEOSGeometry, LineString, Point, Polygon from django.contrib.gis.utils import LayerMapping, LayerMapError from django.test import TestCase @@ -67,8 +67,7 @@ class Geo3DTest(TestCase): # Interstate (2D / 3D and Geographic/Projected variants) for name, line, exp_z in interstate_data: line_3d = GEOSGeometry(line, srid=4269) - # Using `hex` attribute because it omits 3D. - line_2d = GEOSGeometry(line_3d.hex, srid=4269) + line_2d = LineString([l[:2] for l in line_3d.coords], srid=4269) # Creating a geographic and projected version of the # interstate in both 2D and 3D. diff --git a/docs/ref/contrib/gis/geos.txt b/docs/ref/contrib/gis/geos.txt index 88883784f90..eb20b1f4115 100644 --- a/docs/ref/contrib/gis/geos.txt +++ b/docs/ref/contrib/gis/geos.txt @@ -273,14 +273,18 @@ Essentially the SRID is prepended to the WKT representation, for example .. attribute:: GEOSGeometry.hex Returns the WKB of this Geometry in hexadecimal form. Please note -that the SRID and Z values are not included in this representation +that the SRID value is not included in this representation because it is not a part of the OGC specification (use the :attr:`GEOSGeometry.hexewkb` property instead). +.. versionchanged:: 1.5 + + Prior to Django 1.5, the Z value of the geometry was dropped. + .. attribute:: GEOSGeometry.hexewkb Returns the EWKB of this Geometry in hexadecimal form. This is an -extension of the WKB specification that includes SRID and Z values +extension of the WKB specification that includes the SRID value that are a part of this geometry. .. note:: @@ -319,16 +323,20 @@ correspondg to the GEOS geometry. .. attribute:: GEOSGeometry.wkb Returns the WKB (Well-Known Binary) representation of this Geometry -as a Python buffer. SRID and Z values are not included, use the +as a Python buffer. SRID value is not included, use the :attr:`GEOSGeometry.ewkb` property instead. +.. versionchanged:: 1.5 + + Prior to Django 1.5, the Z value of the geometry was dropped. + .. _ewkb: .. attribute:: GEOSGeometry.ewkb Return the EWKB representation of this Geometry as a Python buffer. This is an extension of the WKB specification that includes any SRID -and Z values that are a part of this geometry. +value that are a part of this geometry. .. note:: @@ -822,7 +830,7 @@ Writer Objects All writer objects have a ``write(geom)`` method that returns either the WKB or WKT of the given geometry. In addition, :class:`WKBWriter` objects also have properties that may be used to change the byte order, and or -include the SRID and 3D values (in other words, EWKB). +include the SRID value (in other words, EWKB). .. class:: WKBWriter @@ -884,7 +892,7 @@ so that the Z value is included in the WKB. Outdim Value Description ============ =========================== 2 The default, output 2D WKB. -3 Output 3D EWKB. +3 Output 3D WKB. ============ =========================== Example:: diff --git a/docs/releases/1.5.txt b/docs/releases/1.5.txt index 294ceb159ee..b769debb0b8 100644 --- a/docs/releases/1.5.txt +++ b/docs/releases/1.5.txt @@ -117,6 +117,8 @@ GeoDjango :meth:`~django.contrib.gis.geos.GEOSGeometry.project()` methods (so-called linear referencing). +* The wkb and hex properties of `GEOSGeometry` objects preserve the Z dimension. + * Support for GDAL < 1.5 has been dropped. Minor features