Skip to content

Commit

Permalink
Fixed #18919 -- Stopped dropping Z attribute when transforming geomet…
Browse files Browse the repository at this point in the history
…ries

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.
  • Loading branch information
claudep committed Sep 29, 2012
1 parent 82a74dc commit ffdd659
Show file tree
Hide file tree
Showing 6 changed files with 62 additions and 51 deletions.
43 changes: 18 additions & 25 deletions django/contrib/gis/geos/geometry.py
Expand Up @@ -25,7 +25,7 @@

# 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
Expand Down Expand Up @@ -388,28 +388,24 @@ def wkt(self):
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)

This comment has been minimized.

Copy link
@uruz

uruz Oct 4, 2012

Contributor

Style comment: this is better written with 3 if self.hasz else 2


@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.
"""
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)
extension of the WKB specification that includes SRID value that are
a part of this geometry.
"""
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):
Expand All @@ -429,22 +425,19 @@ def wkb(self):
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):
Expand Down Expand Up @@ -516,7 +509,7 @@ def transform(self, ct, clone=False):
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)
Expand Down
14 changes: 4 additions & 10 deletions django/contrib/gis/geos/prototypes/io.py
Expand Up @@ -207,7 +207,6 @@ class ThreadLocalIO(threading.local):
wkb_r = None
wkb_w = None
ewkb_w = None
ewkb_w3d = None

thread_context = ThreadLocalIO()

Expand All @@ -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
29 changes: 22 additions & 7 deletions django/contrib/gis/geos/tests/test_geos.py
Expand Up @@ -92,6 +92,7 @@ def test_hexewkb(self):
"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)'));`
Expand All @@ -100,9 +101,9 @@ def test_hexewkb(self):
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
Expand Down Expand Up @@ -830,12 +831,17 @@ def test_collections_of_collections(self):
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)

Expand All @@ -848,7 +854,7 @@ def test_copy(self):
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)
Expand All @@ -873,6 +879,15 @@ def test_transform(self):
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,
Expand Down
5 changes: 2 additions & 3 deletions django/contrib/gis/tests/geo3d/tests.py
Expand Up @@ -4,7 +4,7 @@
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

Expand Down Expand Up @@ -67,8 +67,7 @@ def _load_interstate_data(self):
# 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.
Expand Down
20 changes: 14 additions & 6 deletions docs/ref/contrib/gis/geos.txt
Expand Up @@ -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::
Expand Down Expand Up @@ -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::

Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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::
Expand Down
2 changes: 2 additions & 0 deletions docs/releases/1.5.txt
Expand Up @@ -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
Expand Down

0 comments on commit ffdd659

Please sign in to comment.