Commit f0b7cc4a authored by Justin Bronn's avatar Justin Bronn
Browse files

Cleanup, bug fixes, and new tests for the GEOS I/O objects and `GEOSBase` object.


git-svn-id: http://code.djangoproject.com/svn/django/trunk@10177 bcc190cf-cafb-0310-a4f2-bffc1f526a37
parent faf16098
Loading
Loading
Loading
Loading
+1 −3
Original line number Diff line number Diff line
@@ -41,9 +41,7 @@ class GEOSBase(object):
    def _set_ptr(self, ptr):
        # Only allow the pointer to be set with pointers of the
        # compatible type or None (NULL).
        if isinstance(ptr, int):
            self._ptr = self.ptr_type(ptr)
        elif isinstance(ptr, (self.ptr_type, NoneType)):
        if isinstance(ptr, (self.ptr_type, NoneType)):
            self._ptr = ptr
        else:
            raise TypeError('Incompatible pointer type')
+11 −9
Original line number Diff line number Diff line
@@ -20,7 +20,6 @@ from django.contrib.gis.geos.mutable_list import ListMixin
# prototypes module -- which handles all interaction with
# the underlying GEOS library.
from django.contrib.gis.geos import prototypes as capi
from django.contrib.gis.geos import io

# Regular expression for recognizing HEXEWKB and WKT.  A prophylactic measure
# to prevent potentially malicious input from reaching the underlying C
@@ -62,13 +61,13 @@ class GEOSGeometry(GEOSBase, ListMixin):
            if wkt_m:
                # Handling WKT input.
                if wkt_m.group('srid'): srid = int(wkt_m.group('srid'))
                g = io.wkt_r.read(wkt_m.group('wkt'))
                g = wkt_r.read(wkt_m.group('wkt'))
            elif hex_regex.match(geo_input):
                # Handling HEXEWKB input.
                g = io.wkb_r.read(geo_input)
                g = wkb_r.read(geo_input)
            elif gdal.GEOJSON and gdal.geometries.json_regex.match(geo_input):
                # Handling GeoJSON input.
                g = io.wkb_r.read(gdal.OGRGeometry(geo_input).wkb)
                g = wkb_r.read(gdal.OGRGeometry(geo_input).wkb)
            else:
                raise ValueError('String or unicode input unrecognized as WKT EWKT, and HEXEWKB.')
        elif isinstance(geo_input, GEOM_PTR):
@@ -76,7 +75,7 @@ class GEOSGeometry(GEOSBase, ListMixin):
            g = geo_input
        elif isinstance(geo_input, buffer):
            # When the input is a buffer (WKB).
            g = io.wkb_r.read(geo_input)
            g = wkb_r.read(geo_input)
        elif isinstance(geo_input, GEOSGeometry):
            g = capi.geom_clone(geo_input.ptr)
        else:
@@ -366,7 +365,7 @@ class GEOSGeometry(GEOSBase, ListMixin):
    @property
    def wkt(self):
        "Returns the WKT (Well-Known Text) of the Geometry."
        return io.wkt_w.write(self.ptr)
        return wkt_w.write(self)

    @property
    def hex(self):
