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.
This commit is contained in:
Claude Paroz 2012-09-22 11:55:37 +02:00
parent 82a74dce24
commit ffdd6595ea
6 changed files with 61 additions and 50 deletions

View File

@ -25,7 +25,7 @@ from django.contrib.gis.geos import prototypes as capi
# These functions provide access to a thread-local instance # These functions provide access to a thread-local instance
# of their corresponding GEOS I/O class. # 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. # For recognizing geometry input.
from django.contrib.gis.geometry.regex import hex_regex, wkt_regex, json_regex from django.contrib.gis.geometry.regex import hex_regex, wkt_regex, json_regex
@ -388,28 +388,24 @@ class GEOSGeometry(GEOSBase, ListMixin):
def hex(self): def hex(self):
""" """
Returns the WKB of this Geometry in hexadecimal form. Please note 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 is not included in this representation because it is not
because it is not a part of the OGC specification (use the `hexewkb` a part of the OGC specification (use the `hexewkb` property instead).
property instead).
""" """
# A possible faster, all-python, implementation: # A possible faster, all-python, implementation:
# str(self.wkb).encode('hex') # str(self.wkb).encode('hex')
return wkb_w().write_hex(self) return wkb_w(self.hasz and 3 or 2).write_hex(self)
@property @property
def hexewkb(self): def hexewkb(self):
""" """
Returns the EWKB of this Geometry in hexadecimal form. This is an 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 SRID value that are
that are a part of this geometry. a part of this geometry.
""" """
if self.hasz: if self.hasz and not GEOS_PREPARE:
if not GEOS_PREPARE: # See: http://trac.osgeo.org/geos/ticket/216
# 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_w(self.hasz and 3 or 2).write_hex(self)
return ewkb_w3d().write_hex(self)
else:
return ewkb_w().write_hex(self)
@property @property
def json(self): def json(self):
@ -429,22 +425,19 @@ class GEOSGeometry(GEOSBase, ListMixin):
as a Python buffer. SRID and Z values are not included, use the as a Python buffer. SRID and Z values are not included, use the
`ewkb` property instead. `ewkb` property instead.
""" """
return wkb_w().write(self) return wkb_w(self.hasz and 3 or 2).write(self)
@property @property
def ewkb(self): def ewkb(self):
""" """
Return the EWKB representation of this Geometry as a Python buffer. Return the EWKB representation of this Geometry as a Python buffer.
This is an extension of the WKB specification that includes any SRID 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 self.hasz and not GEOS_PREPARE:
if not GEOS_PREPARE: # See: http://trac.osgeo.org/geos/ticket/216
# See: http://trac.osgeo.org/geos/ticket/216 raise GEOSException('Upgrade GEOS to 3.1 to get valid 3D EWKB.')
raise GEOSException('Upgrade GEOS to 3.1 to get valid 3D EWKB.') return ewkb_w(self.hasz and 3 or 2).write(self)
return ewkb_w3d().write(self)
else:
return ewkb_w().write(self)
@property @property
def kml(self): def kml(self):
@ -516,7 +509,7 @@ class GEOSGeometry(GEOSBase, ListMixin):
raise GEOSException("GDAL library is not available to transform() geometry.") raise GEOSException("GDAL library is not available to transform() geometry.")
# Creating an OGR Geometry, which is then transformed. # Creating an OGR Geometry, which is then transformed.
g = gdal.OGRGeometry(self.wkb, srid) g = self.ogr
g.transform(ct) g.transform(ct)
# Getting a new GEOS pointer # Getting a new GEOS pointer
ptr = wkb_r().read(g.wkb) ptr = wkb_r().read(g.wkb)

View File

@ -207,7 +207,6 @@ class ThreadLocalIO(threading.local):
wkb_r = None wkb_r = None
wkb_w = None wkb_w = None
ewkb_w = None ewkb_w = None
ewkb_w3d = None
thread_context = ThreadLocalIO() thread_context = ThreadLocalIO()
@ -228,20 +227,15 @@ def wkb_r():
thread_context.wkb_r = _WKBReader() thread_context.wkb_r = _WKBReader()
return thread_context.wkb_r return thread_context.wkb_r
def wkb_w(): def wkb_w(dim=2):
if not thread_context.wkb_w: if not thread_context.wkb_w:
thread_context.wkb_w = WKBWriter() thread_context.wkb_w = WKBWriter()
thread_context.wkb_w.outdim = dim
return thread_context.wkb_w return thread_context.wkb_w
def ewkb_w(): def ewkb_w(dim=2):
if not thread_context.ewkb_w: if not thread_context.ewkb_w:
thread_context.ewkb_w = WKBWriter() thread_context.ewkb_w = WKBWriter()
thread_context.ewkb_w.srid = True thread_context.ewkb_w.srid = True
thread_context.ewkb_w.outdim = dim
return thread_context.ewkb_w 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

View File

