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
# 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)

View File

@ -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

View File

@ -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,

View File

@ -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.

View File

@ -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::

View File

@ -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