@@ -377,7 +376,7 @@ class GEOSGeometry(GEOSBase, ListMixin):
        """
        # A possible faster, all-python, implementation:
        #  str(self.wkb).encode('hex')
        return io.wkb_w.write_hex(self.ptr)
        return wkb_w.write_hex(self)

    @property
    def json(self):
@@ -394,7 +393,7 @@ class GEOSGeometry(GEOSBase, ListMixin):
    @property
    def wkb(self):
        "Returns the WKB of the Geometry as a buffer."
        return io.wkb_w.write(self.ptr)
        return wkb_w.write(self)

    @property
    def kml(self):
@@ -456,7 +455,7 @@ class GEOSGeometry(GEOSBase, ListMixin):
            g = gdal.OGRGeometry(self.wkb, srid)
            g.transform(ct)
            # Getting a new GEOS pointer
            ptr = io.wkb_r.read(g.wkb)
            ptr = wkb_r.read(g.wkb)
            if clone:
                # User wants a cloned transformed geometry returned.
                return GEOSGeometry(ptr, srid=g.srid)
@@ -618,6 +617,9 @@ GEOS_CLASSES = {0 : Point,
                7 : GeometryCollection,
                }

# 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

# If supported, import the PreparedGeometry class.
if GEOS_PREPARE:
    from django.contrib.gis.geos.prepared import PreparedGeometry
+30 −16
Original line number Diff line number Diff line
@@ -6,11 +6,12 @@ reader and writer classes.
from ctypes import byref, c_size_t
from django.contrib.gis.geos.base import GEOSBase
from django.contrib.gis.geos.error import GEOSException
from django.contrib.gis.geos.geometry import GEOSGeometry
from django.contrib.gis.geos.libgeos import GEOM_PTR
from django.contrib.gis.geos.prototypes import io as capi

class IOBase(GEOSBase):
    "Base class for IO objects that that have `destroy` method."
    "Base class for GEOS I/O objects."
    def __init__(self):
        # Getting the pointer with the constructor.
        self.ptr = self.constructor()
@@ -19,36 +20,43 @@ class IOBase(GEOSBase):
        # Cleaning up with the appropriate destructor.
        if self._ptr: self.destructor(self._ptr)

    def _get_geom_ptr(self, geom):
        if hasattr(geom, 'ptr'): geom = geom.ptr
        if not isinstance(geom, GEOM_PTR): raise TypeError
        return geom

### WKT Reading and Writing objects ###
class WKTReader(IOBase):

# 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
    ptr_type = capi.WKT_READ_PTR

    def read(self, wkt, ptr=False):
    def read(self, wkt):
        if not isinstance(wkt, basestring): raise TypeError
        return capi.wkt_reader_read(self.ptr, wkt)

class WKTReader(_WKTReader):
    def read(self, wkt):
        "Returns a GEOSGeometry for the given WKT string."
        return GEOSGeometry(super(WKTReader, self).read(wkt))

class WKTWriter(IOBase):
    constructor = capi.wkt_writer_create
    destructor = capi.wkt_writer_destroy
    ptr_type = capi.WKT_WRITE_PTR

    def write(self, geom):
        return capi.wkt_writer_write(self.ptr, self._get_geom_ptr(geom))
        "Returns the WKT representation of the given geometry."
        return capi.wkt_writer_write(self.ptr, geom.ptr)

### WKB Reading and Writing objects ###
class WKBReader(IOBase):

# Non-public class for the same reason as _WKTReader above.
class _WKBReader(IOBase):
    constructor = capi.wkb_reader_create
    destructor = capi.wkb_reader_destroy
    ptr_type = capi.WKB_READ_PTR

    def read(self, wkb):
        "Returns a _pointer_ to C GEOS Geometry object from the given WKB."
        if isinstance(wkb, buffer):
            wkb_s = str(wkb)
            return capi.wkb_reader_read(self.ptr, wkb_s, len(wkb_s))
@@ -57,16 +65,23 @@ class WKBReader(IOBase):
        else:
            raise TypeError

class WKBReader(_WKBReader):
    def read(self, wkb):
        "Returns a GEOSGeometry for the given WKB buffer."
        return GEOSGeometry(super(WKBReader, self).read(wkb))

class WKBWriter(IOBase):
    constructor = capi.wkb_writer_create
    destructor = capi.wkb_writer_destroy
    ptr_type = capi.WKB_READ_PTR
    ptr_type = capi.WKB_WRITE_PTR

    def write(self, geom):
        return buffer(capi.wkb_writer_write(self.ptr, self._get_geom_ptr(geom), byref(c_size_t())))
        "Returns the WKB representation of the given geometry."
        return buffer(capi.wkb_writer_write(self.ptr, geom.ptr, byref(c_size_t())))

    def write_hex(self, geom):
        return capi.wkb_writer_write_hex(self.ptr, self._get_geom_ptr(geom), byref(c_size_t()))
        "Returns the HEXEWKB representation of the given geometry."
        return capi.wkb_writer_write_hex(self.ptr, geom.ptr, byref(c_size_t()))

    ### WKBWriter Properties ###

@@ -102,8 +117,7 @@ class WKBWriter(IOBase):
    srid = property(_get_include_srid, _set_include_srid)

# Instances of the WKT and WKB reader/writer objects.
wkt_r = WKTReader()
wkt_r = _WKTReader()
wkt_w = WKTWriter()
wkb_r = WKBReader()
wkb_r = _WKBReader()
wkb_w = WKBWriter()
+2 −1
Original line number Diff line number Diff line
@@ -2,10 +2,11 @@
GEOS Testing module.
"""
from unittest import TestSuite, TextTestRunner
import test_geos, test_geos_mutation, test_mutable_list
import test_geos, test_io, test_geos_mutation, test_mutable_list