@ -92,6 +92,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
"Testing (HEX)EWKB output." "Testing (HEX)EWKB output."
# For testing HEX(EWKB). # For testing HEX(EWKB).
ogc_hex = b'01010000000000000000000000000000000000F03F' ogc_hex = b'01010000000000000000000000000000000000F03F'
ogc_hex_3d = b'01010000800000000000000000000000000000F03F0000000000000040'
# `SELECT ST_AsHEXEWKB(ST_GeomFromText('POINT(0 1)', 4326));` # `SELECT ST_AsHEXEWKB(ST_GeomFromText('POINT(0 1)', 4326));`
hexewkb_2d = b'0101000020E61000000000000000000000000000000000F03F' hexewkb_2d = b'0101000020E61000000000000000000000000000000000F03F'
# `SELECT ST_AsHEXEWKB(ST_GeomFromEWKT('SRID=4326;POINT(0 1 2)'));` # `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_2d = Point(0, 1, srid=4326)
pnt_3d = Point(0, 1, 2, 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_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 # HEXEWKB should be appropriate for its dimension -- have to use an
# a WKBWriter w/dimension set accordingly, else GEOS will insert # a WKBWriter w/dimension set accordingly, else GEOS will insert
@ -830,12 +831,17 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
def test_gdal(self): def test_gdal(self):
"Testing `ogr` and `srs` properties." "Testing `ogr` and `srs` properties."
g1 = fromstr('POINT(5 23)') g1 = fromstr('POINT(5 23)')
self.assertEqual(True, isinstance(g1.ogr, gdal.OGRGeometry)) self.assertIsInstance(g1.ogr, gdal.OGRGeometry)
self.assertEqual(g1.srs, None) 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) g2 = fromstr('LINESTRING(0 0, 5 5, 23 23)', srid=4326)
self.assertEqual(True, isinstance(g2.ogr, gdal.OGRGeometry)) self.assertIsInstance(g2.ogr, gdal.OGRGeometry)
self.assertEqual(True, isinstance(g2.srs, gdal.SpatialReference)) self.assertIsInstance(g2.srs, gdal.SpatialReference)
self.assertEqual(g2.hex, g2.ogr.hex) self.assertEqual(g2.hex, g2.ogr.hex)
self.assertEqual('WGS 84', g2.srs.name) 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, cpy1._ptr)
self.assertNotEqual(poly._ptr, cpy2._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): def test_transform(self):
"Testing `transform` method." "Testing `transform` method."
orig = GEOSGeometry('POINT (-104.609 38.255)', 4326) 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.x, p.x, prec)
self.assertAlmostEqual(trans.y, p.y, 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): def test_transform_noop(self):
""" Testing `transform` method (SRID match) """ """ Testing `transform` method (SRID match) """
# transform() should no-op if source & dest SRIDs match, # transform() should no-op if source & dest SRIDs match,

View File

@ -4,7 +4,7 @@ import os
import re import re
from django.contrib.gis.db.models import Union, Extent3D 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.contrib.gis.utils import LayerMapping, LayerMapError
from django.test import TestCase from django.test import TestCase
@ -67,8 +67,7 @@ class Geo3DTest(TestCase):
# Interstate (2D / 3D and Geographic/Projected variants) # Interstate (2D / 3D and Geographic/Projected variants)
for name, line, exp_z in interstate_data: for name, line, exp_z in interstate_data:
line_3d = GEOSGeometry(line, srid=4269) line_3d = GEOSGeometry(line, srid=4269)
# Using `hex` attribute because it omits 3D. line_2d = LineString([l[:2] for l in line_3d.coords], srid=4269)
line_2d = GEOSGeometry(line_3d.hex, srid=4269)
# Creating a geographic and projected version of the # Creating a geographic and projected version of the
# interstate in both 2D and 3D. # interstate in both 2D and 3D.

View File

@ -273,14 +273,18 @@ Essentially the SRID is prepended to the WKT representation, for example
.. attribute:: GEOSGeometry.hex .. attribute:: GEOSGeometry.hex
Returns the WKB of this Geometry in hexadecimal form. Please note 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 because it is not a part of the OGC specification (use the
:attr:`GEOSGeometry.hexewkb` property instead). :attr:`GEOSGeometry.hexewkb` property instead).
.. versionchanged:: 1.5
Prior to Django 1.5, the Z value of the geometry was dropped.
.. attribute:: GEOSGeometry.hexewkb .. attribute:: GEOSGeometry.hexewkb
Returns the EWKB of this Geometry in hexadecimal form. This is an 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. that are a part of this geometry.
.. note:: .. note::
@ -319,16 +323,20 @@ correspondg to the GEOS geometry.
.. attribute:: GEOSGeometry.wkb .. attribute:: GEOSGeometry.wkb
Returns the WKB (Well-Known Binary) representation of this Geometry 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. :attr:`GEOSGeometry.ewkb` property instead.
.. versionchanged:: 1.5
Prior to Django 1.5, the Z value of the geometry was dropped.
.. _ewkb: .. _ewkb:
.. attribute:: GEOSGeometry.ewkb .. attribute:: GEOSGeometry.ewkb
Return the EWKB representation of this Geometry as a Python buffer. Return the EWKB representation of this Geometry as a Python buffer.
This is an extension of the WKB specification that includes any SRID 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:: .. note::
@ -822,7 +830,7 @@ Writer Objects
All writer objects have a ``write(geom)`` method that returns either the All writer objects have a ``write(geom)`` method that returns either the
WKB or WKT of the given geometry. In addition, :class:`WKBWriter` objects 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 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 .. class:: WKBWriter
@ -884,7 +892,7 @@ so that the Z value is included in the WKB.
Outdim Value Description Outdim Value Description
============ =========================== ============ ===========================
2 The default, output 2D WKB. 2 The default, output 2D WKB.
3 Output 3D EWKB. 3 Output 3D WKB.
============ =========================== ============ ===========================
Example:: Example::

View File

@ -117,6 +117,8 @@ GeoDjango
:meth:`~django.contrib.gis.geos.GEOSGeometry.project()` methods :meth:`~django.contrib.gis.geos.GEOSGeometry.project()` methods
(so-called linear referencing). (so-called linear referencing).
* The wkb and hex properties of `GEOSGeometry` objects preserve the Z dimension.
* Support for GDAL < 1.5 has been dropped. * Support for GDAL < 1.5 has been dropped.
Minor features Minor features