From 382632a26e0b4446d45f8a43bb0bc01dbc05d798 Mon Sep 17 00:00:00 2001 From: Justin Bronn Date: Thu, 12 Jul 2007 06:05:42 +0000 Subject: [PATCH] gis: no more camelcase module names; GEOSGeometry now autodetects WKT or HEXEWKB (hex). git-svn-id: http://code.djangoproject.com/svn/django/branches/gis@5655 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/gis/geos/__init__.py | 17 +- .../gis/geos/{GEOSGeometry.py => base.py} | 352 +----------------- .../gis/geos/{GEOSCoordSeq.py => coordseq.py} | 2 +- .../gis/geos/{GEOSError.py => error.py} | 0 django/contrib/gis/geos/libgeos.py | 2 +- django/contrib/gis/tests/test_geos.py | 2 +- 6 files changed, 31 insertions(+), 344 deletions(-) rename django/contrib/gis/geos/{GEOSGeometry.py => base.py} (52%) rename django/contrib/gis/geos/{GEOSCoordSeq.py => coordseq.py} (98%) rename django/contrib/gis/geos/{GEOSError.py => error.py} (100%) diff --git a/django/contrib/gis/geos/__init__.py b/django/contrib/gis/geos/__init__.py index d6ba06ca7a..12714ed94a 100644 --- a/django/contrib/gis/geos/__init__.py +++ b/django/contrib/gis/geos/__init__.py @@ -29,22 +29,23 @@ http://zcologia.com/news/429/geometries-for-python-update/ """ -from GEOSGeometry import GEOSGeometry, Point, LineString, LinearRing, HAS_NUMPY -from GEOSError import GEOSException +from base import GEOSGeometry +from geometries import Point, LineString, LinearRing, HAS_NUMPY +from error import GEOSException def hex_to_wkt(hex): "Converts HEXEWKB into WKT." - return GEOSGeometry(hex, 'hex').wkt + return GEOSGeometry(hex).wkt def wkt_to_hex(wkt): "Converts WKT into HEXEWKB." - return GEOSGeometry(wkt, 'wkt').hex + return GEOSGeometry(wkt).hex -def centroid(input, geom_type='hex'): +def centroid(input): "Returns the centroid of the geometry (given in HEXEWKB)." - return GEOSGeometry(input, geom_type).centroid.wkt + return GEOSGeometry(input).centroid.wkt -def area(input, geom_type='hex'): +def area(input): "Returns the area of the geometry (given in HEXEWKB)." - return GEOSGeometry(input, geom_type).area + return GEOSGeometry(input).area diff --git a/django/contrib/gis/geos/GEOSGeometry.py b/django/contrib/gis/geos/base.py similarity index 52% rename from django/contrib/gis/geos/GEOSGeometry.py rename to django/contrib/gis/geos/base.py index 8ee2b566b6..ef4c81118e 100644 --- a/django/contrib/gis/geos/GEOSGeometry.py +++ b/django/contrib/gis/geos/base.py @@ -1,26 +1,31 @@ # Trying not to pollute the namespace. from ctypes import \ byref, string_at, create_string_buffer, pointer, \ - c_char_p, c_double, c_float, c_int, c_uint, c_size_t + c_char_p, c_double, c_int, c_size_t from types import StringType, IntType, FloatType, TupleType, ListType # Getting GEOS-related dependencies. +import re +from warnings import warn from django.contrib.gis.geos.libgeos import lgeos, GEOSPointer, HAS_NUMPY -from django.contrib.gis.geos.GEOSError import GEOSException -from django.contrib.gis.geos.GEOSCoordSeq import GEOSCoordSeq, create_cs +from django.contrib.gis.geos.error import GEOSException, GEOSGeometryIndexError +from django.contrib.gis.geos.coordseq import GEOSCoordSeq, create_cs if HAS_NUMPY: from numpy import ndarray, array +# For recognizing HEXEWKB. +hex_regex = re.compile(r'^[0-9A-Fa-f]+') + class GEOSGeometry(object): "A class that, generally, encapsulates a GEOS geometry." #### Python 'magic' routines #### - def __init__(self, geo_input, input_type='wkt', child=False): + def __init__(self, geo_input, input_type=False, child=False): """The constructor for GEOS geometry objects. May take the following strings as inputs, WKT ("wkt"), HEXEWKB ("hex", PostGIS-specific canonical form). - When a hex string is to be used, the `input_type` keyword should be set with 'hex'. + The `input_type` keyword has been deprecated -- geometry type is now auto-detected. The `child` keyword is for internal use only, and indicates to the garbage collector not to delete this geometry if it was spawned from a parent (e.g., the exterior @@ -31,16 +36,17 @@ class GEOSGeometry(object): self._ptr = GEOSPointer(0) if isinstance(geo_input, StringType): - if input_type == 'wkt': - # If the geometry is in WKT form - g = lgeos.GEOSGeomFromWKT(c_char_p(geo_input)) - elif input_type == 'hex': - # If the geometry is in HEX form. + if input_type: warn('input_type keyword is deprecated') + + if hex_regex.match(geo_input): + # If the regex matches, the geometry is in HEX form. sz = c_size_t(len(geo_input)) buf = create_string_buffer(geo_input) g = lgeos.GEOSGeomFromHEX_buf(buf, sz) else: - raise TypeError, 'GEOS input geometry type "%s" not supported.' % input_type + # Otherwise, the geometry is in WKT form. + g = lgeos.GEOSGeomFromWKT(c_char_p(geo_input)) + elif isinstance(geo_input, (IntType, GEOSPointer)): # When the input is either a raw pointer value (an integer), or a GEOSPointer object. g = geo_input @@ -339,329 +345,9 @@ class GEOSGeometry(object): "Clones this Geometry." return GEOSGeometry(lgeos.GEOSGeom_clone(self._ptr())) -class Point(GEOSGeometry): - - def __init__(self, x, y=None, z=None): - """The Point object may be initialized with either a tuple, or individual - parameters. For example: - >>> p = Point((5, 23)) # 2D point, passed in as a tuple - >>> p = Point(5, 23, 8) # 3D point, passed in with individual parameters - """ - - if isinstance(x, (TupleType, ListType)): - # Here a tuple or list was passed in under the ``x`` parameter. - ndim = len(x) - if ndim < 2 or ndim > 3: - raise TypeError, 'Invalid sequence parameter: %s' % str(x) - coords = x - elif isinstance(x, (IntType, FloatType)) and isinstance(y, (IntType, FloatType)): - # Here X, Y, and (optionally) Z were passed in individually as parameters. - if isinstance(z, (IntType, FloatType)): - ndim = 3 - coords = [x, y, z] - else: - ndim = 2 - coords = [x, y] - else: - raise TypeError, 'Invalid parameters given for Point initialization.' - - # Creating the coordinate sequence - cs = create_cs(c_uint(1), c_uint(ndim)) - - # Setting the X - status = lgeos.GEOSCoordSeq_setX(cs, c_uint(0), c_double(coords[0])) - if not status: raise GEOSException, 'Could not set X during Point initialization.' - - # Setting the Y - status = lgeos.GEOSCoordSeq_setY(cs, c_uint(0), c_double(coords[1])) - if not status: raise GEOSException, 'Could not set Y during Point initialization.' - - # Setting the Z - if ndim == 3: - status = lgeos.GEOSCoordSeq_setZ(cs, c_uint(0), c_double(coords[2])) - - # Initializing from the geometry, and getting a Python object - super(Point, self).__init__(lgeos.GEOSGeom_createPoint(cs)) - - def _getOrdinate(self, dim, idx): - "The coordinate sequence getOrdinate() wrapper." - self._cache_cs() - return self._cs.getOrdinate(dim, idx) - - def _setOrdinate(self, dim, idx, value): - "The coordinate sequence setOrdinate() wrapper." - self._cache_cs() - self._cs.setOrdinate(dim, idx, value) - - def get_x(self): - "Returns the X component of the Point." - return self._getOrdinate(0, 0) - - def set_x(self, value): - "Sets the X component of the Point." - self._setOrdinate(0, 0, value) - - def get_y(self): - "Returns the Y component of the Point." - return self._getOrdinate(1, 0) - - def set_y(self, value): - "Sets the Y component of the Point." - self._setOrdinate(1, 0, value) - - def get_z(self): - "Returns the Z component of the Point." - if self.hasz: - return self._getOrdinate(2, 0) - else: - return None - - def set_z(self, value): - "Sets the Z component of the Point." - if self.hasz: - self._setOrdinate(2, 0, value) - else: - raise GEOSException, 'Cannot set Z on 2D Point.' - - # X, Y, Z properties - x = property(get_x, set_x) - y = property(get_y, set_y) - z = property(get_z, set_z) - - @property - def tuple(self): - "Returns a tuple of the point." - self._cache_cs() - return self._cs.tuple - -class LineString(GEOSGeometry): - - #### Python 'magic' routines #### - def __init__(self, coords, ring=False): - """Initializes on the given sequence, may take lists, tuples, or NumPy arrays - of X,Y pairs.""" - - if isinstance(coords, (TupleType, ListType)): - ncoords = len(coords) - first = True - for coord in coords: - if not isinstance(coord, (TupleType, ListType)): - raise TypeError, 'each coordinate should be a sequence (list or tuple)' - if first: - ndim = len(coord) - self._checkdim(ndim) - first = False - else: - if len(coord) != ndim: raise TypeError, 'Dimension mismatch.' - numpy_coords = False - elif HAS_NUMPY and isinstance(coords, ndarray): - shape = coords.shape - if len(shape) != 2: raise TypeError, 'Too many dimensions.' - self._checkdim(shape[1]) - ncoords = shape[0] - ndim = shape[1] - numpy_coords = True - else: - raise TypeError, 'Invalid initialization input for LineStrings.' - - # Creating the coordinate sequence - cs = GEOSCoordSeq(GEOSPointer(create_cs(c_uint(ncoords), c_uint(ndim)))) - - # Setting each point in the coordinate sequence - for i in xrange(ncoords): - if numpy_coords: cs[i] = coords[i,:] - else: cs[i] = coords[i] - - # Getting the initialization function - if ring: - func = lgeos.GEOSGeom_createLinearRing - else: - func = lgeos.GEOSGeom_createLineString - - # Calling the base geometry initialization with the returned pointer from the function. - super(LineString, self).__init__(func(cs._ptr())) - - def __getitem__(self, index): - "Gets the point at the specified index." - self._cache_cs() - return self._cs[index] - - def __setitem__(self, index, value): - "Sets the point at the specified index, e.g., line_str[0] = (1, 2)." - self._cache_cs() - self._cs[index] = value - - def __iter__(self): - "Allows iteration over this LineString." - for i in xrange(self.__len__()): - yield self.__getitem__(index) - - def __len__(self): - "Returns the number of points in this LineString." - self._cache_cs() - return len(self._cs) - - def _checkdim(self, dim): - if dim not in (2, 3): raise TypeError, 'Dimension mismatch.' - - #### Sequence Properties #### - @property - def tuple(self): - "Returns a tuple version of the geometry from the coordinate sequence." - self._cache_cs() - return self._cs.tuple - - def _listarr(self, func): - """Internal routine that returns a sequence (list) corresponding with - the given function. Will return a numpy array if possible.""" - lst = [func(i) for i in xrange(self.__len__())] # constructing the list, using the function - if HAS_NUMPY: return array(lst) # ARRRR! - else: return lst - - @property - def array(self): - "Returns a numpy array for the LineString." - self._cache_cs() - return self._listarr(self._cs.__getitem__) - - @property - def x(self): - "Returns a list or numpy array of the X variable." - self._cache_cs() - return self._listarr(self._cs.getX) - - @property - def y(self): - "Returns a list or numpy array of the Y variable." - self._cache_cs() - return self._listarr(self._cs.getY) - - @property - def z(self): - "Returns a list or numpy array of the Z variable." - self._cache_cs() - if not self.hasz: return None - else: return self._listarr(self._cs.getZ) - -# LinearRings are LineStrings used within Polygons. -class LinearRing(LineString): - def __init__(self, coords): - "Overriding the initialization function to set the ring keyword." - super(LinearRing, self).__init__(coords, ring=True) - -class Polygon(GEOSGeometry): - - def __del__(self): - "Override the GEOSGeometry delete routine to safely take care of any spawned rings." - # Nullifying the pointers to internal rings, preventing any attempted future access - for k in self._rings: self._rings[k].nullify() - super(Polygon, self).__del__() # Calling the parent __del__() method. - - def __getitem__(self, index): - """Returns the ring at the specified index. The first index, 0, will always - return the exterior ring. Indices > 0 will return the interior ring.""" - if index < 0 or index > self.num_interior_rings: - raise GEOSGeometryIndexError, 'invalid GEOS Geometry index: %s' % str(index) - else: - if index == 0: - return self.exterior_ring - else: - # Getting the interior ring, have to subtract 1 from the index. - return self.get_interior_ring(index-1) - - def __iter__(self): - "Iterates over each ring in the polygon." - for i in xrange(self.__len__()): - yield self.__getitem__(i) - - def __len__(self): - "Returns the number of rings in this Polygon." - return self.num_interior_rings + 1 - - def get_interior_ring(self, ring_i): - """Gets the interior ring at the specified index, - 0 is for the first interior ring, not the exterior ring.""" - - # Making sure the ring index is within range - if ring_i < 0 or ring_i >= self.num_interior_rings: - raise IndexError, 'ring index out of range' - - # Placing the ring in internal rings dictionary. - idx = ring_i+1 # the index for the polygon is +1 because of the exterior ring - if not idx in self._rings: - self._rings[idx] = GEOSPointer(lgeos.GEOSGetInteriorRingN(self._ptr(), c_int(ring_i))) - - # Returning the ring at the given index. - return GEOSGeometry(self._rings[idx], child=True) - - #### Polygon Properties #### - @property - def num_interior_rings(self): - "Returns the number of interior rings." - - # Getting the number of rings - n = lgeos.GEOSGetNumInteriorRings(self._ptr()) - - # -1 indicates an exception occurred - if n == -1: raise GEOSException, 'Error getting the number of interior rings.' - else: return n - - @property - def exterior_ring(self): - "Gets the exterior ring of the Polygon." - # Returns exterior ring - self._rings[0] = GEOSPointer(lgeos.GEOSGetExteriorRing((self._ptr()))) - return GEOSGeometry(self._rings[0], child=True) - - @property - def shell(self): - "Gets the shell (exterior ring) of the Polygon." - return self.exterior_ring - - @property - def tuple(self): - "Gets the tuple for each ring in this Polygon." - return tuple(self.__getitem__(i).tuple for i in xrange(self.__len__())) - -class GeometryCollection(GEOSGeometry): - - def __del__(self): - "Override the GEOSGeometry delete routine to safely take care of any spawned geometries." - # Nullifying the pointers to internal geometries, preventing any attempted future access - for k in self._geoms: self._geoms[k].nullify() - super(GeometryCollection, self).__del__() # Calling the parent __del__() method. - - def __getitem__(self, index): - "For indexing on the multiple geometries." - self._checkindex(index) - - # Setting an entry in the _geoms dictionary for the requested geometry. - if not index in self._geoms: - self._geoms[index] = GEOSPointer(lgeos.GEOSGetGeometryN(self._ptr(), c_int(index))) - - # Cloning the GEOS Geometry first, before returning it. - return GEOSGeometry(self._geoms[index], child=True) - - def __iter__(self): - "For iteration on the multiple geometries." - for i in xrange(self.__len__()): - yield self.__getitem__(i) - - def __len__(self): - "Returns the number of geometries in this collection." - return self.num_geom - - def _checkindex(self, index): - "Checks the given geometry index." - if index < 0 or index >= self.num_geom: - raise GEOSGeometryIndexError, 'invalid GEOS Geometry index: %s' % str(index) - -# MultiPoint, MultiLineString, and MultiPolygon class definitions. -class MultiPoint(GeometryCollection): pass -class MultiLineString(GeometryCollection): pass -class MultiPolygon(GeometryCollection): pass - # Class mapping dictionary +from django.contrib.gis.geos.geometries import Point, Polygon, LineString, LinearRing +from django.contrib.gis.geos.collections import GeometryCollection, MultiPoint, MultiLineString, MultiPolygon GEOS_CLASSES = {'Point' : Point, 'Polygon' : Polygon, 'LineString' : LineString, diff --git a/django/contrib/gis/geos/GEOSCoordSeq.py b/django/contrib/gis/geos/coordseq.py similarity index 98% rename from django/contrib/gis/geos/GEOSCoordSeq.py rename to django/contrib/gis/geos/coordseq.py index faacccc471..eba49f768b 100644 --- a/django/contrib/gis/geos/GEOSCoordSeq.py +++ b/django/contrib/gis/geos/coordseq.py @@ -1,5 +1,5 @@ from django.contrib.gis.geos.libgeos import lgeos -from django.contrib.gis.geos.GEOSError import GEOSException, GEOSGeometryIndexError +from django.contrib.gis.geos.error import GEOSException, GEOSGeometryIndexError from ctypes import c_double, c_int, c_uint, byref """ diff --git a/django/contrib/gis/geos/GEOSError.py b/django/contrib/gis/geos/error.py similarity index 100% rename from django/contrib/gis/geos/GEOSError.py rename to django/contrib/gis/geos/error.py diff --git a/django/contrib/gis/geos/libgeos.py b/django/contrib/gis/geos/libgeos.py index 17c8898cf6..f0d8642e59 100644 --- a/django/contrib/gis/geos/libgeos.py +++ b/django/contrib/gis/geos/libgeos.py @@ -4,7 +4,7 @@ when an error occurs in GEOS). """ -from django.contrib.gis.geos.GEOSError import GEOSException +from django.contrib.gis.geos.error import GEOSException from ctypes import \ c_char_p, c_int, c_size_t, c_ubyte, pointer, addressof, \ CDLL, CFUNCTYPE, POINTER, Structure diff --git a/django/contrib/gis/tests/test_geos.py b/django/contrib/gis/tests/test_geos.py index b356cac208..103c789f55 100644 --- a/django/contrib/gis/tests/test_geos.py +++ b/django/contrib/gis/tests/test_geos.py @@ -24,7 +24,7 @@ class GEOSTest(unittest.TestCase): print "\nBEGIN - expecting GEOS_ERROR; safe to ignore.\n" for err in errors: if err.hex: - self.assertRaises(GEOSException, GEOSGeometry, err.wkt, 'hex') + self.assertRaises(GEOSException, GEOSGeometry, err.wkt) else: self.assertRaises(GEOSException, GEOSGeometry, err.wkt) print "\nEND - expecting GEOS_ERROR; safe to ignore.\n"