diff --git a/django/contrib/gis/gdal/__init__.py b/django/contrib/gis/gdal/__init__.py index 759103282c..cb70199f9a 100644 --- a/django/contrib/gis/gdal/__init__.py +++ b/django/contrib/gis/gdal/__init__.py @@ -1,12 +1,39 @@ +""" + This module houses ctypes interfaces for GDAL objects. The following GDAL + objects are supported: + + CoordTransform: Used for coordinate transformations from one spatial + reference system to another. + + Driver: Wraps an OGR data source driver. + + DataSource: Wrapper for the OGR data source object, supports + OGR-supported data sources. + + Envelope: A ctypes structure for bounding boxes (GDAL library + not required). + + OGRGeometry: Layer for accessing OGR Geometry objects. + + OGRGeomType: A class for representing the different OGR Geometry + types (GDAL library not required). + + SpatialReference: Represents OSR Spatial Reference objects. +""" +# Attempting to import objects that depend on the GDAL library. The +# HAS_GDAL flag will be set to True if the library is present on +# the system. try: from django.contrib.gis.gdal.driver import Driver - from django.contrib.gis.gdal.envelope import Envelope from django.contrib.gis.gdal.datasource import DataSource from django.contrib.gis.gdal.srs import SpatialReference, CoordTransform from django.contrib.gis.gdal.geometries import OGRGeometry - from django.contrib.gis.gdal.geomtype import OGRGeomType - from django.contrib.gis.gdal.error import check_err, OGRException, SRSException HAS_GDAL = True except: HAS_GDAL = False +# The envelope, error, and geomtype modules do not actually require the +# GDAL library. +from django.contrib.gis.gdal.envelope import Envelope +from django.contrib.gis.gdal.error import check_err, OGRException, OGRIndexError, SRSException +from django.contrib.gis.gdal.geomtype import OGRGeomType diff --git a/django/contrib/gis/gdal/datasource.py b/django/contrib/gis/gdal/datasource.py index 1776b34185..cf72098808 100644 --- a/django/contrib/gis/gdal/datasource.py +++ b/django/contrib/gis/gdal/datasource.py @@ -4,7 +4,7 @@ from ctypes import c_char_p, c_int, c_void_p, byref, string_at # The GDAL C library, OGR exceptions, and the Layer object. from django.contrib.gis.gdal.libgdal import lgdal -from django.contrib.gis.gdal.error import OGRException, check_err +from django.contrib.gis.gdal.error import OGRException, OGRIndexError, check_err from django.contrib.gis.gdal.layer import Layer from django.contrib.gis.gdal.driver import Driver @@ -60,7 +60,7 @@ class DataSource(object): # Registering all the drivers, this needs to be done # _before_ we try to open up a data source. if not lgdal.OGRGetDriverCount() and not lgdal.OGRRegisterAll(): - raise OGRException, 'Could not register all the OGR data source drivers!' + raise OGRException('Could not register all the OGR data source drivers!') if isinstance(ds_input, StringType): @@ -72,12 +72,12 @@ class DataSource(object): elif isinstance(ds_input, c_void_p) and isinstance(ds_driver, c_void_p): ds = ds_input else: - raise OGRException, 'Invalid data source input type: %s' % str(type(ds_input)) + raise OGRException('Invalid data source input type: %s' % str(type(ds_input))) # Raise an exception if the returned pointer is NULL if not ds: self._ds = False - raise OGRException, 'Invalid data source file "%s"' % ds_input + raise OGRException('Invalid data source file "%s"' % ds_input) else: self._ds = ds self._driver = Driver(ds_driver) @@ -95,10 +95,10 @@ class DataSource(object): "Allows use of the index [] operator to get a layer at the index." if isinstance(index, StringType): l = lgdal.OGR_DS_GetLayerByName(self._ds, c_char_p(index)) - if not l: raise IndexError, 'invalid OGR Layer name given: "%s"' % index + if not l: raise OGRIndexError('invalid OGR Layer name given: "%s"' % index) else: if index < 0 or index >= self.layer_count: - raise IndexError, 'index out of range' + raise OGRIndexError('index out of range') l = lgdal.OGR_DS_GetLayer(self._ds, c_int(index)) return Layer(l) diff --git a/django/contrib/gis/gdal/driver.py b/django/contrib/gis/gdal/driver.py index 404bf7facf..e56c24917c 100644 --- a/django/contrib/gis/gdal/driver.py +++ b/django/contrib/gis/gdal/driver.py @@ -45,11 +45,11 @@ class Driver(object): elif isinstance(input, c_void_p): dr = input else: - raise OGRException, 'Unrecognized input type for OGR Driver: %s' % str(type(input)) + raise OGRException('Unrecognized input type for OGR Driver: %s' % str(type(input))) # Making sure we get a valid pointer to the OGR Driver if not dr: - raise OGRException, 'Could not initialize OGR Driver on input: %s' % str(input) + raise OGRException('Could not initialize OGR Driver on input: %s' % str(input)) self._dr = dr def __str__(self): @@ -61,7 +61,7 @@ class Driver(object): # Only register all if the driver count is 0 (or else all drivers # will be registered over and over again) if not self.driver_count and not lgdal.OGRRegisterAll(): - raise OGRException, 'Could not register all the OGR data source drivers!' + raise OGRException('Could not register all the OGR data source drivers!') # Driver properties @property diff --git a/django/contrib/gis/gdal/envelope.py b/django/contrib/gis/gdal/envelope.py index 88d9895dbe..ed210d882f 100644 --- a/django/contrib/gis/gdal/envelope.py +++ b/django/contrib/gis/gdal/envelope.py @@ -1,6 +1,3 @@ -from ctypes import Structure, c_double -from types import TupleType - """ The GDAL/OGR library uses an Envelope structure to hold the bounding box information for a geometry. The envelope (bounding box) contains @@ -14,6 +11,9 @@ from types import TupleType Lower left (min_x, min_y) o----------+ """ +from ctypes import Structure, c_double +from types import TupleType, ListType +from django.contrib.gis.gdal.error import OGRException # The OGR definition of an Envelope is a C structure containing four doubles. # See the 'ogr_core.h' source file for more information: @@ -27,25 +27,48 @@ class OGREnvelope(Structure): ] class Envelope(object): - "A class that will wrap an OGR Envelope structure." + """ + The Envelope object is a C structure that contains the minimum and + maximum X, Y coordinates for a rectangle bounding box. The naming + of the variables is compatible with the OGR Envelope structure. + """ def __init__(self, *args): + """ + The initialization function may take an OGREnvelope structure, 4-element + tuple or list, or 4 individual arguments. + """ + if len(args) == 1: if isinstance(args[0], OGREnvelope): # OGREnvelope (a ctypes Structure) was passed in. self._envelope = args[0] - elif isinstance(args[0], TupleType) and len(args[0]) == 4: - # A Tuple was passed in - self._from_tuple(args[0]) + elif isinstance(args[0], (TupleType, ListType)): + # A tuple was passed in. + if len(args[0]) != 4: + raise OGRException('Incorrect number of tuple elements (%d).' % len(args[0])) + else: + self._from_sequence(args[0]) else: - raise OGRException, 'Incorrect type of argument: %s' % str(type(args[0])) + raise TypeError('Incorrect type of argument: %s' % str(type(args[0]))) elif len(args) == 4: - self._from_tuple(args) + # Individiual parameters passed in. + # Thanks to ww for the help + self._from_sequence(map(float, args)) else: - raise OGRException, 'Incorrect number of arguments!' + raise OGRException('Incorrect number (%d) of arguments.' % len(args)) + + # Checking the x,y coordinates + if self.min_x >= self.max_x: + raise OGRException('Envelope minimum X >= maximum X.') + if self.min_y >= self.max_y: + raise OGRException('Envelope minimum Y >= maximum Y.') def __eq__(self, other): - "Returns true if the envelopes are equivalent; can compare against other Envelopes and 4-tuples." + """ + Returns True if the envelopes are equivalent; can compare against + other Envelopes and 4-tuples. + """ if isinstance(other, Envelope): return (self.min_x == other.min_x) and (self.min_y == other.min_y) and \ (self.max_x == other.max_x) and (self.max_y == other.max_y) @@ -53,19 +76,19 @@ class Envelope(object): return (self.min_x == other[0]) and (self.min_y == other[1]) and \ (self.max_x == other[2]) and (self.max_y == other[3]) else: - raise OGRException, 'Equivalence testing only works with other Envelopes.' + raise OGRException('Equivalence testing only works with other Envelopes.') def __str__(self): "Returns a string representation of the tuple." return str(self.tuple) - def _from_tuple(self, tup): - "Initializes the C OGR Envelope structure from the given tuple." + def _from_sequence(self, seq): + "Initializes the C OGR Envelope structure from the given sequence." self._envelope = OGREnvelope() - self._envelope.MinX = tup[0] - self._envelope.MinY = tup[1] - self._envelope.MaxX = tup[2] - self._envelope.MaxY = tup[3] + self._envelope.MinX = seq[0] + self._envelope.MinY = seq[1] + self._envelope.MaxX = seq[2] + self._envelope.MaxY = seq[3] @property def min_x(self): @@ -106,7 +129,7 @@ class Envelope(object): def wkt(self): "Returns WKT representing a Polygon for this envelope." # TODO: Fix significant figures. - return 'POLYGON((%s %s,%s %s,%s %s,%s %s,%s %s))' % (self.min_x, self.min_y, self.min_x, self.max_y, - self.max_x, self.max_y, self.max_x, self.min_y, - self.min_x, self.min_y) - + return 'POLYGON((%s %s,%s %s,%s %s,%s %s,%s %s))' % \ + (self.min_x, self.min_y, self.min_x, self.max_y, + self.max_x, self.max_y, self.max_x, self.min_y, + self.min_x, self.min_y) diff --git a/django/contrib/gis/gdal/feature.py b/django/contrib/gis/gdal/feature.py index a3af85e70b..d83b41bc11 100644 --- a/django/contrib/gis/gdal/feature.py +++ b/django/contrib/gis/gdal/feature.py @@ -7,6 +7,7 @@ from django.contrib.gis.gdal.libgdal import lgdal from django.contrib.gis.gdal.error import OGRException, OGRIndexError from django.contrib.gis.gdal.field import Field from django.contrib.gis.gdal.geometries import OGRGeometry, OGRGeomType +from django.contrib.gis.gdal.srs import SpatialReference # For more information, see the OGR C API source code: # http://www.gdal.org/ogr/ogr__api_8h.html @@ -21,7 +22,7 @@ class Feature(object): self._feat = 0 # Initially NULL self._fdefn = 0 if not f: - raise OGRException, 'Cannot create OGR Feature, invalid pointer given.' + raise OGRException('Cannot create OGR Feature, invalid pointer given.') self._feat = f self._fdefn = lgdal.OGR_F_GetDefnRef(f) @@ -35,7 +36,7 @@ class Feature(object): i = self.index(index) else: if index < 0 or index > self.num_fields: - raise OGRIndexError, 'index out of range' + raise OGRIndexError('index out of range') i = index return Field(lgdal.OGR_F_GetFieldDefnRef(self._feat, c_int(i)), string_at(lgdal.OGR_F_GetFieldAsString(self._feat, c_int(i)))) @@ -84,8 +85,20 @@ class Feature(object): @property def geom(self): "Returns the OGR Geometry for this Feature." - # A clone is used, so destruction of the Geometry won't bork the Feature. - return OGRGeometry(lgdal.OGR_G_Clone(lgdal.OGR_F_GetGeometryRef(self._feat))) + # Retrieving the geometry pointer for the feature. + geom_ptr = lgdal.OGR_F_GetGeometryRef(self._feat) + if not geom_ptr: + raise OGRException('Cannot retrieve Geometry from the feature.') + + # Attempting to retrieve the Spatial Reference for the geometry. + srs_ptr = lgdal.OGR_G_GetSpatialReference(geom_ptr) + if srs_ptr: + srs = SpatialReference(srs_ptr, 'ogr') + else: + srs = None + + # Geometry is cloned so the feature isn't invalidated. + return OGRGeometry(lgdal.OGR_G_Clone(geom_ptr), srs) @property def geom_type(self): @@ -105,7 +118,7 @@ class Feature(object): def index(self, field_name): "Returns the index of the given field name." i = lgdal.OGR_F_GetFieldIndex(self._feat, c_char_p(field_name)) - if i < 0: raise OGRIndexError, 'invalid OFT field name given: "%s"' % field_name + if i < 0: raise OGRIndexError('invalid OFT field name given: "%s"' % field_name) return i def clone(self): diff --git a/django/contrib/gis/gdal/field.py b/django/contrib/gis/gdal/field.py index e2ed6d8297..aa40f03474 100644 --- a/django/contrib/gis/gdal/field.py +++ b/django/contrib/gis/gdal/field.py @@ -1,5 +1,4 @@ from ctypes import string_at - from django.contrib.gis.gdal.libgdal import lgdal from django.contrib.gis.gdal.error import OGRException @@ -16,7 +15,7 @@ class Field(object): self._fld = 0 # Initially NULL if not fld: - raise OGRException, 'Cannot create OGR Field, invalid pointer given.' + raise OGRException('Cannot create OGR Field, invalid pointer given.') self._fld = fld self._val = val @@ -64,7 +63,10 @@ class OFTReal(Field): return None class OFTRealList(Field): pass -class OFTString(Field): pass +class OFTString(Field): + def __str__(self): + return '%s ("%s")' % (self.name, self.value) + class OFTStringList(Field): pass class OFTWideString(Field): pass class OFTWideStringList(Field): pass diff --git a/django/contrib/gis/gdal/geometries.py b/django/contrib/gis/gdal/geometries.py index 42af45bf2d..68789085d6 100644 --- a/django/contrib/gis/gdal/geometries.py +++ b/django/contrib/gis/gdal/geometries.py @@ -1,24 +1,13 @@ -# types & ctypes -from types import IntType, StringType -from ctypes import byref, string_at, c_char_p, c_double, c_int, c_void_p - -# Getting geodjango gdal prerequisites -from django.contrib.gis.gdal.libgdal import lgdal -from django.contrib.gis.gdal.envelope import Envelope, OGREnvelope -from django.contrib.gis.gdal.error import check_err, OGRException, OGRIndexError -from django.contrib.gis.gdal.geomtype import OGRGeomType -from django.contrib.gis.gdal.srs import SpatialReference, CoordTransform - """ The OGRGeometry is a wrapper for using the OGR Geometry class - (see http://www.gdal.org/ogr/classOGRGeometry.html). OGRGeometry - may be instantiated when reading geometries from OGR Data Sources - (e.g. SHP files), or when given OGC WKT (a string). + (see http://www.gdal.org/ogr/classOGRGeometry.html). OGRGeometry + may be instantiated when reading geometries from OGR Data Sources + (e.g. SHP files), or when given OGC WKT (a string). While the 'full' API is not present yet, the API is "pythonic" unlike - the traditional and "next-generation" OGR Python bindings. One major - advantage OGR Geometries have over their GEOS counterparts is support - for spatial reference systems and their transformation. + the traditional and "next-generation" OGR Python bindings. One major + advantage OGR Geometries have over their GEOS counterparts is support + for spatial reference systems and their transformation. Example: >>> from django.contrib.gis.gdal import OGRGeometry, OGRGeomType, SpatialReference @@ -49,6 +38,16 @@ from django.contrib.gis.gdal.srs import SpatialReference, CoordTransform >>> print gt1 == 3, gt1 == 'Polygon' # Equivalence works w/non-OGRGeomType objects True """ +# types & ctypes +from types import IntType, StringType +from ctypes import byref, string_at, c_char_p, c_double, c_int, c_void_p + +# Getting GDAL prerequisites +from django.contrib.gis.gdal.libgdal import lgdal +from django.contrib.gis.gdal.envelope import Envelope, OGREnvelope +from django.contrib.gis.gdal.error import check_err, OGRException, OGRIndexError +from django.contrib.gis.gdal.geomtype import OGRGeomType +from django.contrib.gis.gdal.srs import SpatialReference, CoordTransform # For more information, see the OGR C API source code: # http://www.gdal.org/ogr/ogr__api_8h.html @@ -75,37 +74,45 @@ get_area.argtypes = [c_void_p] class OGRGeometry(object): "Generally encapsulates an OGR geometry." - def __init__(self, input, srs=False): + def __init__(self, geom_input, srs=None): "Initializes Geometry on either WKT or an OGR pointer as input." self._g = 0 # Initially NULL - self._init_srs(srs) - if isinstance(input, StringType): + if isinstance(geom_input, StringType): # First, trying the input as WKT - buf = c_char_p(input) + buf = c_char_p(geom_input) g = c_void_p() try: - check_err(lgdal.OGR_G_CreateFromWkt(byref(buf), self._s._srs, byref(g))) - except OGRException, msg: + check_err(lgdal.OGR_G_CreateFromWkt(byref(buf), c_void_p(), byref(g))) + except OGRException: try: - ogr_t = OGRGeomType(input) # Seeing if the input is a valid short-hand string + # Seeing if the input is a valid short-hand string + ogr_t = OGRGeomType(geom_input) g = lgdal.OGR_G_CreateGeometry(ogr_t.num) except: - raise OGRException, 'Could not initialize on WKT "%s"' % input - elif isinstance(input, OGRGeomType): - g = lgdal.OGR_G_CreateGeometry(input.num) - lgdal.OGR_G_AssignSpatialReference(g, self._s._srs) - elif isinstance(input, IntType): + raise OGRException('Could not initialize OGR Geometry from: %s' % geom_input) + elif isinstance(geom_input, OGRGeomType): + g = lgdal.OGR_G_CreateGeometry(geom_input.num) + elif isinstance(geom_input, IntType): # OGR Pointer (integer) was the input - g = input + g = geom_input else: - raise OGRException, 'Type of input cannot be determined!' + raise OGRException('Type of input cannot be determined!') + + # Assigning the SpatialReference object to the geometry, if valid. + if bool(srs): + if isinstance(srs, SpatialReference): + srs_ptr = srs._srs + else: + sr = SpatialReference(srs) + srs_ptr = sr._srs + lgdal.OGR_G_AssignSpatialReference(g, srs_ptr) # Now checking the Geometry pointer before finishing initialization if not g: - raise OGRException, 'Cannot create OGR Geometry from input: %s' % str(input) + raise OGRException('Cannot create OGR Geometry from input: %s' % str(geom_input)) self._g = g # Setting the class depending upon the OGR Geometry Type @@ -115,13 +122,6 @@ class OGRGeometry(object): "Deletes this Geometry." if self._g: lgdal.OGR_G_DestroyGeometry(self._g) - def _init_srs(self, srs): - # Getting the spatial - if not isinstance(srs, SpatialReference): - self._s = SpatialReference() # creating an empty spatial reference - else: - self._s = srs.clone() # cloning the given spatial reference - ### Geometry set-like operations ### # g = g1 | g2 def __or__(self, other): @@ -184,7 +184,11 @@ class OGRGeometry(object): @property def srs(self): "Returns the Spatial Reference for this Geometry." - return SpatialReference(lgdal.OSRClone(lgdal.OGR_G_GetSpatialReference(self._g)), 'ogr') + srs_ptr = lgdal.OGR_G_GetSpatialReference(self._g) + if srs_ptr: + return SpatialReference(lgdal.OSRClone(srs_ptr), 'ogr') + else: + return None @property def geom_type(self): @@ -196,13 +200,6 @@ class OGRGeometry(object): "Returns the Name of this Geometry." return string_at(lgdal.OGR_G_GetGeometryName(self._g)) - @property - def wkt(self): - "Returns the WKT form of the Geometry." - buf = c_char_p() - check_err(lgdal.OGR_G_ExportToWkt(self._g, byref(buf))) - return string_at(buf) - @property def area(self): "Returns the area for a LinearRing, Polygon, or MultiPolygon; 0 otherwise." @@ -214,6 +211,21 @@ class OGRGeometry(object): env = OGREnvelope() lgdal.OGR_G_GetEnvelope(self._g, byref(env)) return Envelope(env) + + #### Output Methods #### + @property + def gml(self): + "Returns the GML representation of the Geometry." + buf = c_char_p() + check_err(lgdal.OGR_G_ExportToGML(self._g, byref(buf))) + return string_at(buf) + + @property + def wkt(self): + "Returns the WKT representation of the Geometry." + buf = c_char_p() + check_err(lgdal.OGR_G_ExportToWkt(self._g, byref(buf))) + return string_at(buf) #### Geometry Methods #### def clone(self): @@ -230,13 +242,13 @@ class OGRGeometry(object): def transform(self, coord_trans): "Transforms this Geometry with the given CoordTransform object." if not isinstance(coord_trans, CoordTransform): - raise OGRException, 'CoordTransform object required for transform.' + raise OGRException('CoordTransform object required for transform.') check_err(lgdal.OGR_G_Transform(self._g, coord_trans._ct)) def transform_to(self, srs): "Transforms this Geometry with the given SpatialReference." if not isinstance(srs, SpatialReference): - raise OGRException, 'SpatialReference object required for transform_to.' + raise OGRException('SpatialReference object required for transform_to.') check_err(lgdal.OGR_G_TransformTo(self._g, srs._srs)) #### Topology Methods #### @@ -244,7 +256,7 @@ class OGRGeometry(object): """A generalized function for topology operations, takes a GDAL function and the other geometry to perform the operation on.""" if not isinstance(other, OGRGeometry): - raise OGRException, 'Must use another OGRGeometry object for topology operations!' + raise OGRException('Must use another OGRGeometry object for topology operations!') # Calling the passed-in topology function with the other geometry status = topo_func(self._g, other._g) @@ -368,7 +380,7 @@ class LineString(OGRGeometry): elif self.coord_dim == 3: return (x.value, y.value, z.value) else: - raise OGRIndexError, 'index out of range: %s' % str(index) + raise OGRIndexError('index out of range: %s' % str(index)) def __iter__(self): "Iterates over each point in the LineString." @@ -401,9 +413,9 @@ class Polygon(OGRGeometry): def __getitem__(self, index): "Gets the ring at the specified index." if index < 0 or index >= self.geom_count: - raise OGRIndexError, 'index out of range: %s' % str(index) + raise OGRIndexError('index out of range: %s' % str(index)) else: - return OGRGeometry(lgdal.OGR_G_Clone(lgdal.OGR_G_GetGeometryRef(self._g, c_int(index)))) + return OGRGeometry(lgdal.OGR_G_Clone(lgdal.OGR_G_GetGeometryRef(self._g, c_int(index))), self.srs) # Polygon Properties @property @@ -437,9 +449,9 @@ class GeometryCollection(OGRGeometry): def __getitem__(self, index): "Gets the Geometry at the specified index." if index < 0 or index >= self.geom_count: - raise OGRIndexError, 'index out of range: %s' % str(index) + raise OGRIndexError('index out of range: %s' % str(index)) else: - return OGRGeometry(lgdal.OGR_G_Clone(lgdal.OGR_G_GetGeometryRef(self._g, c_int(index)))) + return OGRGeometry(lgdal.OGR_G_Clone(lgdal.OGR_G_GetGeometryRef(self._g, c_int(index))), self.srs) def __iter__(self): "Iterates over each Geometry." @@ -458,7 +470,7 @@ class GeometryCollection(OGRGeometry): tmp = OGRGeometry(geom) ptr = tmp._g else: - raise OGRException, 'Must add an OGRGeometry.' + raise OGRException('Must add an OGRGeometry.') lgdal.OGR_G_AddGeometry(self._g, ptr) @property diff --git a/django/contrib/gis/gdal/geomtype.py b/django/contrib/gis/gdal/geomtype.py index c0189f8fa9..17f6918840 100644 --- a/django/contrib/gis/gdal/geomtype.py +++ b/django/contrib/gis/gdal/geomtype.py @@ -11,21 +11,21 @@ class OGRGeomType(object): 'LinearRing'] __ogr_int = [1, 2, 3, 4, 5, 6, 7, 101] - def __init__(self, input): + def __init__(self, type_input): "Figures out the correct OGR Type based upon the input." - if isinstance(input, OGRGeomType): - self._index = input._index - elif isinstance(input, StringType): - idx = self._has_str(self.__ogr_str, input) + if isinstance(type_input, OGRGeomType): + self._index = type_input._index + elif isinstance(type_input, StringType): + idx = self._has_str(self.__ogr_str, type_input) if idx == None: - raise OGRException, 'Invalid OGR String Type "%s"' % input + raise OGRException('Invalid OGR String Type "%s"' % type_input) self._index = idx - elif isinstance(input, int): - if not input in self.__ogr_int: - raise OGRException, 'Invalid OGR Integer Type: %d' % input - self._index = self.__ogr_int.index(input) + elif isinstance(type_input, int): + if not type_input in self.__ogr_int: + raise OGRException('Invalid OGR Integer Type: %d' % type_input) + self._index = self.__ogr_int.index(type_input) else: - raise TypeError, 'Invalid OGR Input type given!' + raise TypeError('Invalid OGR Input type given!') def __str__(self): "Returns a short-hand string form of the OGR Geometry type." @@ -44,7 +44,7 @@ class OGRGeomType(object): if not other in self.__ogr_int: return False return self.__ogr_int.index(other) == self._index else: - raise TypeError, 'Cannot compare with type: %s' % str(type(other)) + raise TypeError('Cannot compare with type: %s' % str(type(other))) def _has_str(self, arr, s): "Case-insensitive search of the string array for the given pattern." diff --git a/django/contrib/gis/gdal/srs.py b/django/contrib/gis/gdal/srs.py index b8632daa3c..fea050afba 100644 --- a/django/contrib/gis/gdal/srs.py +++ b/django/contrib/gis/gdal/srs.py @@ -79,7 +79,7 @@ class SpatialReference(object): _epsg_regex = re.compile('^EPSG:(?P\d+)$', re.I) #### Python 'magic' routines #### - def __init__(self, input='', srs_type='wkt'): + def __init__(self, srs_input='', srs_type='wkt'): "Creates a spatial reference object from the given OGC Well Known Text (WKT)." self._srs = 0 # Initially NULL @@ -87,45 +87,46 @@ class SpatialReference(object): # Creating an initial empty string buffer. buf = c_char_p('') - if isinstance(input, UnicodeType): - input = input.encode('ascii') + # Encoding to ASCII if unicode passed in. + if isinstance(srs_input, UnicodeType): + srs_input = srs_input.encode('ascii') - if isinstance(input, StringType): + if isinstance(srs_input, StringType): # Is this an EPSG well known name? - m = self._epsg_regex.match(input) + m = self._epsg_regex.match(srs_input) if m: srs_type = 'epsg' - input = int(m.group('epsg')) + srs_input = int(m.group('epsg')) # Is this a short-hand well known name? - elif input in self._well_known: + elif srs_input in self._well_known: srs_type = 'epsg' - input = self._well_known[input] + srs_input = self._well_known[srs_input] elif srs_type == 'proj': pass else: - buf = c_char_p(input) - elif isinstance(input, int): + buf = c_char_p(srs_input) + elif isinstance(srs_input, int): if srs_type == 'wkt': srs_type = 'epsg' # want to try epsg if only integer provided if srs_type not in ('epsg', 'ogr'): - raise SRSException, 'Integer input requires SRS type of "ogr" or "epsg".' + raise SRSException('Integer input requires SRS type of "ogr" or "epsg".') else: - raise TypeError, 'Invalid SRS type "%s"' % srs_type + raise TypeError('Invalid SRS type "%s"' % srs_type) # Calling OSRNewSpatialReference with the string buffer. if srs_type == 'ogr': - srs = input # Input is OGR pointer + srs = srs_input # SRS input is OGR pointer else: srs = lgdal.OSRNewSpatialReference(buf) # If the pointer is NULL, throw an exception. if not srs: - raise SRSException, 'Could not create spatial reference from WKT! (%s)' % input + raise SRSException('Could not create spatial reference from: %s' % srs_input) else: self._srs = srs # Post-processing if in PROJ.4 or EPSG formats. - if srs_type == 'proj': self.import_proj(input) - elif srs_type == 'epsg': self.import_epsg(input) + if srs_type == 'proj': self.import_proj(srs_input) + elif srs_type == 'epsg': self.import_epsg(srs_input) def __del__(self): "Destroys this spatial reference." @@ -142,6 +143,14 @@ class SpatialReference(object): else: return self.attr_value(target) + def __nonzero__(self): + "Returns True if this SpatialReference object is valid." + try: + self.validate() + return True + except OGRException: + return False + def __str__(self): "The string representation uses 'pretty' WKT." return self.pretty_wkt @@ -188,7 +197,15 @@ class SpatialReference(object): elif self.geographic: return self.attr_value('GEOGCS') elif self.local: return self.attr_value('LOCAL_CS') else: return None - + + @property + def srid(self): + """ + Returns the EPSG SRID of this Spatial Reference, will be None if + if undefined. + """ + return self.srs['AUTHORITY', 1] + #### Unit Properties #### def _cache_linear(self): "Caches the linear units value and name." @@ -345,10 +362,10 @@ class CoordTransform(object): "Initializes on a source and target SpatialReference objects." self._ct = 0 # Initially NULL if not isinstance(source, SpatialReference) or not isinstance(target, SpatialReference): - raise SRSException, 'source and target must be of type SpatialReference' + raise SRSException('source and target must be of type SpatialReference') ct = lgdal.OCTNewCoordinateTransformation(source._srs, target._srs) if not ct: - raise SRSException, 'could not intialize CoordTransform object' + raise SRSException('could not intialize CoordTransform object') self._ct = ct self._srs1_name = source.name self._srs2_name = target.name diff --git a/django/contrib/gis/tests/__init__.py b/django/contrib/gis/tests/__init__.py index 8b64e06911..9ced485163 100644 --- a/django/contrib/gis/tests/__init__.py +++ b/django/contrib/gis/tests/__init__.py @@ -13,6 +13,7 @@ if HAS_GDAL: test_suite_names += [ 'test_gdal_driver', 'test_gdal_ds', + 'test_gdal_envelope', 'test_gdal_geom', 'test_gdal_srs', 'test_spatialrefsys', diff --git a/django/contrib/gis/tests/test_gdal.py b/django/contrib/gis/tests/test_gdal.py new file mode 100644 index 0000000000..c7c33fb6e1 --- /dev/null +++ b/django/contrib/gis/tests/test_gdal.py @@ -0,0 +1,24 @@ +from unittest import TestSuite, TextTestRunner + +# Importing the GDAL test modules. +from django.contrib.gis.tests import \ + test_gdal_driver, test_gdal_ds, test_gdal_envelope, \ + test_gdal_geom, test_gdal_srs + + +test_suites = [test_gdal_driver.suite(), + test_gdal_ds.suite(), + test_gdal_envelope.suite(), + test_gdal_geom.suite(), + test_gdal_srs.suite(), + ] + +def suite(): + "Builds a test suite for the GDAL tests." + s = TestSuite() + map(s.addTest, test_suites) + return s + +def run(verbosity=1): + "Runs the tests that do not require geographic (GEOS, GDAL, etc.) models." + TextTestRunner(verbosity=verbosity).run(suite()) diff --git a/django/contrib/gis/tests/test_gdal_ds.py b/django/contrib/gis/tests/test_gdal_ds.py index 799442cf70..beb5b9eaf1 100644 --- a/django/contrib/gis/tests/test_gdal_ds.py +++ b/django/contrib/gis/tests/test_gdal_ds.py @@ -1,6 +1,5 @@ import os, os.path, unittest -from django.contrib.gis.gdal import DataSource, OGRException -from django.contrib.gis.gdal.envelope import Envelope +from django.contrib.gis.gdal import DataSource, Envelope, OGRException, OGRIndexError from django.contrib.gis.gdal.field import OFTReal, OFTInteger, OFTString # Path for SHP files @@ -48,7 +47,7 @@ class DataSourceTest(unittest.TestCase): # Making sure indexing works try: ds[len(ds)] - except IndexError: + except OGRIndexError: pass else: self.fail('Expected an IndexError!') @@ -106,7 +105,9 @@ class DataSourceTest(unittest.TestCase): # Asserting the string representation, and making sure we get # the proper OGR Field instance. - self.assertEqual('%s (%s)' % (k, fld.value), str(fld)) + if isinstance(fld, OFTString): fmt = '%s ("%s")' + else: fmt = '%s (%s)' + self.assertEqual(fmt % (k, fld.value), str(fld)) self.assertEqual(True, isinstance(fld, v)) # Testing __iter__ on the Feature @@ -136,5 +137,3 @@ def suite(): def run(verbosity=2): unittest.TextTestRunner(verbosity=verbosity).run(suite()) - - diff --git a/django/contrib/gis/tests/test_gdal_envelope.py b/django/contrib/gis/tests/test_gdal_envelope.py new file mode 100644 index 0000000000..fda27191ac --- /dev/null +++ b/django/contrib/gis/tests/test_gdal_envelope.py @@ -0,0 +1,45 @@ +import unittest +from django.contrib.gis.gdal import Envelope, OGRException + +class EnvelopeTest(unittest.TestCase): + + def test01_init(self): + "Testing Envelope initilization." + e1 = Envelope((0, 0, 5, 5)) + e2 = Envelope(0, 0, 5, 5) + e3 = Envelope(0, '0', '5', 5) # Thanks to ww for this + e4 = Envelope(e1._envelope) + self.assertRaises(OGRException, Envelope, (5, 5, 0, 0)) + self.assertRaises(OGRException, Envelope, 5, 5, 0, 0) + self.assertRaises(OGRException, Envelope, (0, 0, 5, 5, 3)) + self.assertRaises(OGRException, Envelope, ()) + self.assertRaises(ValueError, Envelope, 0, 'a', 5, 5) + self.assertRaises(TypeError, Envelope, u'foo') + + def test02_properties(self): + "Testing Envelope properties." + e = Envelope(0, 0, 2, 3) + self.assertEqual(0, e.min_x) + self.assertEqual(0, e.min_y) + self.assertEqual(2, e.max_x) + self.assertEqual(3, e.max_y) + self.assertEqual((0, 0), e.ll) + self.assertEqual((2, 3), e.ur) + self.assertEqual((0, 0, 2, 3), e.tuple) + self.assertEqual('POLYGON((0.0 0.0,0.0 3.0,2.0 3.0,2.0 0.0,0.0 0.0))', e.wkt) + self.assertEqual('(0.0, 0.0, 2.0, 3.0)', str(e)) + + def test03_equivalence(self): + "Testing Envelope equivalence." + e1 = Envelope(0.523, 0.217, 253.23, 523.69) + e2 = Envelope((0.523, 0.217, 253.23, 523.69)) + self.assertEqual(e1, e2) + self.assertEqual((0.523, 0.217, 253.23, 523.69), e1) + +def suite(): + s = unittest.TestSuite() + s.addTest(unittest.makeSuite(EnvelopeTest)) + return s + +def run(verbosity=2): + unittest.TextTestRunner(verbosity=verbosity).run(suite()) diff --git a/django/contrib/gis/tests/test_gdal_geom.py b/django/contrib/gis/tests/test_gdal_geom.py index 53068e4cba..b4a3c72afb 100644 --- a/django/contrib/gis/tests/test_gdal_geom.py +++ b/django/contrib/gis/tests/test_gdal_geom.py @@ -1,5 +1,5 @@ import unittest -from django.contrib.gis.gdal import OGRGeometry, OGRGeomType, OGRException +from django.contrib.gis.gdal import OGRGeometry, OGRGeomType, OGRException, SpatialReference from geometries import * class OGRGeomTest(unittest.TestCase): @@ -130,6 +130,18 @@ class OGRGeomTest(unittest.TestCase): self.assertEqual(mp.n_p, mpoly.point_count) self.assertEqual(mp.num_geom, len(mpoly)) + def test09_srs(self): + "Testing OGR Geometries with Spatial Reference objects." + for mp in multipolygons: + sr = SpatialReference('WGS84') + mpoly = OGRGeometry(mp.wkt, sr) + self.assertEqual(sr.wkt, mpoly.srs.wkt) + for poly in mpoly: + self.assertEqual(sr.wkt, poly.srs.wkt) + for ring in poly: + self.assertEqual(sr.wkt, ring.srs.wkt) + + def suite(): s = unittest.TestSuite() s.addTest(unittest.makeSuite(OGRGeomTest))