test_suites = [
    test_geos.suite(),
    test_io.suite(),
    test_geos_mutation.suite(),
    test_mutable_list.suite(),
    ]
+47 −6
Original line number Diff line number Diff line
import random, unittest, sys
from ctypes import ArgumentError
import ctypes, random, unittest, sys
from django.contrib.gis.geos import *
from django.contrib.gis.geos.base import gdal, numpy
from django.contrib.gis.geos.base import gdal, numpy, GEOSBase
from django.contrib.gis.tests.geometries import *

class GEOSTest(unittest.TestCase):
@@ -18,6 +17,48 @@ class GEOSTest(unittest.TestCase):
        else:
            return None

    def test00_base(self):
        "Tests out the GEOSBase class."
        # Testing out GEOSBase class, which provides a `ptr` property
        # that abstracts out access to underlying C pointers.
        class FakeGeom1(GEOSBase):
            pass

        # This one only accepts pointers to floats
        c_float_p = ctypes.POINTER(ctypes.c_float)
        class FakeGeom2(GEOSBase):
            ptr_type = c_float_p

        # Default ptr_type is `c_void_p`.
        fg1 = FakeGeom1()
        # Default ptr_type is C float pointer
        fg2 = FakeGeom2()

        # These assignments are OK -- None is allowed because
        # it's equivalent to the NULL pointer.
        fg1.ptr = ctypes.c_void_p()
        fg1.ptr = None
        fg2.ptr = c_float_p(ctypes.c_float(5.23))
        fg2.ptr = None

        # Because pointers have been set to NULL, an exception should be
        # raised when we try to access it.  Raising an exception is
        # preferrable to a segmentation fault that commonly occurs when
        # a C method is given a NULL memory reference.
        for fg in (fg1, fg2):
            # Equivalent to `fg.ptr`
            self.assertRaises(GEOSException, fg._get_ptr)

        # Anything that is either not None or the acceptable pointer type will
        # result in a TypeError when trying to assign it to the `ptr` property.
        # Thus, memmory addresses (integers) and pointers of the incorrect type
        # (in `bad_ptrs`) will not be allowed.
        bad_ptrs = (5, ctypes.c_char_p('foobar'))
        for bad_ptr in bad_ptrs:
            # Equivalent to `fg.ptr = bad_ptr`
            self.assertRaises(TypeError, fg1._set_ptr, bad_ptr)
            self.assertRaises(TypeError, fg2._set_ptr, bad_ptr)

    def test01a_wkt(self):
        "Testing WKT output."
        for g in wkt_out:
@@ -494,7 +535,7 @@ class GEOSTest(unittest.TestCase):
            exp_buf = fromstr(g_tup[1].wkt)

            # Can't use a floating-point for the number of quadsegs.
            self.assertRaises(ArgumentError, g.buffer, g_tup[2], float(g_tup[3]))
            self.assertRaises(ctypes.ArgumentError, g.buffer, g_tup[2], float(g_tup[3]))

            # Constructing our buffer
            buf = g.buffer(g_tup[2], g_tup[3])
@@ -518,7 +559,7 @@ class GEOSTest(unittest.TestCase):
        self.assertEqual(4326, pnt.srid)
        pnt.srid = 3084
        self.assertEqual(3084, pnt.srid)
        self.assertRaises(ArgumentError, pnt.set_srid, '4326')
        self.assertRaises(ctypes.ArgumentError, pnt.set_srid, '4326')

        # Testing SRID keyword on fromstr(), and on Polygon rings.
        poly = fromstr(polygons[1].wkt, srid=4269)
Loading