Commit ffdd6595 authored by Claude Paroz's avatar Claude Paroz
Browse files

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.
parent 82a74dce
Loading
Loading
Loading
Loading
+18 −25
Original line number Diff line number Diff line
@@ -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:
        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_w3d().write_hex(self)
        else:
            return ewkb_w().write_hex(self)
        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:
        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_w3d().write(self)
        else:
            return ewkb_w().write(self)
        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)
+4 −10
Original line number Diff line number Diff line
@@ -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
+22 −7
Original line number Diff line number Diff line
@@ -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,
+2 −3
Original line number Diff line number Diff line
@@ -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.
+14 −6
Original line number Diff line number Diff line
@@ -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::
Loading