diff --git a/django/contrib/gis/geos/geometry.py b/django/contrib/gis/geos/geometry.py
index 4e5409d..fae6a56 100644
--- a/django/contrib/gis/geos/geometry.py
+++ b/django/contrib/gis/geos/geometry.py
@@ -22,7 +22,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
@@ -385,28 +385,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.
-        """
-        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):
@@ -426,22 +422,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):
@@ -513,7 +506,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)
diff --git a/django/contrib/gis/geos/prototypes/io.py b/django/contrib/gis/geos/prototypes/io.py
index 053b994..91afb2a 100644
--- a/django/contrib/gis/geos/prototypes/io.py
+++ b/django/contrib/gis/geos/prototypes/io.py
@@ -204,7 +204,6 @@ class ThreadLocalIO(threading.local):
     wkb_r = None
     wkb_w = None
     ewkb_w = None
-    ewkb_w3d = None
 
 thread_context = ThreadLocalIO()
 
@@ -225,20 +224,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
diff --git a/django/contrib/gis/geos/tests/test_geos.py b/django/contrib/gis/geos/tests/test_geos.py
index 7300ab9..03a407b 100644
--- a/django/contrib/gis/geos/tests/test_geos.py
+++ b/django/contrib/gis/geos/tests/test_geos.py
@@ -88,6 +88,7 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
 
         # For testing HEX(EWKB).
         ogc_hex = '01010000000000000000000000000000000000F03F'
+        ogc_hex_3d = '01010000800000000000000000000000000000F03F0000000000000040'
         # `SELECT ST_AsHEXEWKB(ST_GeomFromText('POINT(0 1)', 4326));`
         hexewkb_2d = '0101000020E61000000000000000000000000000000000F03F'
         # `SELECT ST_AsHEXEWKB(ST_GeomFromEWKT('SRID=4326;POINT(0 1 2)'));`
@@ -96,9 +97,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
@@ -829,12 +830,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)
 
@@ -847,7 +853,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)
@@ -872,6 +878,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,
diff --git a/django/contrib/gis/tests/geo3d/tests.py b/django/contrib/gis/tests/geo3d/tests.py
index 0aba38f..f7590fe 100644
--- a/django/contrib/gis/tests/geo3d/tests.py
+++ b/django/contrib/gis/tests/geo3d/tests.py
@@ -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.
diff --git a/docs/ref/contrib/gis/geos.txt b/docs/ref/contrib/gis/geos.txt
index b569a74..726e6a7 100644
--- a/docs/ref/contrib/gis/geos.txt
+++ b/docs/ref/contrib/gis/geos.txt
@@ -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::
 
@@ -797,7 +805,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
 
@@ -859,7 +867,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::
