Index: django/contrib/gis/geos/geometry.py
===================================================================
--- django/contrib/gis/geos/geometry.py	(revision 11603)
+++ django/contrib/gis/geos/geometry.py	(working copy)
@@ -375,7 +375,10 @@
         """
         # A possible faster, all-python, implementation:
         #  str(self.wkb).encode('hex')
-        return wkb_w.write_hex(self)
+        if self.hasz:
+            return wkb_w3d.write_hex(self)
+        else:
+            return wkb_w.write_hex(self)
 
     @property
     def json(self):
@@ -392,7 +395,10 @@
     @property
     def wkb(self):
         "Returns the WKB of the Geometry as a buffer."
-        return wkb_w.write(self)
+        if self.hasz:
+            return wkb_w3d.write(self)
+        else:
+            return wkb_w.write(self)
 
     @property
     def kml(self):
@@ -617,7 +623,7 @@
                 }
 
 # Similarly, import the GEOS I/O instances here to avoid conflicts.
-from django.contrib.gis.geos.io import wkt_r, wkt_w, wkb_r, wkb_w
+from django.contrib.gis.geos.io import wkt_r, wkt_w, wkb_r, wkb_w, wkb_w3d
 
 # If supported, import the PreparedGeometry class.
 if GEOS_PREPARE:
Index: django/contrib/gis/geos/prototypes/geom.py
===================================================================
--- django/contrib/gis/geos/prototypes/geom.py	(revision 11603)
+++ django/contrib/gis/geos/prototypes/geom.py	(working copy)
@@ -62,17 +62,16 @@
 
 ### ctypes prototypes ###
 
-# Deprecated creation routines from WKB, HEX, WKT
+# Deprecated creation and output routines from WKB, HEX, WKT
 from_hex = bin_constructor(lgeos.GEOSGeomFromHEX_buf)
 from_wkb = bin_constructor(lgeos.GEOSGeomFromWKB_buf)
 from_wkt = geom_output(lgeos.GEOSGeomFromWKT, [c_char_p])
 
-# Output routines
 to_hex = bin_output(lgeos.GEOSGeomToHEX_buf)
 to_wkb = bin_output(lgeos.GEOSGeomToWKB_buf)
 to_wkt = string_from_geom(lgeos.GEOSGeomToWKT)
 
-# The GEOS geometry type, typeid, num_coordites and number of geometries
+# The GEOS geometry type, typeid, num_coordinates and number of geometries
 geos_normalize = int_from_geom(lgeos.GEOSNormalize)
 geos_type = string_from_geom(lgeos.GEOSGeomType)
 geos_typeid = int_from_geom(lgeos.GEOSGeomTypeId)
Index: django/contrib/gis/geos/io.py
===================================================================
--- django/contrib/gis/geos/io.py	(revision 11603)
+++ django/contrib/gis/geos/io.py	(working copy)
@@ -14,19 +14,19 @@
     "Base class for GEOS I/O objects."
     def __init__(self):
         # Getting the pointer with the constructor.
-        self.ptr = self.constructor()
+        self.ptr = self._constructor()
 
     def __del__(self):
         # Cleaning up with the appropriate destructor.
-        if self._ptr: self.destructor(self._ptr)
+        if self._ptr: self._destructor(self._ptr)
 
 ### WKT Reading and Writing objects ###
 
 # Non-public class for internal use because its `read` method returns
 # _pointers_ instead of a GEOSGeometry object.
 class _WKTReader(IOBase):
-    constructor = capi.wkt_reader_create
-    destructor = capi.wkt_reader_destroy
+    _constructor = capi.wkt_reader_create
+    _destructor = capi.wkt_reader_destroy
     ptr_type = capi.WKT_READ_PTR
 
     def read(self, wkt):
@@ -39,8 +39,8 @@
         return GEOSGeometry(super(WKTReader, self).read(wkt))
 
 class WKTWriter(IOBase):
-    constructor = capi.wkt_writer_create
-    destructor = capi.wkt_writer_destroy
+    _constructor = capi.wkt_writer_create
+    _destructor = capi.wkt_writer_destroy
     ptr_type = capi.WKT_WRITE_PTR
 
     def write(self, geom):
@@ -51,8 +51,8 @@
 
 # Non-public class for the same reason as _WKTReader above.
 class _WKBReader(IOBase):
-    constructor = capi.wkb_reader_create
-    destructor = capi.wkb_reader_destroy
+    _constructor = capi.wkb_reader_create
+    _destructor = capi.wkb_reader_destroy
     ptr_type = capi.WKB_READ_PTR
 
     def read(self, wkb):
@@ -71,8 +71,8 @@
         return GEOSGeometry(super(WKBReader, self).read(wkb))
 
 class WKBWriter(IOBase):
-    constructor = capi.wkb_writer_create
-    destructor = capi.wkb_writer_destroy
+    _constructor = capi.wkb_writer_create
+    _destructor = capi.wkb_writer_destroy
     ptr_type = capi.WKB_WRITE_PTR
 
     def write(self, geom):
@@ -121,3 +121,5 @@
 wkt_w = WKTWriter()
 wkb_r = _WKBReader()
 wkb_w = WKBWriter()
+wkb_w3d = WKBWriter()
+wkb_w3d.outdim = 3
Index: django/contrib/gis/utils/layermapping.py
===================================================================
--- django/contrib/gis/utils/layermapping.py	(revision 11603)
+++ django/contrib/gis/utils/layermapping.py	(working copy)
@@ -493,7 +493,16 @@
         # Transforming the geometry with our Coordinate Transformation object,
         # but only if the class variable `transform` is set w/a CoordTransform 
         # object.
-        if self.transform: g.transform(self.transform)
+        if self.transform: 
+            # Have to get the coordinate dimension of the original geometry
+            # and set the transformed geometry's dimension with it afterwards.
+            # This is because of a GDAL bug (in versions prior to 1.7) which 
+            # automatically converts non-point 2D geometries to 3D if they're:
+            # transformed.  See: http://trac.osgeo.org/gdal/changeset/17792
+            orig_dim = g.coord_dim
+            g.transform(self.transform)
+            if g.coord_dim != orig_dim: 
+                g.coord_dim = orig_dim
         
         # Returning the WKT of the geometry.
         return g.wkt
@@ -506,7 +515,8 @@
             # Getting the target spatial reference system
             target_srs = SpatialRefSys.objects.get(srid=self.geo_col.srid).srs
 
-            # Creating the CoordTransform object
+            # Creating the CoordTransform object -- which is much faster than
+            # transforming w/a SpatialReferencce object.
             return CoordTransform(self.source_srs, target_srs)
         except Exception, msg:
             raise LayerMapError('Could not translate between the data source and model geometry: %s' % msg)
Index: django/contrib/gis/tests/geo3d/views.py
===================================================================
--- django/contrib/gis/tests/geo3d/views.py	(revision 0)
+++ django/contrib/gis/tests/geo3d/views.py	(revision 0)
@@ -0,0 +1 @@
+# Create your views here.
Index: django/contrib/gis/tests/geo3d/__init__.py
===================================================================
Index: django/contrib/gis/tests/geo3d/tests.py
===================================================================
--- django/contrib/gis/tests/geo3d/tests.py	(revision 0)
+++ django/contrib/gis/tests/geo3d/tests.py	(revision 0)
@@ -0,0 +1,16 @@
+import unittest
+from django.test import TestCase
+
+from models import City3D
+
+class Geo3DTest(TestCase):
+    def test01_3D(self):
+        "Test the creation of 3D models."
+        City3D.objects.create(name='Pueblo', point='POINT(5 23 17)')
+        cty = City3D.objects.get(name='Pueblo')
+        self.assertEqual(17.0, cty.point.z)
+
+def suite():
+    s = unittest.TestSuite()
+    s.addTest(unittest.makeSuite(Geo3DTest))
+    return s
Index: django/contrib/gis/tests/geo3d/models.py
===================================================================
--- django/contrib/gis/tests/geo3d/models.py	(revision 0)
+++ django/contrib/gis/tests/geo3d/models.py	(revision 0)
@@ -0,0 +1,9 @@
+from django.contrib.gis.db import models
+
+class City3D(models.Model):
+    name = models.CharField(max_length=30)
+    point = models.PointField(dim=3)
+    objects = models.GeoManager()
+
+    def __unicode__(self):
+        return self.name
Index: django/contrib/gis/tests/__init__.py
===================================================================
--- django/contrib/gis/tests/__init__.py	(revision 11603)
+++ django/contrib/gis/tests/__init__.py	(working copy)
@@ -21,7 +21,7 @@
     s.addTest(geos_tests.suite())
 
     # Tests that require use of a spatial database (e.g., creation of models)
-    test_apps = ['geoapp', 'relatedapp']
+    test_apps = ['geoapp', 'relatedapp', 'geo3d']
 
     # Tests that do not require setting up and tearing down a spatial database.
     test_suite_names = [
Index: django/contrib/gis/gdal/prototypes/geom.py
===================================================================
--- django/contrib/gis/gdal/prototypes/geom.py	(revision 11603)
+++ django/contrib/gis/gdal/prototypes/geom.py	(working copy)
@@ -83,7 +83,8 @@
 get_area = double_output(lgdal.OGR_G_GetArea, [c_void_p])
 get_centroid = void_output(lgdal.OGR_G_Centroid, [c_void_p, c_void_p])
 get_dims = int_output(lgdal.OGR_G_GetDimension, [c_void_p])
-get_coord_dims = int_output(lgdal.OGR_G_GetCoordinateDimension, [c_void_p])
+get_coord_dim = int_output(lgdal.OGR_G_GetCoordinateDimension, [c_void_p])
+set_coord_dim = void_output(lgdal.OGR_G_SetCoordinateDimension, [c_void_p, c_int], errcheck=False)
 
 get_geom_count = int_output(lgdal.OGR_G_GetGeometryCount, [c_void_p])
 get_geom_name = const_string_output(lgdal.OGR_G_GetGeometryName, [c_void_p])
Index: django/contrib/gis/gdal/geometries.py
===================================================================
--- django/contrib/gis/gdal/geometries.py	(revision 11603)
+++ django/contrib/gis/gdal/geometries.py	(working copy)
@@ -179,11 +179,18 @@
         "Returns 0 for points, 1 for lines, and 2 for surfaces."
         return capi.get_dims(self.ptr)
 
-    @property
-    def coord_dim(self):
+    def _get_coord_dim(self):
         "Returns the coordinate dimension of the Geometry."
-        return capi.get_coord_dims(self.ptr)
+        return capi.get_coord_dim(self.ptr)
 
+    def _set_coord_dim(self, dim):
+        "Sets the coordinate dimension of this Geometry."
+        if not dim in (2, 3):
+            raise ValueError('Geometry dimension must be either 2 or 3')
+        capi.set_coord_dim(self.ptr, dim)
+
+    coord_dim = property(_get_coord_dim, _set_coord_dim)
+
     @property
     def geom_count(self):
         "The number of elements in this Geometry."
