From f1ada99997de72fdd3f765ffa11fa4d1d9f438f9 Mon Sep 17 00:00:00 2001 From: Justin Bronn Date: Sat, 7 Mar 2009 23:02:48 +0000 Subject: [PATCH] Maintenance refactor of the GDAL (OGR) ctypes interface. Changes include: * All C API method explictly called from their prototype module, no longer imported via *. * Applied DRY to C pointer management, classes that do so subclass from `GDALBase`. * `OGRGeometry`: Added `from_bbox` class method (patch from Christopher Schmidt) and `kml` property. * `SpatialReference`: Now initialize with `SetFromUserInput` (initialization is now more simple and flexible); removed duplicate methods. * `Envelope`: Added `expand_to_include` method and now allow same coordinates for lower left and upper right points. Thanks to Paul Smith for tickets and patches. * `OGRGeomType`: Now treat OGC 'Geometry' type as 'Unknown'. Fixed #9855, #10368, #10380. Refs #9806. git-svn-id: http://code.djangoproject.com/svn/django/trunk@9985 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/gis/gdal/LICENSE | 2 +- django/contrib/gis/gdal/base.py | 35 ++++ django/contrib/gis/gdal/datasource.py | 40 ++-- django/contrib/gis/gdal/driver.py | 19 +- django/contrib/gis/gdal/envelope.py | 55 +++++- django/contrib/gis/gdal/error.py | 1 + django/contrib/gis/gdal/feature.py | 35 ++-- django/contrib/gis/gdal/field.py | 41 ++--- django/contrib/gis/gdal/geometries.py | 174 ++++++++++-------- django/contrib/gis/gdal/geomtype.py | 11 +- django/contrib/gis/gdal/layer.py | 47 +++-- django/contrib/gis/gdal/prototypes/geom.py | 2 + django/contrib/gis/gdal/prototypes/srs.py | 1 + django/contrib/gis/gdal/srs.py | 159 +++++++--------- .../test_gdal.py => gdal/tests/__init__.py} | 15 +- .../tests/test_driver.py} | 0 .../test_gdal_ds.py => gdal/tests/test_ds.py} | 5 +- .../contrib/gis/gdal/tests/test_envelope.py | 94 ++++++++++ .../tests/test_geom.py} | 21 ++- .../tests/test_srs.py} | 0 django/contrib/gis/tests/__init__.py | 85 ++++++--- .../contrib/gis/tests/test_gdal_envelope.py | 45 ----- 22 files changed, 515 insertions(+), 372 deletions(-) create mode 100644 django/contrib/gis/gdal/base.py rename django/contrib/gis/{tests/test_gdal.py => gdal/tests/__init__.py} (56%) rename django/contrib/gis/{tests/test_gdal_driver.py => gdal/tests/test_driver.py} (100%) rename django/contrib/gis/{tests/test_gdal_ds.py => gdal/tests/test_ds.py} (98%) create mode 100644 django/contrib/gis/gdal/tests/test_envelope.py rename django/contrib/gis/{tests/test_gdal_geom.py => gdal/tests/test_geom.py} (96%) rename django/contrib/gis/{tests/test_gdal_srs.py => gdal/tests/test_srs.py} (100%) delete mode 100644 django/contrib/gis/tests/test_gdal_envelope.py diff --git a/django/contrib/gis/gdal/LICENSE b/django/contrib/gis/gdal/LICENSE index 4bd0bed44b..30d410ecfe 100644 --- a/django/contrib/gis/gdal/LICENSE +++ b/django/contrib/gis/gdal/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2007, Justin Bronn +Copyright (c) 2007-2009, Justin Bronn All rights reserved. Redistribution and use in source and binary forms, with or without modification, diff --git a/django/contrib/gis/gdal/base.py b/django/contrib/gis/gdal/base.py new file mode 100644 index 0000000000..e19b748e6f --- /dev/null +++ b/django/contrib/gis/gdal/base.py @@ -0,0 +1,35 @@ +from ctypes import c_void_p +from types import NoneType +from django.contrib.gis.gdal.error import GDALException + +class GDALBase(object): + """ + Base object for GDAL objects that has a pointer access property + that controls access to the underlying C pointer. + """ + # Initially the pointer is NULL. + _ptr = None + + # Default allowed pointer type. + ptr_type = c_void_p + + # Pointer access property. + def _get_ptr(self): + # Raise an exception if the pointer isn't valid don't + # want to be passing NULL pointers to routines -- + # that's very bad. + if self._ptr: return self._ptr + else: raise GDALException('GDAL %s pointer no longer valid.' % self.__class__.__name__) + + 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)): + self._ptr = ptr + else: + raise TypeError('Incompatible pointer type') + + ptr = property(_get_ptr, _set_ptr) + diff --git a/django/contrib/gis/gdal/datasource.py b/django/contrib/gis/gdal/datasource.py index 1eace25bef..7db5fd9ce3 100644 --- a/django/contrib/gis/gdal/datasource.py +++ b/django/contrib/gis/gdal/datasource.py @@ -37,28 +37,23 @@ from ctypes import byref, c_void_p # The GDAL C library, OGR exceptions, and the Layer object. +from django.contrib.gis.gdal.base import GDALBase from django.contrib.gis.gdal.driver import Driver from django.contrib.gis.gdal.error import OGRException, OGRIndexError from django.contrib.gis.gdal.layer import Layer # Getting the ctypes prototypes for the DataSource. -from django.contrib.gis.gdal.prototypes.ds import \ - destroy_ds, get_driver_count, register_all, open_ds, release_ds, \ - get_ds_name, get_layer, get_layer_count, get_layer_by_name +from django.contrib.gis.gdal.prototypes import ds as capi # For more information, see the OGR C API source code: # http://www.gdal.org/ogr/ogr__api_8h.html # # The OGR_DS_* routines are relevant here. -class DataSource(object): +class DataSource(GDALBase): "Wraps an OGR Data Source object." #### Python 'magic' routines #### def __init__(self, ds_input, ds_driver=False, write=False): - - # DataSource pointer is initially NULL. - self._ptr = None - # The write flag. if write: self._write = 1 @@ -67,33 +62,34 @@ class DataSource(object): # Registering all the drivers, this needs to be done # _before_ we try to open up a data source. - if not get_driver_count(): register_all() + if not capi.get_driver_count(): + capi.register_all() if isinstance(ds_input, basestring): # The data source driver is a void pointer. - ds_driver = c_void_p() + ds_driver = Driver.ptr_type() try: # OGROpen will auto-detect the data source type. - ds = open_ds(ds_input, self._write, byref(ds_driver)) + ds = capi.open_ds(ds_input, self._write, byref(ds_driver)) except OGRException: # Making the error message more clear rather than something # like "Invalid pointer returned from OGROpen". raise OGRException('Could not open the datasource at "%s"' % ds_input) - elif isinstance(ds_input, c_void_p) and isinstance(ds_driver, c_void_p): + elif isinstance(ds_input, self.ptr_type) and isinstance(ds_driver, Driver.ptr_type): ds = ds_input else: raise OGRException('Invalid data source input type: %s' % type(ds_input)) if bool(ds): - self._ptr = ds - self._driver = Driver(ds_driver) + self.ptr = ds + self.driver = Driver(ds_driver) else: # Raise an exception if the returned pointer is NULL raise OGRException('Invalid data source file "%s"' % ds_input) def __del__(self): "Destroys this DataStructure object." - if self._ptr: destroy_ds(self._ptr) + if self._ptr: capi.destroy_ds(self._ptr) def __iter__(self): "Allows for iteration over the layers in a data source." @@ -103,12 +99,12 @@ class DataSource(object): def __getitem__(self, index): "Allows use of the index [] operator to get a layer at the index." if isinstance(index, basestring): - l = get_layer_by_name(self._ptr, index) + l = capi.get_layer_by_name(self.ptr, index) if not l: raise OGRIndexError('invalid OGR Layer name given: "%s"' % index) elif isinstance(index, int): if index < 0 or index >= self.layer_count: raise OGRIndexError('index out of range') - l = get_layer(self._ptr, index) + l = capi.get_layer(self._ptr, index) else: raise TypeError('Invalid index type: %s' % type(index)) return Layer(l, self) @@ -121,18 +117,12 @@ class DataSource(object): "Returns OGR GetName and Driver for the Data Source." return '%s (%s)' % (self.name, str(self.driver)) - #### DataSource Properties #### - @property - def driver(self): - "Returns the Driver object for this Data Source." - return self._driver - @property def layer_count(self): "Returns the number of layers in the data source." - return get_layer_count(self._ptr) + return capi.get_layer_count(self._ptr) @property def name(self): "Returns the name of the data source." - return get_ds_name(self._ptr) + return capi.get_ds_name(self._ptr) diff --git a/django/contrib/gis/gdal/driver.py b/django/contrib/gis/gdal/driver.py index 2d5b3df807..1753db2b2b 100644 --- a/django/contrib/gis/gdal/driver.py +++ b/django/contrib/gis/gdal/driver.py @@ -1,14 +1,14 @@ # prerequisites imports from ctypes import c_void_p +from django.contrib.gis.gdal.base import GDALBase from django.contrib.gis.gdal.error import OGRException -from django.contrib.gis.gdal.prototypes.ds import \ - get_driver, get_driver_by_name, get_driver_count, get_driver_name, register_all +from django.contrib.gis.gdal.prototypes import ds as capi # For more information, see the OGR C API source code: # http://www.gdal.org/ogr/ogr__api_8h.html # # The OGR_Dr_* routines are relevant here. -class Driver(object): +class Driver(GDALBase): "Wraps an OGR Data Source Driver." # Case-insensitive aliases for OGR Drivers. @@ -24,7 +24,6 @@ class Driver(object): if isinstance(dr_input, basestring): # If a string name of the driver was passed in - self._ptr = None # Initially NULL self._register() # Checking the alias dictionary (case-insensitive) to see if an alias @@ -35,10 +34,10 @@ class Driver(object): name = dr_input # Attempting to get the OGR driver by the string name. - dr = get_driver_by_name(name) + dr = capi.get_driver_by_name(name) elif isinstance(dr_input, int): self._register() - dr = get_driver(dr_input) + dr = capi.get_driver(dr_input) elif isinstance(dr_input, c_void_p): dr = dr_input else: @@ -47,20 +46,20 @@ class Driver(object): # 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(dr_input)) - self._ptr = dr + self.ptr = dr def __str__(self): "Returns the string name of the OGR Driver." - return get_driver_name(self._ptr) + return capi.get_driver_name(self.ptr) def _register(self): "Attempts to register all the data source drivers." # 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: register_all() + if not self.driver_count: capi.register_all() # Driver properties @property def driver_count(self): "Returns the number of OGR data source drivers registered." - return get_driver_count() + return capi.get_driver_count() diff --git a/django/contrib/gis/gdal/envelope.py b/django/contrib/gis/gdal/envelope.py index 971f06da90..0e6aa0ece6 100644 --- a/django/contrib/gis/gdal/envelope.py +++ b/django/contrib/gis/gdal/envelope.py @@ -11,7 +11,6 @@ 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. @@ -42,7 +41,7 @@ class Envelope(object): if isinstance(args[0], OGREnvelope): # OGREnvelope (a ctypes Structure) was passed in. self._envelope = args[0] - elif isinstance(args[0], (TupleType, ListType)): + elif isinstance(args[0], (tuple, list)): # A tuple was passed in. if len(args[0]) != 4: raise OGRException('Incorrect number of tuple elements (%d).' % len(args[0])) @@ -58,10 +57,10 @@ class Envelope(object): 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.') + 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): """ @@ -71,7 +70,7 @@ class Envelope(object): 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) - elif isinstance(other, TupleType) and len(other) == 4: + elif isinstance(other, tuple) and len(other) == 4: 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: @@ -89,6 +88,48 @@ class Envelope(object): self._envelope.MaxX = seq[2] self._envelope.MaxY = seq[3] + def expand_to_include(self, *args): + """ + Modifies the envelope to expand to include the boundaries of + the passed-in 2-tuple (a point), 4-tuple (an extent) or + envelope. + """ + # We provide a number of different signatures for this method, + # and the logic here is all about converting them into a + # 4-tuple single parameter which does the actual work of + # expanding the envelope. + if len(args) == 1: + if isinstance(args[0], Envelope): + return self.expand_to_include(args[0].tuple) + elif hasattr(args[0], 'x') and hasattr(args[0], 'y'): + return self.expand_to_include(args[0].x, args[0].y, args[0].x, args[0].y) + elif isinstance(args[0], (tuple, list)): + # A tuple was passed in. + if len(args[0]) == 2: + return self.expand_to_include((args[0][0], args[0][1], args[0][0], args[0][1])) + elif len(args[0]) == 4: + (minx, miny, maxx, maxy) = args[0] + if minx < self._envelope.MinX: + self._envelope.MinX = minx + if miny < self._envelope.MinY: + self._envelope.MinY = miny + if maxx > self._envelope.MaxX: + self._envelope.MaxX = maxx + if maxy > self._envelope.MaxY: + self._envelope.MaxY = maxy + else: + raise OGRException('Incorrect number of tuple elements (%d).' % len(args[0])) + else: + raise TypeError('Incorrect type of argument: %s' % str(type(args[0]))) + elif len(args) == 2: + # An x and an y parameter were passed in + return self.expand_to_include((args[0], args[1], args[0], args[1])) + elif len(args) == 4: + # Individiual parameters passed in. + return self.expand_to_include(args) + else: + raise OGRException('Incorrect number (%d) of arguments.' % len(args[0])) + @property def min_x(self): "Returns the value of the minimum X coordinate." diff --git a/django/contrib/gis/gdal/error.py b/django/contrib/gis/gdal/error.py index 4ec3f7e735..58ca891672 100644 --- a/django/contrib/gis/gdal/error.py +++ b/django/contrib/gis/gdal/error.py @@ -4,6 +4,7 @@ OGR methods. """ #### OGR & SRS Exceptions #### +class GDALException(Exception): pass class OGRException(Exception): pass class SRSException(Exception): pass class OGRIndexError(OGRException, KeyError): diff --git a/django/contrib/gis/gdal/feature.py b/django/contrib/gis/gdal/feature.py index bc3857f606..b5c173a240 100644 --- a/django/contrib/gis/gdal/feature.py +++ b/django/contrib/gis/gdal/feature.py @@ -1,36 +1,31 @@ # The GDAL C library, OGR exception, and the Field object +from django.contrib.gis.gdal.base import GDALBase 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 # ctypes function prototypes -from django.contrib.gis.gdal.prototypes.ds import \ - destroy_feature, feature_equal, get_fd_geom_type, get_feat_geom_ref, \ - get_feat_name, get_feat_field_count, get_fid, get_field_defn, \ - get_field_index, get_field_name -from django.contrib.gis.gdal.prototypes.geom import clone_geom, get_geom_srs -from django.contrib.gis.gdal.prototypes.srs import clone_srs +from django.contrib.gis.gdal.prototypes import ds as capi, geom as geom_api # For more information, see the OGR C API source code: # http://www.gdal.org/ogr/ogr__api_8h.html # # The OGR_F_* routines are relevant here. -class Feature(object): +class Feature(GDALBase): "A class that wraps an OGR Feature, needs to be instantiated from a Layer object." #### Python 'magic' routines #### def __init__(self, feat, fdefn): "Initializes on the pointers for the feature and the layer definition." - self._ptr = None # Initially NULL if not feat or not fdefn: raise OGRException('Cannot create OGR Feature, invalid pointer given.') - self._ptr = feat + self.ptr = feat self._fdefn = fdefn def __del__(self): "Releases a reference to this object." - if self._ptr: destroy_feature(self._ptr) + if self._ptr: capi.destroy_feature(self._ptr) def __getitem__(self, index): """ @@ -45,7 +40,7 @@ class Feature(object): if index < 0 or index > self.num_fields: raise OGRIndexError('index out of range') i = index - return Field(self._ptr, i) + return Field(self.ptr, i) def __iter__(self): "Iterates over each field in the Feature." @@ -62,41 +57,41 @@ class Feature(object): def __eq__(self, other): "Does equivalence testing on the features." - return bool(feature_equal(self._ptr, other._ptr)) + return bool(capi.feature_equal(self.ptr, other._ptr)) #### Feature Properties #### @property def fid(self): "Returns the feature identifier." - return get_fid(self._ptr) + return capi.get_fid(self.ptr) @property def layer_name(self): "Returns the name of the layer for the feature." - return get_feat_name(self._fdefn) + return capi.get_feat_name(self._fdefn) @property def num_fields(self): "Returns the number of fields in the Feature." - return get_feat_field_count(self._ptr) + return capi.get_feat_field_count(self.ptr) @property def fields(self): "Returns a list of fields in the Feature." - return [get_field_name(get_field_defn(self._fdefn, i)) + return [capi.get_field_name(capi.get_field_defn(self._fdefn, i)) for i in xrange(self.num_fields)] @property def geom(self): "Returns the OGR Geometry for this Feature." # Retrieving the geometry pointer for the feature. - geom_ptr = get_feat_geom_ref(self._ptr) - return OGRGeometry(clone_geom(geom_ptr)) + geom_ptr = capi.get_feat_geom_ref(self.ptr) + return OGRGeometry(geom_api.clone_geom(geom_ptr)) @property def geom_type(self): "Returns the OGR Geometry Type for this Feture." - return OGRGeomType(get_fd_geom_type(self._fdefn)) + return OGRGeomType(capi.get_fd_geom_type(self._fdefn)) #### Feature Methods #### def get(self, field): @@ -110,6 +105,6 @@ class Feature(object): def index(self, field_name): "Returns the index of the given field name." - i = get_field_index(self._ptr, field_name) + i = capi.get_field_index(self.ptr, field_name) if i < 0: raise OGRIndexError('invalid OFT field name given: "%s"' % field_name) return i diff --git a/django/contrib/gis/gdal/field.py b/django/contrib/gis/gdal/field.py index 6374bc7d80..46dbc869b7 100644 --- a/django/contrib/gis/gdal/field.py +++ b/django/contrib/gis/gdal/field.py @@ -1,16 +1,14 @@ from ctypes import byref, c_int from datetime import date, datetime, time +from django.contrib.gis.gdal.base import GDALBase from django.contrib.gis.gdal.error import OGRException -from django.contrib.gis.gdal.prototypes.ds import \ - get_feat_field_defn, get_field_as_datetime, get_field_as_double, \ - get_field_as_integer, get_field_as_string, get_field_name, get_field_precision, \ - get_field_type, get_field_type_name, get_field_width +from django.contrib.gis.gdal.prototypes import ds as capi # For more information, see the OGR C API source code: # http://www.gdal.org/ogr/ogr__api_8h.html # # The OGR_Fld_* routines are relevant here. -class Field(object): +class Field(GDALBase): "A class that wraps an OGR Field, needs to be instantiated from a Feature object." #### Python 'magic' routines #### @@ -24,13 +22,13 @@ class Field(object): self._index = index # Getting the pointer for this field. - fld = get_feat_field_defn(feat, index) - if not fld: + fld_ptr = capi.get_feat_field_defn(feat, index) + if not fld_ptr: raise OGRException('Cannot create OGR Field, invalid pointer given.') - self._ptr = fld + self.ptr = fld_ptr # Setting the class depending upon the OGR Field Type (OFT) - self.__class__ = FIELD_CLASSES[self.type] + self.__class__ = OGRFieldTypes[self.type] # OFTReal with no precision should be an OFTInteger. if isinstance(self, OFTReal) and self.precision == 0: @@ -43,21 +41,21 @@ class Field(object): #### Field Methods #### def as_double(self): "Retrieves the Field's value as a double (float)." - return get_field_as_double(self._feat, self._index) + return capi.get_field_as_double(self._feat, self._index) def as_int(self): "Retrieves the Field's value as an integer." - return get_field_as_integer(self._feat, self._index) + return capi.get_field_as_integer(self._feat, self._index) def as_string(self): "Retrieves the Field's value as a string." - return get_field_as_string(self._feat, self._index) + return capi.get_field_as_string(self._feat, self._index) def as_datetime(self): "Retrieves the Field's value as a tuple of date & time components." yy, mm, dd, hh, mn, ss, tz = [c_int() for i in range(7)] - status = get_field_as_datetime(self._feat, self._index, byref(yy), byref(mm), byref(dd), - byref(hh), byref(mn), byref(ss), byref(tz)) + status = capi.get_field_as_datetime(self._feat, self._index, byref(yy), byref(mm), byref(dd), + byref(hh), byref(mn), byref(ss), byref(tz)) if status: return (yy, mm, dd, hh, mn, ss, tz) else: @@ -67,22 +65,22 @@ class Field(object): @property def name(self): "Returns the name of this Field." - return get_field_name(self._ptr) + return capi.get_field_name(self.ptr) @property def precision(self): "Returns the precision of this Field." - return get_field_precision(self._ptr) + return capi.get_field_precision(self.ptr) @property def type(self): "Returns the OGR type of this Field." - return get_field_type(self._ptr) + return capi.get_field_type(self.ptr) @property def type_name(self): "Return the OGR field type name for this Field." - return get_field_type_name(self.type) + return capi.get_field_type_name(self.type) @property def value(self): @@ -93,7 +91,7 @@ class Field(object): @property def width(self): "Returns the width of this Field." - return get_field_width(self._ptr) + return capi.get_field_width(self.ptr) ### The Field sub-classes for each OGR Field type. ### class OFTInteger(Field): @@ -163,8 +161,8 @@ class OFTRealList(Field): pass class OFTStringList(Field): pass class OFTWideStringList(Field): pass -# Class mapping dictionary for OFT Types -FIELD_CLASSES = { 0 : OFTInteger, +# Class mapping dictionary for OFT Types and reverse mapping. +OGRFieldTypes = { 0 : OFTInteger, 1 : OFTIntegerList, 2 : OFTReal, 3 : OFTRealList, @@ -177,3 +175,4 @@ FIELD_CLASSES = { 0 : OFTInteger, 10 : OFTTime, 11 : OFTDateTime, } +ROGRFieldTypes = dict([(cls, num) for num, cls in OGRFieldTypes.items()]) diff --git a/django/contrib/gis/gdal/geometries.py b/django/contrib/gis/gdal/geometries.py index a880a75d13..05b824b95d 100644 --- a/django/contrib/gis/gdal/geometries.py +++ b/django/contrib/gis/gdal/geometries.py @@ -44,14 +44,15 @@ from binascii import a2b_hex from ctypes import byref, string_at, c_char_p, c_double, c_ubyte, c_void_p # Getting GDAL prerequisites +from django.contrib.gis.gdal.base import GDALBase from django.contrib.gis.gdal.envelope import Envelope, OGREnvelope from django.contrib.gis.gdal.error import OGRException, OGRIndexError, SRSException from django.contrib.gis.gdal.geomtype import OGRGeomType from django.contrib.gis.gdal.srs import SpatialReference, CoordTransform # Getting the ctypes prototype functions that interface w/the GDAL C library. -from django.contrib.gis.gdal.prototypes.geom import * -from django.contrib.gis.gdal.prototypes.srs import clone_srs +from django.contrib.gis.gdal.prototypes import geom as capi, srs as srs_api +GEOJSON = capi.GEOJSON # For more information, see the OGR C API source code: # http://www.gdal.org/ogr/ogr__api_8h.html @@ -64,13 +65,12 @@ wkt_regex = re.compile(r'^(?PPOINT|LINESTRING|LINEARRING|POLYGON|MULTIPOIN json_regex = re.compile(r'^(\s+)?\{[\s\w,\[\]\{\}\-\."\':]+\}(\s+)?$') #### OGRGeometry Class #### -class OGRGeometry(object): +class OGRGeometry(GDALBase): "Generally encapsulates an OGR geometry." def __init__(self, geom_input, srs=None): "Initializes Geometry on either WKT or an OGR pointer as input." - self._ptr = c_void_p(None) # Initially NULL str_instance = isinstance(geom_input, basestring) # If HEX, unpack input to to a binary buffer. @@ -91,27 +91,27 @@ class OGRGeometry(object): if wkt_m.group('type').upper() == 'LINEARRING': # OGR_G_CreateFromWkt doesn't work with LINEARRING WKT. # See http://trac.osgeo.org/gdal/ticket/1992. - g = create_geom(OGRGeomType(wkt_m.group('type')).num) - import_wkt(g, byref(c_char_p(geom_input))) + g = capi.create_geom(OGRGeomType(wkt_m.group('type')).num) + capi.import_wkt(g, byref(c_char_p(geom_input))) else: - g = from_wkt(byref(c_char_p(geom_input)), None, byref(c_void_p())) + g = capi.from_wkt(byref(c_char_p(geom_input)), None, byref(c_void_p())) elif json_m: if GEOJSON: - g = from_json(geom_input) + g = capi.from_json(geom_input) else: raise NotImplementedError('GeoJSON input only supported on GDAL 1.5+.') else: # Seeing if the input is a valid short-hand string # (e.g., 'Point', 'POLYGON'). ogr_t = OGRGeomType(geom_input) - g = create_geom(OGRGeomType(geom_input).num) + g = capi.create_geom(OGRGeomType(geom_input).num) elif isinstance(geom_input, buffer): # WKB was passed in - g = from_wkb(str(geom_input), None, byref(c_void_p()), len(geom_input)) + g = capi.from_wkb(str(geom_input), None, byref(c_void_p()), len(geom_input)) elif isinstance(geom_input, OGRGeomType): # OGRGeomType was passed in, an empty geometry will be created. - g = create_geom(geom_input.num) - elif isinstance(geom_input, c_void_p): + g = capi.create_geom(geom_input.num) + elif isinstance(geom_input, self.ptr_type): # OGR pointer (c_void_p) was the input. g = geom_input else: @@ -121,7 +121,7 @@ class OGRGeometry(object): # by setting the pointer for the object. if not g: raise OGRException('Cannot create OGR Geometry from input: %s' % str(geom_input)) - self._ptr = g + self.ptr = g # Assigning the SpatialReference object to the geometry, if valid. if bool(srs): self.srs = srs @@ -129,9 +129,16 @@ class OGRGeometry(object): # Setting the class depending upon the OGR Geometry Type self.__class__ = GEO_CLASSES[self.geom_type.num] + @classmethod + def from_bbox(cls, bbox): + "Constructs a Polygon from a bounding box (4-tuple)." + x0, y0, x1, y1 = bbox + return OGRGeometry( 'POLYGON((%s %s, %s %s, %s %s, %s %s, %s %s))' % ( + x0, y0, x0, y1, x1, y1, x1, y0, x0, y0) ) + def __del__(self): "Deletes this Geometry." - if self._ptr: destroy_geom(self._ptr) + if self._ptr: capi.destroy_geom(self._ptr) ### Geometry set-like operations ### # g = g1 | g2 @@ -170,22 +177,22 @@ class OGRGeometry(object): @property def dimension(self): "Returns 0 for points, 1 for lines, and 2 for surfaces." - return get_dims(self._ptr) + return capi.get_dims(self.ptr) @property def coord_dim(self): "Returns the coordinate dimension of the Geometry." - return get_coord_dims(self._ptr) + return capi.get_coord_dims(self.ptr) @property def geom_count(self): "The number of elements in this Geometry." - return get_geom_count(self._ptr) + return capi.get_geom_count(self.ptr) @property def point_count(self): "Returns the number of Points in this Geometry." - return get_point_count(self._ptr) + return capi.get_point_count(self.ptr) @property def num_points(self): @@ -201,28 +208,28 @@ class OGRGeometry(object): def geom_type(self): "Returns the Type for this Geometry." try: - return OGRGeomType(get_geom_type(self._ptr)) + return OGRGeomType(capi.get_geom_type(self.ptr)) except OGRException: # VRT datasources return an invalid geometry type # number, but a valid name -- we'll try that instead. # See: http://trac.osgeo.org/gdal/ticket/2491 - return OGRGeomType(get_geom_name(self._ptr)) + return OGRGeomType(capi.get_geom_name(self.ptr)) @property def geom_name(self): "Returns the Name of this Geometry." - return get_geom_name(self._ptr) + return capi.get_geom_name(self.ptr) @property def area(self): "Returns the area for a LinearRing, Polygon, or MultiPolygon; 0 otherwise." - return get_area(self._ptr) + return capi.get_area(self.ptr) @property def envelope(self): "Returns the envelope for this Geometry." # TODO: Fix Envelope() for Point geometries. - return Envelope(get_envelope(self._ptr, byref(OGREnvelope()))) + return Envelope(capi.get_envelope(self.ptr, byref(OGREnvelope()))) @property def extent(self): @@ -232,39 +239,40 @@ class OGRGeometry(object): #### SpatialReference-related Properties #### # The SRS property - def get_srs(self): + def _get_srs(self): "Returns the Spatial Reference for this Geometry." try: - srs_ptr = get_geom_srs(self._ptr) - return SpatialReference(clone_srs(srs_ptr)) + srs_ptr = capi.get_geom_srs(self.ptr) + return SpatialReference(srs_api.clone_srs(srs_ptr)) except SRSException: return None - def set_srs(self, srs): + def _set_srs(self, srs): "Sets the SpatialReference for this geometry." if isinstance(srs, SpatialReference): - srs_ptr = clone_srs(srs._ptr) + srs_ptr = srs_api.clone_srs(srs.ptr) elif isinstance(srs, (int, long, basestring)): sr = SpatialReference(srs) - srs_ptr = clone_srs(sr._ptr) + srs_ptr = srs_api.clone_srs(sr.ptr) else: raise TypeError('Cannot assign spatial reference with object of type: %s' % type(srs)) - assign_srs(self._ptr, srs_ptr) + capi.assign_srs(self.ptr, srs_ptr) - srs = property(get_srs, set_srs) + srs = property(_get_srs, _set_srs) # The SRID property - def get_srid(self): - if self.srs: return self.srs.srid - else: return None + def _get_srid(self): + srs = self.srs + if srs: return srs.srid + return None - def set_srid(self, srid): + def _set_srid(self, srid): if isinstance(srid, (int, long)): self.srs = srid else: raise TypeError('SRID must be set with an integer.') - srid = property(get_srid, set_srid) + srid = property(_get_srid, _set_srid) #### Output Methods #### @property @@ -276,7 +284,7 @@ class OGRGeometry(object): @property def gml(self): "Returns the GML representation of the Geometry." - return to_gml(self._ptr) + return capi.to_gml(self.ptr) @property def hex(self): @@ -286,16 +294,28 @@ class OGRGeometry(object): @property def json(self): + """ + Returns the GeoJSON representation of this Geometry (requires + GDAL 1.5+). + """ if GEOJSON: - return to_json(self._ptr) + return capi.to_json(self.ptr) else: raise NotImplementedError('GeoJSON output only supported on GDAL 1.5+.') geojson = json + @property + def kml(self): + "Returns the KML representation of the Geometry." + if GEOJSON: + return capi.to_kml(self.ptr, None) + else: + raise NotImplementedError('KML output only supported on GDAL 1.5+.') + @property def wkb_size(self): "Returns the size of the WKB buffer." - return get_wkbsize(self._ptr) + return capi.get_wkbsize(self.ptr) @property def wkb(self): @@ -307,19 +327,19 @@ class OGRGeometry(object): sz = self.wkb_size # Creating the unsigned character buffer, and passing it in by reference. buf = (c_ubyte * sz)() - wkb = to_wkb(self._ptr, byteorder, byref(buf)) + wkb = capi.to_wkb(self.ptr, byteorder, byref(buf)) # Returning a buffer of the string at the pointer. return buffer(string_at(buf, sz)) @property def wkt(self): "Returns the WKT representation of the Geometry." - return to_wkt(self._ptr, byref(c_char_p())) + return capi.to_wkt(self.ptr, byref(c_char_p())) #### Geometry Methods #### def clone(self): "Clones this OGR Geometry." - return OGRGeometry(clone_geom(self._ptr), self.srs) + return OGRGeometry(capi.clone_geom(self.ptr), self.srs) def close_rings(self): """ @@ -328,7 +348,7 @@ class OGRGeometry(object): end. """ # Closing the open rings. - geom_close_rings(self._ptr) + capi.geom_close_rings(self.ptr) def transform(self, coord_trans, clone=False): """ @@ -344,12 +364,12 @@ class OGRGeometry(object): klone.transform(coord_trans) return klone if isinstance(coord_trans, CoordTransform): - geom_transform(self._ptr, coord_trans._ptr) + capi.geom_transform(self.ptr, coord_trans.ptr) elif isinstance(coord_trans, SpatialReference): - geom_transform_to(self._ptr, coord_trans._ptr) + capi.geom_transform_to(self.ptr, coord_trans.ptr) elif isinstance(coord_trans, (int, long, basestring)): sr = SpatialReference(coord_trans) - geom_transform_to(self._ptr, sr._ptr) + capi.geom_transform_to(self.ptr, sr.ptr) else: raise TypeError('Transform only accepts CoordTransform, SpatialReference, string, and integer objects.') @@ -366,52 +386,52 @@ class OGRGeometry(object): # Returning the output of the given function with the other geometry's # pointer. - return func(self._ptr, other._ptr) + return func(self.ptr, other.ptr) def intersects(self, other): "Returns True if this geometry intersects with the other." - return self._topology(ogr_intersects, other) + return self._topology(capi.ogr_intersects, other) def equals(self, other): "Returns True if this geometry is equivalent to the other." - return self._topology(ogr_equals, other) + return self._topology(capi.ogr_equals, other) def disjoint(self, other): "Returns True if this geometry and the other are spatially disjoint." - return self._topology(ogr_disjoint, other) + return self._topology(capi.ogr_disjoint, other) def touches(self, other): "Returns True if this geometry touches the other." - return self._topology(ogr_touches, other) + return self._topology(capi.ogr_touches, other) def crosses(self, other): "Returns True if this geometry crosses the other." - return self._topology(ogr_crosses, other) + return self._topology(capi.ogr_crosses, other) def within(self, other): "Returns True if this geometry is within the other." - return self._topology(ogr_within, other) + return self._topology(capi.ogr_within, other) def contains(self, other): "Returns True if this geometry contains the other." - return self._topology(ogr_contains, other) + return self._topology(capi.ogr_contains, other) def overlaps(self, other): "Returns True if this geometry overlaps the other." - return self._topology(ogr_overlaps, other) + return self._topology(capi.ogr_overlaps, other) #### Geometry-generation Methods #### def _geomgen(self, gen_func, other=None): "A helper routine for the OGR routines that generate geometries." if isinstance(other, OGRGeometry): - return OGRGeometry(gen_func(self._ptr, other._ptr), self.srs) + return OGRGeometry(gen_func(self.ptr, other.ptr), self.srs) else: - return OGRGeometry(gen_func(self._ptr), self.srs) + return OGRGeometry(gen_func(self.ptr), self.srs) @property def boundary(self): "Returns the boundary of this geometry." - return self._geomgen(get_boundary) + return self._geomgen(capi.get_boundary) @property def convex_hull(self): @@ -419,35 +439,35 @@ class OGRGeometry(object): Returns the smallest convex Polygon that contains all the points in this Geometry. """ - return self._geomgen(geom_convex_hull) + return self._geomgen(capi.geom_convex_hull) def difference(self, other): """ Returns a new geometry consisting of the region which is the difference of this geometry and the other. """ - return self._geomgen(geom_diff, other) + return self._geomgen(capi.geom_diff, other) def intersection(self, other): """ Returns a new geometry consisting of the region of intersection of this geometry and the other. """ - return self._geomgen(geom_intersection, other) + return self._geomgen(capi.geom_intersection, other) def sym_difference(self, other): """ Returns a new geometry which is the symmetric difference of this geometry and the other. """ - return self._geomgen(geom_sym_diff, other) + return self._geomgen(capi.geom_sym_diff, other) def union(self, other): """ Returns a new geometry consisting of the region which is the union of this geometry and the other. """ - return self._geomgen(geom_union, other) + return self._geomgen(capi.geom_union, other) # The subclasses for OGR Geometry. class Point(OGRGeometry): @@ -455,18 +475,18 @@ class Point(OGRGeometry): @property def x(self): "Returns the X coordinate for this Point." - return getx(self._ptr, 0) + return capi.getx(self.ptr, 0) @property def y(self): "Returns the Y coordinate for this Point." - return gety(self._ptr, 0) + return capi.gety(self.ptr, 0) @property def z(self): "Returns the Z coordinate for this Point." if self.coord_dim == 3: - return getz(self._ptr, 0) + return capi.getz(self.ptr, 0) @property def tuple(self): @@ -483,7 +503,7 @@ class LineString(OGRGeometry): "Returns the Point at the given index." if index >= 0 and index < self.point_count: x, y, z = c_double(), c_double(), c_double() - get_point(self._ptr, index, byref(x), byref(y), byref(z)) + capi.get_point(self.ptr, index, byref(x), byref(y), byref(z)) dim = self.coord_dim if dim == 1: return (x.value,) @@ -514,23 +534,23 @@ class LineString(OGRGeometry): Internal routine that returns a sequence (list) corresponding with the given function. """ - return [func(self._ptr, i) for i in xrange(len(self))] + return [func(self.ptr, i) for i in xrange(len(self))] @property def x(self): "Returns the X coordinates in a list." - return self._listarr(getx) + return self._listarr(capi.getx) @property def y(self): "Returns the Y coordinates in a list." - return self._listarr(gety) + return self._listarr(capi.gety) @property def z(self): "Returns the Z coordinates in a list." if self.coord_dim == 3: - return self._listarr(getz) + return self._listarr(capi.getz) # LinearRings are used in Polygons. class LinearRing(LineString): pass @@ -551,7 +571,7 @@ class Polygon(OGRGeometry): if index < 0 or index >= self.geom_count: raise OGRIndexError('index out of range: %s' % index) else: - return OGRGeometry(clone_geom(get_geom_ref(self._ptr, index)), self.srs) + return OGRGeometry(capi.clone_geom(capi.get_geom_ref(self.ptr, index)), self.srs) # Polygon Properties @property @@ -577,7 +597,7 @@ class Polygon(OGRGeometry): "Returns the centroid (a Point) of this Polygon." # The centroid is a Point, create a geometry for this. p = OGRGeometry(OGRGeomType('Point')) - get_centroid(self._ptr, p._ptr) + capi.get_centroid(self.ptr, p.ptr) return p # Geometry Collection base class. @@ -589,7 +609,7 @@ class GeometryCollection(OGRGeometry): if index < 0 or index >= self.geom_count: raise OGRIndexError('index out of range: %s' % index) else: - return OGRGeometry(clone_geom(get_geom_ref(self._ptr, index)), self.srs) + return OGRGeometry(capi.clone_geom(capi.get_geom_ref(self.ptr, index)), self.srs) def __iter__(self): "Iterates over each Geometry." @@ -604,12 +624,12 @@ class GeometryCollection(OGRGeometry): "Add the geometry to this Geometry Collection." if isinstance(geom, OGRGeometry): if isinstance(geom, self.__class__): - for g in geom: add_geom(self._ptr, g._ptr) + for g in geom: capi.add_geom(self.ptr, g.ptr) else: - add_geom(self._ptr, geom._ptr) + capi.add_geom(self.ptr, geom.ptr) elif isinstance(geom, basestring): tmp = OGRGeometry(geom) - add_geom(self._ptr, tmp._ptr) + capi.add_geom(self.ptr, tmp.ptr) else: raise OGRException('Must add an OGRGeometry.') diff --git a/django/contrib/gis/gdal/geomtype.py b/django/contrib/gis/gdal/geomtype.py index 565326f5a8..b3309531c0 100644 --- a/django/contrib/gis/gdal/geomtype.py +++ b/django/contrib/gis/gdal/geomtype.py @@ -24,7 +24,9 @@ class OGRGeomType(object): if isinstance(type_input, OGRGeomType): num = type_input.num elif isinstance(type_input, basestring): - num = self._str_types.get(type_input.lower(), None) + type_input = type_input.lower() + if type_input == 'geometry': type_input='unknown' + num = self._str_types.get(type_input, None) if num is None: raise OGRException('Invalid OGR String Type "%s"' % type_input) elif isinstance(type_input, int): @@ -67,7 +69,8 @@ class OGRGeomType(object): def django(self): "Returns the Django GeometryField for this OGR Type." s = self.name - if s in ('Unknown', 'LinearRing', 'None'): + if s in ('LinearRing', 'None'): return None - else: - return s + 'Field' + elif s == 'Unknown': + s = 'Geometry' + return s + 'Field' diff --git a/django/contrib/gis/gdal/layer.py b/django/contrib/gis/gdal/layer.py index 45e823dedc..cf5e57866e 100644 --- a/django/contrib/gis/gdal/layer.py +++ b/django/contrib/gis/gdal/layer.py @@ -2,26 +2,22 @@ from ctypes import byref # Other GDAL imports. +from django.contrib.gis.gdal.base import GDALBase from django.contrib.gis.gdal.envelope import Envelope, OGREnvelope from django.contrib.gis.gdal.error import OGRException, OGRIndexError, SRSException from django.contrib.gis.gdal.feature import Feature -from django.contrib.gis.gdal.field import FIELD_CLASSES +from django.contrib.gis.gdal.field import OGRFieldTypes from django.contrib.gis.gdal.geometries import OGRGeomType from django.contrib.gis.gdal.srs import SpatialReference # GDAL ctypes function prototypes. -from django.contrib.gis.gdal.prototypes.ds import \ - get_extent, get_fd_geom_type, get_fd_name, get_feature, get_feature_count, \ - get_field_count, get_field_defn, get_field_name, get_field_precision, \ - get_field_width, get_field_type, get_layer_defn, get_layer_srs, \ - get_next_feature, reset_reading, test_capability -from django.contrib.gis.gdal.prototypes.srs import clone_srs +from django.contrib.gis.gdal.prototypes import ds as capi, srs as srs_api # For more information, see the OGR C API source code: # http://www.gdal.org/ogr/ogr__api_8h.html # # The OGR_L_* routines are relevant here. -class Layer(object): +class Layer(GDALBase): "A class that wraps an OGR Layer, needs to be instantiated from a DataSource object." #### Python 'magic' routines #### @@ -32,12 +28,11 @@ class Layer(object): reference to it is kept with this Layer. This prevents garbage collection of the `DataSource` while this Layer is still active. """ - self._ptr = None # Initially NULL if not layer_ptr: raise OGRException('Cannot create Layer, invalid pointer given') - self._ptr = layer_ptr + self.ptr = layer_ptr self._ds = ds - self._ldefn = get_layer_defn(self._ptr) + self._ldefn = capi.get_layer_defn(self._ptr) # Does the Layer support random reading? self._random_read = self.test_capability('RandomRead') @@ -59,9 +54,9 @@ class Layer(object): def __iter__(self): "Iterates over each Feature in the Layer." # ResetReading() must be called before iteration is to begin. - reset_reading(self._ptr) + capi.reset_reading(self._ptr) for i in xrange(self.num_feat): - yield Feature(get_next_feature(self._ptr), self._ldefn) + yield Feature(capi.get_next_feature(self._ptr), self._ldefn) def __len__(self): "The length is the number of features." @@ -81,7 +76,7 @@ class Layer(object): if self._random_read: # If the Layer supports random reading, return. try: - return Feature(get_feature(self._ptr, feat_id), self._ldefn) + return Feature(capi.get_feature(self.ptr, feat_id), self._ldefn) except OGRException: pass else: @@ -97,35 +92,35 @@ class Layer(object): def extent(self): "Returns the extent (an Envelope) of this layer." env = OGREnvelope() - get_extent(self._ptr, byref(env), 1) + capi.get_extent(self.ptr, byref(env), 1) return Envelope(env) @property def name(self): "Returns the name of this layer in the Data Source." - return get_fd_name(self._ldefn) + return capi.get_fd_name(self._ldefn) @property def num_feat(self, force=1): "Returns the number of features in the Layer." - return get_feature_count(self._ptr, force) + return capi.get_feature_count(self.ptr, force) @property def num_fields(self): "Returns the number of fields in the Layer." - return get_field_count(self._ldefn) + return capi.get_field_count(self._ldefn) @property def geom_type(self): "Returns the geometry type (OGRGeomType) of the Layer." - return OGRGeomType(get_fd_geom_type(self._ldefn)) + return OGRGeomType(capi.get_fd_geom_type(self._ldefn)) @property def srs(self): "Returns the Spatial Reference used in this Layer." try: - ptr = get_layer_srs(self._ptr) - return SpatialReference(clone_srs(ptr)) + ptr = capi.get_layer_srs(self.ptr) + return SpatialReference(srs_api.clone_srs(ptr)) except SRSException: return None @@ -135,7 +130,7 @@ class Layer(object): Returns a list of string names corresponding to each of the Fields available in this Layer. """ - return [get_field_name(get_field_defn(self._ldefn, i)) + return [capi.get_field_name(capi.get_field_defn(self._ldefn, i)) for i in xrange(self.num_fields) ] @property @@ -146,19 +141,19 @@ class Layer(object): an OGR layer that had an integer, a floating-point, and string fields. """ - return [FIELD_CLASSES[get_field_type(get_field_defn(self._ldefn, i))] + return [OGRFieldTypes[capi.get_field_type(capi.get_field_defn(self._ldefn, i))] for i in xrange(self.num_fields)] @property def field_widths(self): "Returns a list of the maximum field widths for the features." - return [get_field_width(get_field_defn(self._ldefn, i)) + return [capi.get_field_width(capi.get_field_defn(self._ldefn, i)) for i in xrange(self.num_fields)] @property def field_precisions(self): "Returns the field precisions for the features." - return [get_field_precision(get_field_defn(self._ldefn, i)) + return [capi.get_field_precision(capi.get_field_defn(self._ldefn, i)) for i in xrange(self.num_fields)] #### Layer Methods #### @@ -190,4 +185,4 @@ class Layer(object): 'FastFeatureCount', 'FastGetExtent', 'CreateField', 'Transactions', 'DeleteFeature', and 'FastSetNextByIndex'. """ - return bool(test_capability(self._ptr, capability)) + return bool(capi.test_capability(self.ptr, capability)) diff --git a/django/contrib/gis/gdal/prototypes/geom.py b/django/contrib/gis/gdal/prototypes/geom.py index 7757f557ad..2898198d0d 100644 --- a/django/contrib/gis/gdal/prototypes/geom.py +++ b/django/contrib/gis/gdal/prototypes/geom.py @@ -38,9 +38,11 @@ def topology_func(f): if GEOJSON: from_json = geom_output(lgdal.OGR_G_CreateGeometryFromJson, [c_char_p]) to_json = string_output(lgdal.OGR_G_ExportToJson, [c_void_p], str_result=True) + to_kml = string_output(lgdal.OGR_G_ExportToKML, [c_void_p, c_char_p], str_result=True) else: from_json = False to_json = False + to_kml = False # GetX, GetY, GetZ all return doubles. getx = pnt_func(lgdal.OGR_G_GetX) diff --git a/django/contrib/gis/gdal/prototypes/srs.py b/django/contrib/gis/gdal/prototypes/srs.py index ff4d0add95..411cec903c 100644 --- a/django/contrib/gis/gdal/prototypes/srs.py +++ b/django/contrib/gis/gdal/prototypes/srs.py @@ -36,6 +36,7 @@ from_wkt = void_output(lgdal.OSRImportFromWkt, [c_void_p, POINTER(c_char_p)]) from_proj = void_output(lgdal.OSRImportFromProj4, [c_void_p, c_char_p]) from_epsg = void_output(std_call('OSRImportFromEPSG'), [c_void_p, c_int]) from_xml = void_output(lgdal.OSRImportFromXML, [c_void_p, c_char_p]) +from_user_input = void_output(std_call('OSRSetFromUserInput'), [c_void_p, c_char_p]) # Morphing to/from ESRI WKT. morph_to_esri = void_output(lgdal.OSRMorphToESRI, [c_void_p]) diff --git a/django/contrib/gis/gdal/srs.py b/django/contrib/gis/gdal/srs.py index d70e71ebc7..93bd8416ff 100644 --- a/django/contrib/gis/gdal/srs.py +++ b/django/contrib/gis/gdal/srs.py @@ -27,89 +27,74 @@ NAD83 / Texas South Central """ import re -from types import UnicodeType, TupleType from ctypes import byref, c_char_p, c_int, c_void_p # Getting the error checking routine and exceptions +from django.contrib.gis.gdal.base import GDALBase from django.contrib.gis.gdal.error import OGRException, SRSException -from django.contrib.gis.gdal.prototypes.srs import * +from django.contrib.gis.gdal.prototypes import srs as capi #### Spatial Reference class. #### -class SpatialReference(object): +class SpatialReference(GDALBase): """ A wrapper for the OGRSpatialReference object. According to the GDAL website, the SpatialReference object "provide[s] services to represent coordinate systems (projections and datums) and to transform between them." """ - # Well-Known Geographical Coordinate System Name - _well_known = {'WGS84':4326, 'WGS72':4322, 'NAD27':4267, 'NAD83':4269} - _epsg_regex = re.compile('^(EPSG:)?(?P\d+)$', re.I) - _proj_regex = re.compile(r'^\+proj') - #### Python 'magic' routines #### - def __init__(self, srs_input='', srs_type='wkt'): + def __init__(self, srs_input=''): """ Creates a GDAL OSR Spatial Reference object from the given input. The input may be string of OGC Well Known Text (WKT), an integer EPSG code, a PROJ.4 string, and/or a projection "well known" shorthand string (one of 'WGS84', 'WGS72', 'NAD27', 'NAD83'). """ - # Intializing pointer and string buffer. - self._ptr = None buf = c_char_p('') + srs_type = 'user' if isinstance(srs_input, basestring): # Encoding to ASCII if unicode passed in. - if isinstance(srs_input, UnicodeType): + if isinstance(srs_input, unicode): srs_input = srs_input.encode('ascii') - - epsg_m = self._epsg_regex.match(srs_input) - proj_m = self._proj_regex.match(srs_input) - if epsg_m: - # Is this an EPSG well known name? - srs_type = 'epsg' - srs_input = int(epsg_m.group('epsg')) - elif proj_m: - # Is the string a PROJ.4 string? - srs_type = 'proj' - elif srs_input in self._well_known: - # Is this a short-hand well known name? - srs_type = 'epsg' - srs_input = self._well_known[srs_input] - elif srs_type == 'proj': + try: + # If SRID is a string, e.g., '4326', then make acceptable + # as user input. + srid = int(srs_input) + srs_input = 'EPSG:%d' % srid + except ValueError: pass - else: - # Setting the buffer with WKT, PROJ.4 string, etc. - buf = c_char_p(srs_input) - elif isinstance(srs_input, int): + elif isinstance(srs_input, (int, long)): # EPSG integer code was input. - if srs_type != 'epsg': srs_type = 'epsg' - elif isinstance(srs_input, c_void_p): + srs_type = 'epsg' + elif isinstance(srs_input, self.ptr_type): + srs = srs_input srs_type = 'ogr' else: raise TypeError('Invalid SRS type "%s"' % srs_type) if srs_type == 'ogr': - # SRS input is OGR pointer + # Input is already an SRS pointer. srs = srs_input else: - # Creating a new pointer, using the string buffer. - srs = new_srs(buf) + # Creating a new SRS pointer, using the string buffer. + srs = capi.new_srs(buf) # If the pointer is NULL, throw an exception. if not srs: raise SRSException('Could not create spatial reference from: %s' % srs_input) else: - self._ptr = srs + self.ptr = srs - # Post-processing if in PROJ.4 or EPSG formats. - if srs_type == 'proj': self.import_proj(srs_input) - elif srs_type == 'epsg': self.import_epsg(srs_input) + # Importing from either the user input string or an integer SRID. + if srs_type == 'user': + self.import_user_input(srs_input) + elif srs_type == 'epsg': + self.import_epsg(srs_input) def __del__(self): "Destroys this spatial reference." - if self._ptr: release_srs(self._ptr) + if self._ptr: capi.release_srs(self._ptr) def __getitem__(self, target): """ @@ -134,7 +119,7 @@ class SpatialReference(object): >>> print srs['UNIT|AUTHORITY', 1] # The authority value for the untis 9122 """ - if isinstance(target, TupleType): + if isinstance(target, tuple): return self.attr_value(*target) else: return self.attr_value(target) @@ -149,40 +134,40 @@ class SpatialReference(object): The attribute value for the given target node (e.g. 'PROJCS'). The index keyword specifies an index of the child node to return. """ - if not isinstance(target, str) or not isinstance(index, int): + if not isinstance(target, basestring) or not isinstance(index, int): raise TypeError - return get_attr_value(self._ptr, target, index) + return capi.get_attr_value(self.ptr, target, index) def auth_name(self, target): "Returns the authority name for the given string target node." - return get_auth_name(self._ptr, target) + return capi.get_auth_name(self.ptr, target) def auth_code(self, target): "Returns the authority code for the given string target node." - return get_auth_code(self._ptr, target) + return capi.get_auth_code(self.ptr, target) def clone(self): "Returns a clone of this SpatialReference object." - return SpatialReference(clone_srs(self._ptr)) + return SpatialReference(capi.clone_srs(self.ptr)) def from_esri(self): "Morphs this SpatialReference from ESRI's format to EPSG." - morph_from_esri(self._ptr) + capi.morph_from_esri(self.ptr) def identify_epsg(self): """ This method inspects the WKT of this SpatialReference, and will add EPSG authority nodes where an EPSG identifier is applicable. """ - identify_epsg(self._ptr) + capi.identify_epsg(self.ptr) def to_esri(self): "Morphs this SpatialReference to ESRI's format." - morph_to_esri(self._ptr) + capi.morph_to_esri(self.ptr) def validate(self): "Checks to see if the given spatial reference is valid." - srs_validate(self._ptr) + capi.srs_validate(self.ptr) #### Name & SRID properties #### @property @@ -205,25 +190,25 @@ class SpatialReference(object): @property def linear_name(self): "Returns the name of the linear units." - units, name = linear_units(self._ptr, byref(c_char_p())) + units, name = capi.linear_units(self.ptr, byref(c_char_p())) return name @property def linear_units(self): "Returns the value of the linear units." - units, name = linear_units(self._ptr, byref(c_char_p())) + units, name = capi.linear_units(self.ptr, byref(c_char_p())) return units @property def angular_name(self): "Returns the name of the angular units." - units, name = angular_units(self._ptr, byref(c_char_p())) + units, name = capi.angular_units(self.ptr, byref(c_char_p())) return name @property def angular_units(self): "Returns the value of the angular units." - units, name = angular_units(self._ptr, byref(c_char_p())) + units, name = capi.angular_units(self.ptr, byref(c_char_p())) return units @property @@ -234,9 +219,9 @@ class SpatialReference(object): or angular units. """ if self.projected or self.local: - return linear_units(self._ptr, byref(c_char_p())) + return capi.linear_units(self.ptr, byref(c_char_p())) elif self.geographic: - return angular_units(self._ptr, byref(c_char_p())) + return capi.angular_units(self.ptr, byref(c_char_p())) else: return (None, None) @@ -252,17 +237,17 @@ class SpatialReference(object): @property def semi_major(self): "Returns the Semi Major Axis for this Spatial Reference." - return semi_major(self._ptr, byref(c_int())) + return capi.semi_major(self.ptr, byref(c_int())) @property def semi_minor(self): "Returns the Semi Minor Axis for this Spatial Reference." - return semi_minor(self._ptr, byref(c_int())) + return capi.semi_minor(self.ptr, byref(c_int())) @property def inverse_flattening(self): "Returns the Inverse Flattening for this Spatial Reference." - return invflattening(self._ptr, byref(c_int())) + return capi.invflattening(self.ptr, byref(c_int())) #### Boolean Properties #### @property @@ -271,12 +256,12 @@ class SpatialReference(object): Returns True if this SpatialReference is geographic (root node is GEOGCS). """ - return bool(isgeographic(self._ptr)) + return bool(capi.isgeographic(self.ptr)) @property def local(self): "Returns True if this SpatialReference is local (root node is LOCAL_CS)." - return bool(islocal(self._ptr)) + return bool(capi.islocal(self.ptr)) @property def projected(self): @@ -284,40 +269,44 @@ class SpatialReference(object): Returns True if this SpatialReference is a projected coordinate system (root node is PROJCS). """ - return bool(isprojected(self._ptr)) + return bool(capi.isprojected(self.ptr)) #### Import Routines ##### - def import_wkt(self, wkt): - "Imports the Spatial Reference from OGC WKT (string)" - from_wkt(self._ptr, byref(c_char_p(wkt))) + def import_epsg(self, epsg): + "Imports the Spatial Reference from the EPSG code (an integer)." + capi.from_epsg(self.ptr, epsg) def import_proj(self, proj): "Imports the Spatial Reference from a PROJ.4 string." - from_proj(self._ptr, proj) + capi.from_proj(self.ptr, proj) - def import_epsg(self, epsg): - "Imports the Spatial Reference from the EPSG code (an integer)." - from_epsg(self._ptr, epsg) + def import_user_input(self, user_input): + "Imports the Spatial Reference from the given user input string." + capi.from_user_input(self.ptr, user_input) + + def import_wkt(self, wkt): + "Imports the Spatial Reference from OGC WKT (string)" + capi.from_wkt(self.ptr, byref(c_char_p(wkt))) def import_xml(self, xml): "Imports the Spatial Reference from an XML string." - from_xml(self._ptr, xml) + capi.from_xml(self.ptr, xml) #### Export Properties #### @property def wkt(self): "Returns the WKT representation of this Spatial Reference." - return to_wkt(self._ptr, byref(c_char_p())) + return capi.to_wkt(self.ptr, byref(c_char_p())) @property def pretty_wkt(self, simplify=0): "Returns the 'pretty' representation of the WKT." - return to_pretty_wkt(self._ptr, byref(c_char_p()), simplify) + return capi.to_pretty_wkt(self.ptr, byref(c_char_p()), simplify) @property def proj(self): "Returns the PROJ.4 representation for this Spatial Reference." - return to_proj(self._ptr, byref(c_char_p())) + return capi.to_proj(self.ptr, byref(c_char_p())) @property def proj4(self): @@ -327,34 +316,22 @@ class SpatialReference(object): @property def xml(self, dialect=''): "Returns the XML representation of this Spatial Reference." - # FIXME: This leaks memory, have to figure out why. - return to_xml(self._ptr, byref(c_char_p()), dialect) + return capi.to_xml(self.ptr, byref(c_char_p()), dialect) - def to_esri(self): - "Morphs this SpatialReference to ESRI's format." - morph_to_esri(self._ptr) - - def from_esri(self): - "Morphs this SpatialReference from ESRI's format to EPSG." - morph_from_esri(self._ptr) - -class CoordTransform(object): +class CoordTransform(GDALBase): "The coordinate system transformation object." def __init__(self, source, target): "Initializes on a source and target SpatialReference objects." - self._ptr = None # Initially NULL if not isinstance(source, SpatialReference) or not isinstance(target, SpatialReference): - raise SRSException('source and target must be of type SpatialReference') - self._ptr = new_ct(source._ptr, target._ptr) - if not self._ptr: - raise SRSException('could not intialize CoordTransform object') + raise TypeError('source and target must be of type SpatialReference') + self.ptr = capi.new_ct(source._ptr, target._ptr) self._srs1_name = source.name self._srs2_name = target.name def __del__(self): "Deletes this Coordinate Transformation object." - if self._ptr: destroy_ct(self._ptr) + if self._ptr: capi.destroy_ct(self._ptr) def __str__(self): return 'Transform from "%s" to "%s"' % (self._srs1_name, self._srs2_name) diff --git a/django/contrib/gis/tests/test_gdal.py b/django/contrib/gis/gdal/tests/__init__.py similarity index 56% rename from django/contrib/gis/tests/test_gdal.py rename to django/contrib/gis/gdal/tests/__init__.py index 030ac505fe..aada5f40fc 100644 --- a/django/contrib/gis/tests/test_gdal.py +++ b/django/contrib/gis/gdal/tests/__init__.py @@ -5,16 +5,13 @@ of these tests require the use of the database. 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 - +import test_driver, test_ds, test_envelope, test_geom, test_srs -test_suites = [test_gdal_driver.suite(), - test_gdal_ds.suite(), - test_gdal_envelope.suite(), - test_gdal_geom.suite(), - test_gdal_srs.suite(), +test_suites = [test_driver.suite(), + test_ds.suite(), + test_envelope.suite(), + test_geom.suite(), + test_srs.suite(), ] def suite(): diff --git a/django/contrib/gis/tests/test_gdal_driver.py b/django/contrib/gis/gdal/tests/test_driver.py similarity index 100% rename from django/contrib/gis/tests/test_gdal_driver.py rename to django/contrib/gis/gdal/tests/test_driver.py diff --git a/django/contrib/gis/tests/test_gdal_ds.py b/django/contrib/gis/gdal/tests/test_ds.py similarity index 98% rename from django/contrib/gis/tests/test_gdal_ds.py rename to django/contrib/gis/gdal/tests/test_ds.py index 0be0984ff5..30ce462475 100644 --- a/django/contrib/gis/tests/test_gdal_ds.py +++ b/django/contrib/gis/gdal/tests/test_ds.py @@ -1,10 +1,13 @@ import os, os.path, unittest from django.contrib.gis.gdal import DataSource, Envelope, OGRException, OGRIndexError from django.contrib.gis.gdal.field import OFTReal, OFTInteger, OFTString +from django.contrib import gis # Path for SHP files -data_path = os.path.join(os.path.dirname(__file__), 'data') +data_path = os.path.join(os.path.dirname(gis.__file__), 'tests' + os.sep + 'data') def get_ds_file(name, ext): + + return os.sep.join([data_path, name, name + '.%s' % ext]) # Test SHP data source object diff --git a/django/contrib/gis/gdal/tests/test_envelope.py b/django/contrib/gis/gdal/tests/test_envelope.py new file mode 100644 index 0000000000..6c0c13724c --- /dev/null +++ b/django/contrib/gis/gdal/tests/test_envelope.py @@ -0,0 +1,94 @@ +import unittest +from django.contrib.gis.gdal import Envelope, OGRException + +class TestPoint(object): + def __init__(self, x, y): + self.x = x + self.y = y + +class EnvelopeTest(unittest.TestCase): + + def setUp(self): + self.e = Envelope(0, 0, 5, 5) + + 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') + self.assertRaises(OGRException, Envelope, (1, 1, 0, 0)) + try: + Envelope(0, 0, 0, 0) + except OGRException: + self.fail("shouldn't raise an exception for min_x == max_x or min_y == max_y") + + 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 test04_expand_to_include_pt_2_params(self): + "Testing Envelope expand_to_include -- point as two parameters." + self.e.expand_to_include(2, 6) + self.assertEqual((0, 0, 5, 6), self.e) + self.e.expand_to_include(-1, -1) + self.assertEqual((-1, -1, 5, 6), self.e) + + def test05_expand_to_include_pt_2_tuple(self): + "Testing Envelope expand_to_include -- point as a single 2-tuple parameter." + self.e.expand_to_include((10, 10)) + self.assertEqual((0, 0, 10, 10), self.e) + self.e.expand_to_include((-10, -10)) + self.assertEqual((-10, -10, 10, 10), self.e) + + def test06_expand_to_include_extent_4_params(self): + "Testing Envelope expand_to_include -- extent as 4 parameters." + self.e.expand_to_include(-1, 1, 3, 7) + self.assertEqual((-1, 0, 5, 7), self.e) + + def test06_expand_to_include_extent_4_tuple(self): + "Testing Envelope expand_to_include -- extent as a single 4-tuple parameter." + self.e.expand_to_include((-1, 1, 3, 7)) + self.assertEqual((-1, 0, 5, 7), self.e) + + def test07_expand_to_include_envelope(self): + "Testing Envelope expand_to_include with Envelope as parameter." + self.e.expand_to_include(Envelope(-1, 1, 3, 7)) + self.assertEqual((-1, 0, 5, 7), self.e) + + def test08_expand_to_include_point(self): + "Testing Envelope expand_to_include with Point as parameter." + self.e.expand_to_include(TestPoint(-1, 1)) + self.assertEqual((-1, 0, 5, 5), self.e) + self.e.expand_to_include(TestPoint(10, 10)) + self.assertEqual((-1, 0, 10, 10), self.e) + +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/gdal/tests/test_geom.py similarity index 96% rename from django/contrib/gis/tests/test_gdal_geom.py rename to django/contrib/gis/gdal/tests/test_geom.py index 8f5caa9615..c920adc6c0 100644 --- a/django/contrib/gis/tests/test_gdal_geom.py +++ b/django/contrib/gis/gdal/tests/test_geom.py @@ -7,7 +7,7 @@ from django.contrib.gis.tests.geometries import * class OGRGeomTest(unittest.TestCase): "This tests the OGR Geometry." - def test00_geomtype(self): + def test00a_geomtype(self): "Testing OGRGeomType object." # OGRGeomType should initialize on all these inputs. @@ -22,9 +22,9 @@ class OGRGeomTest(unittest.TestCase): self.fail('Could not create an OGRGeomType object!') # Should throw TypeError on this input - self.assertRaises(TypeError, OGRGeomType.__init__, 23) - self.assertRaises(TypeError, OGRGeomType.__init__, 'fooD') - self.assertRaises(TypeError, OGRGeomType.__init__, 9) + self.assertRaises(OGRException, OGRGeomType, 23) + self.assertRaises(OGRException, OGRGeomType, 'fooD') + self.assertRaises(OGRException, OGRGeomType, 9) # Equivalence can take strings, ints, and other OGRGeomTypes self.assertEqual(True, OGRGeomType(1) == OGRGeomType(1)) @@ -38,9 +38,14 @@ class OGRGeomTest(unittest.TestCase): # Testing the Django field name equivalent property. self.assertEqual('PointField', OGRGeomType('Point').django) - self.assertEqual(None, OGRGeomType('Unknown').django) + self.assertEqual('GeometryField', OGRGeomType('Unknown').django) self.assertEqual(None, OGRGeomType('none').django) + # 'Geometry' initialization implies an unknown geometry type. + gt = OGRGeomType('Geometry') + self.assertEqual(0, gt.num) + self.assertEqual('Unknown', gt.name) + def test01a_wkt(self): "Testing WKT output." for g in wkt_out: @@ -165,6 +170,12 @@ class OGRGeomTest(unittest.TestCase): def test07a_polygons(self): "Testing Polygon objects." + + # Testing `from_bbox` class method + bbox = (-180,-90,180,90) + p = OGRGeometry.from_bbox( bbox ) + self.assertEqual(bbox, p.extent) + prev = OGRGeometry('POINT(0 0)') for p in polygons: poly = OGRGeometry(p.wkt) diff --git a/django/contrib/gis/tests/test_gdal_srs.py b/django/contrib/gis/gdal/tests/test_srs.py similarity index 100% rename from django/contrib/gis/tests/test_gdal_srs.py rename to django/contrib/gis/gdal/tests/test_srs.py diff --git a/django/contrib/gis/tests/__init__.py b/django/contrib/gis/tests/__init__.py index 53674afa62..a2abee9173 100644 --- a/django/contrib/gis/tests/__init__.py +++ b/django/contrib/gis/tests/__init__.py @@ -9,42 +9,45 @@ def geo_suite(): """ from django.conf import settings from django.contrib.gis.tests.utils import mysql, oracle, postgis - from django.contrib.gis.gdal import HAS_GDAL - from django.contrib.gis.utils import HAS_GEOIP + from django.contrib.gis import gdal, utils - # Tests that require use of a spatial database (e.g., creation of models) - test_models = ['geoapp',] + # The test suite. + s = unittest.TestSuite() - # Tests that do not require setting up and tearing down a spatial database. + # Adding the GEOS tests. (__future__) + #from django.contrib.gis.geos import tests as geos_tests + #s.addTest(geos_tests.suite()) + + # Test apps that require use of a spatial database (e.g., creation of models) + test_apps = ['geoapp', 'relatedapp'] + if oracle or postgis: + test_apps.append('distapp') + + # Tests that do not require setting up and tearing down a spatial database + # and are modules in `django.contrib.gis.tests`. test_suite_names = [ 'test_geos', 'test_measure', ] - if HAS_GDAL: - if oracle or postgis: - test_models += ['distapp', 'layermap', 'relatedapp'] - elif mysql: - test_models += ['relatedapp', 'layermap'] - test_suite_names += [ - 'test_gdal_driver', - 'test_gdal_ds', - 'test_gdal_envelope', - 'test_gdal_geom', - 'test_gdal_srs', - 'test_spatialrefsys', - ] + if gdal.HAS_GDAL: + # These tests require GDAL. + test_suite_names.append('test_spatialrefsys') + test_apps.append('layermap') + + # Adding the GDAL tests. + from django.contrib.gis.gdal import tests as gdal_tests + s.addTest(gdal_tests.suite()) else: print >>sys.stderr, "GDAL not available - no GDAL tests will be run." - if HAS_GEOIP and hasattr(settings, 'GEOIP_PATH'): + if utils.HAS_GEOIP and hasattr(settings, 'GEOIP_PATH'): test_suite_names.append('test_geoip') - s = unittest.TestSuite() - for test_suite in test_suite_names: - tsuite = getattr(__import__('django.contrib.gis.tests', globals(), locals(), [test_suite]),test_suite) + for suite_name in test_suite_names: + tsuite = getattr(__import__('django.contrib.gis.tests', globals(), locals(), [suite_name]), suite_name) s.addTest(tsuite.suite()) - return s, test_models + return s, test_apps def run_gis_tests(test_labels, **kwargs): """ @@ -80,9 +83,9 @@ def run_gis_tests(test_labels, **kwargs): # Creating the test suite, adding the test models to INSTALLED_APPS, and # adding the model test suites to our suite package. - gis_suite, test_models = geo_suite() - for test_model in test_models: - module_name = 'django.contrib.gis.tests.%s' % test_model + gis_suite, test_apps = geo_suite() + for test_app in test_apps: + module_name = 'django.contrib.gis.tests.%s' % test_app if mysql: test_module_name = 'tests_mysql' else: @@ -90,13 +93,13 @@ def run_gis_tests(test_labels, **kwargs): new_installed.append(module_name) # Getting the model test suite - tsuite = getattr(__import__('django.contrib.gis.tests.%s' % test_model, globals(), locals(), [test_module_name]), + tsuite = getattr(__import__('django.contrib.gis.tests.%s' % test_app, globals(), locals(), [test_module_name]), test_module_name) gis_suite.addTest(tsuite.suite()) - # Resetting the loaded flag to take into account what we appended to - # the INSTALLED_APPS (since this routine is invoked through - # django/core/management, it caches the apps; this ensures that syncdb + # Resetting the loaded flag to take into account what we appended to + # the INSTALLED_APPS (since this routine is invoked through + # django/core/management, it caches the apps; this ensures that syncdb # will see our appended models) settings.INSTALLED_APPS = new_installed loading.cache.loaded = False @@ -198,3 +201,25 @@ def run_tests(test_labels, verbosity=1, interactive=True, extra_tests=[], suite= # Returning the total failures and errors return len(result.failures) + len(result.errors) + +# Class for creating a fake module with a run method. This is for the +# GEOS and GDAL tests that were moved to their respective modules. +class _DeprecatedTestModule(object): + def __init__(self, tests, mod): + self.tests = tests + self.mod = mod + + def run(self): + from warnings import warn + warn('This test module is deprecated because it has moved to ' \ + '`django.contrib.gis.%s.tests` and will disappear in 1.2.' % + self.mod, DeprecationWarning) + self.tests.run() + +#from django.contrib.gis.geos import tests as _tests +#test_geos = _DeprecatedTestModule(_tests, 'geos') + +from django.contrib.gis.gdal import HAS_GDAL +if HAS_GDAL: + from django.contrib.gis.gdal import tests as _tests + test_gdal = _DeprecatedTestModule(_tests, 'gdal') diff --git a/django/contrib/gis/tests/test_gdal_envelope.py b/django/contrib/gis/tests/test_gdal_envelope.py deleted file mode 100644 index fda27191ac..0000000000 --- a/django/contrib/gis/tests/test_gdal_envelope.py +++ /dev/null @@ -1,45 +0,0 @@ -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())