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:
parent
82a74dce24
commit
ffdd6595ea
|
@ -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_w3d().write_hex(self)
|
return ewkb_w(self.hasz and 3 or 2).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_w3d().write(self)
|
return ewkb_w(self.hasz and 3 or 2).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)
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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::
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue