From ef32f913a0849e06c81302c323d51080c4a9e88e Mon Sep 17 00:00:00 2001 From: Justin Bronn Date: Sat, 17 Nov 2007 21:38:36 +0000 Subject: [PATCH] gis: gdal: refactor of the GDAL ctypes interface (1) All interactions with the GDAL library take place through predefined ctypes prototypes, abstracting away error-checking. (2) Fixed memory leaks by properly freeing pointers allocated w/in GDAL. (3) Improved OFTField support, and added support for the OGR date/time fields. (4) Significantly improved the OGRGeometry tests. git-svn-id: http://code.djangoproject.com/svn/django/branches/gis@6686 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/gis/gdal/__init__.py | 4 +- django/contrib/gis/gdal/datasource.py | 137 +++--- django/contrib/gis/gdal/driver.py | 60 +-- django/contrib/gis/gdal/envelope.py | 23 +- django/contrib/gis/gdal/error.py | 13 +- django/contrib/gis/gdal/feature.py | 74 ++-- django/contrib/gis/gdal/field.py | 149 +++++-- django/contrib/gis/gdal/geometries.py | 398 +++++++++--------- django/contrib/gis/gdal/geomtype.py | 13 +- django/contrib/gis/gdal/layer.py | 86 ++-- django/contrib/gis/gdal/libgdal.py | 13 +- .../contrib/gis/gdal/prototypes/__init__.py | 16 + django/contrib/gis/gdal/prototypes/ds.py | 67 +++ .../contrib/gis/gdal/prototypes/errcheck.py | 125 ++++++ .../contrib/gis/gdal/prototypes/generation.py | 113 +++++ django/contrib/gis/gdal/prototypes/geom.py | 95 +++++ django/contrib/gis/gdal/prototypes/srs.py | 70 +++ django/contrib/gis/gdal/srs.py | 230 ++++------ django/contrib/gis/tests/test_gdal_ds.py | 17 +- django/contrib/gis/tests/test_gdal_geom.py | 140 +++++- 20 files changed, 1226 insertions(+), 617 deletions(-) create mode 100644 django/contrib/gis/gdal/prototypes/__init__.py create mode 100644 django/contrib/gis/gdal/prototypes/ds.py create mode 100644 django/contrib/gis/gdal/prototypes/errcheck.py create mode 100644 django/contrib/gis/gdal/prototypes/generation.py create mode 100644 django/contrib/gis/gdal/prototypes/geom.py create mode 100644 django/contrib/gis/gdal/prototypes/srs.py diff --git a/django/contrib/gis/gdal/__init__.py b/django/contrib/gis/gdal/__init__.py index 32d9da73d1..2fc4d4f7e5 100644 --- a/django/contrib/gis/gdal/__init__.py +++ b/django/contrib/gis/gdal/__init__.py @@ -21,8 +21,8 @@ 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. +# 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.datasource import DataSource diff --git a/django/contrib/gis/gdal/datasource.py b/django/contrib/gis/gdal/datasource.py index d754821143..f2b519afc7 100644 --- a/django/contrib/gis/gdal/datasource.py +++ b/django/contrib/gis/gdal/datasource.py @@ -1,105 +1,113 @@ -# types and ctypes -from types import StringType -from ctypes import c_char_p, c_int, c_void_p, byref, string_at +""" + DataSource is a wrapper for the OGR Data Source object, which provides + an interface for reading vector geometry data from many different file + formats (including ESRI shapefiles). + + When instantiating a DataSource object, use the filename of a + GDAL-supported data source. For example, a SHP file or a + TIGER/Line file from the government. + + The ds_driver keyword is used internally when a ctypes pointer + is passed in directly. + + Example: + ds = DataSource('/home/foo/bar.shp') + for layer in ds: + for feature in layer: + # Getting the geometry for the feature. + g = feature.geom + + # Getting the 'description' field for the feature. + desc = feature['description'] + + # We can also increment through all of the fields + # attached to this feature. + for field in feature: + # Get the name of the field (e.g. 'description') + nm = field.name + + # Get the type (integer) of the field, e.g. 0 => OFTInteger + t = field.type + + # Returns the value the field; OFTIntegers return ints, + # OFTReal returns floats, all else returns string. + val = field.value +""" +# ctypes prerequisites. +from ctypes import byref, c_void_p # 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, OGRIndexError, check_err -from django.contrib.gis.gdal.layer import Layer 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 -""" - DataSource is a wrapper for the OGR Data Source object, which provides - an interface for reading vector geometry data from many different file - formats (including ESRI shapefiles). - - When instantiating a DataSource object, use the filename of a - GDAL-supported data source. For example, a SHP file or a - TIGER/Line file from the government. - - The ds_driver keyword is used internally when a ctypes pointer - is passed in directly. - - Example: - ds = DataSource('/home/foo/bar.shp') - for layer in ds: - for feature in layer: - # Getting the geometry for the feature. - g = feature.geom - - # Getting the 'description' field for the feature. - desc = feature['description'] - - # We can also increment through all of the fields - # attached to this feature. - for field in feature: - # Get the name of the field (e.g. 'description') - nm = field.name - - # Get the type (integer) of the field, e.g. 0 => OFTInteger - t = field.type - - # Returns the value the field; OFTIntegers return ints, - # OFTReal returns floats, all else returns string. - val = field.value -""" +# 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 # 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): "Wraps an OGR Data Source object." #### Python 'magic' routines #### - def __init__(self, ds_input, ds_driver=False): + def __init__(self, ds_input, ds_driver=False, write=False): - self._ds = None # Initially NULL + # DataSource pointer is initially NULL. + self._ptr = None + + # The write flag. + if write: + self._write = 1 + else: + self._write = 0 # 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(): + if not get_driver_count() and not register_all(): raise OGRException('Could not register all the OGR data source drivers!') - if isinstance(ds_input, StringType): - + if isinstance(ds_input, basestring): # The data source driver is a void pointer. ds_driver = c_void_p() # OGROpen will auto-detect the data source type. - ds = lgdal.OGROpen(c_char_p(ds_input), c_int(0), byref(ds_driver)) + ds = open_ds(ds_input, self._write, byref(ds_driver)) 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' % 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) - else: - self._ds = ds + if bool(ds): + 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): - "This releases the reference to the data source (destroying it if it's the only one)." - if self._ds: lgdal.OGRReleaseDataSource(self._ds) + "Destroys this DataStructure object." + if self._ptr: destroy_ds(self._ptr) def __iter__(self): "Allows for iteration over the layers in a data source." for i in xrange(self.layer_count): - yield self.__getitem__(i) + yield self[i] def __getitem__(self, index): "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 isinstance(index, basestring): + l = get_layer_by_name(self._ptr, index) if not l: raise OGRIndexError('invalid OGR Layer name given: "%s"' % index) - else: + elif isinstance(index, int): if index < 0 or index >= self.layer_count: raise OGRIndexError('index out of range') - l = lgdal.OGR_DS_GetLayer(self._ds, c_int(index)) + l = get_layer(self._ptr, index) + else: + raise TypeError('Invalid index type: %s' % type(index)) return Layer(l) def __len__(self): @@ -119,10 +127,9 @@ class DataSource(object): @property def layer_count(self): "Returns the number of layers in the data source." - return lgdal.OGR_DS_GetLayerCount(self._ds) + return get_layer_count(self._ptr) @property def name(self): "Returns the name of the data source." - return string_at(lgdal.OGR_DS_GetName(self._ds)) - + return get_ds_name(self._ptr) diff --git a/django/contrib/gis/gdal/driver.py b/django/contrib/gis/gdal/driver.py index 6123bb45e3..bfb87fa9f6 100644 --- a/django/contrib/gis/gdal/driver.py +++ b/django/contrib/gis/gdal/driver.py @@ -1,16 +1,13 @@ -# types and ctypes -from types import StringType -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 +# prerequisites imports +from ctypes import c_void_p 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 # 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): "Wraps an OGR Data Source Driver." @@ -22,64 +19,49 @@ class Driver(object): 'tiger/line' : 'TIGER', } - def __init__(self, input, ptr=False): + def __init__(self, dr_input): "Initializes an OGR driver on either a string or integer input." - if isinstance(input, StringType): + if isinstance(dr_input, basestring): # If a string name of the driver was passed in - self._dr = None # Initially NULL + self._ptr = None # Initially NULL self._register() # Checking the alias dictionary (case-insensitive) to see if an alias # exists for the given driver. - if input.lower() in self._alias: - name = c_char_p(self._alias[input.lower()]) + if dr_input.lower() in self._alias: + name = self._alias[dr_input.lower()] else: - name = c_char_p(input) + name = dr_input # Attempting to get the OGR driver by the string name. - dr = lgdal.OGRGetDriverByName(name) - elif isinstance(input, int): + dr = get_driver_by_name(name) + elif isinstance(dr_input, int): self._register() - dr = lgdal.OGRGetDriver(c_int(input)) - elif isinstance(input, c_void_p): - dr = input + dr = get_driver(dr_input) + elif isinstance(dr_input, c_void_p): + dr = 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(dr_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)) - self._dr = dr + raise OGRException('Could not initialize OGR Driver on input: %s' % str(dr_input)) + self._ptr = dr def __str__(self): "Returns the string name of the OGR Driver." - return string_at(lgdal.OGR_Dr_GetName(self._dr)) + return 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 and not lgdal.OGRRegisterAll(): + if not self.driver_count and not register_all(): raise OGRException('Could not register all the OGR data source drivers!') # Driver properties @property def driver_count(self): "Returns the number of OGR data source drivers registered." - return lgdal.OGRGetDriverCount() - - def create_ds(self, **kwargs): - "Creates a data source using the keyword args as name value options." - raise NotImplementedError - # Getting the options string - #options = '' - #n_opts = len(kwargs) - #for i in xrange(n_opts): - # options += '%s=%s' % (str(k), str(v)) - # if i < n_opts-1: options += ',' - #opts = c_char_p(options) - - - - + return get_driver_count() diff --git a/django/contrib/gis/gdal/envelope.py b/django/contrib/gis/gdal/envelope.py index ed210d882f..971f06da90 100644 --- a/django/contrib/gis/gdal/envelope.py +++ b/django/contrib/gis/gdal/envelope.py @@ -1,15 +1,14 @@ """ The GDAL/OGR library uses an Envelope structure to hold the bounding - box information for a geometry. The envelope (bounding box) contains - two pairs of coordinates, one for the lower left coordinate and one - for the upper right coordinate: + box information for a geometry. The envelope (bounding box) contains + two pairs of coordinates, one for the lower left coordinate and one + for the upper right coordinate: - +----------o Upper right; (max_x, max_y) - | | - | | - | | - Lower left (min_x, min_y) o----------+ - + +----------o Upper right; (max_x, max_y) + | | + | | + | | + Lower left (min_x, min_y) o----------+ """ from ctypes import Structure, c_double from types import TupleType, ListType @@ -29,14 +28,14 @@ class OGREnvelope(Structure): class Envelope(object): """ 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. + 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. + tuple or list, or 4 individual arguments. """ if len(args) == 1: diff --git a/django/contrib/gis/gdal/error.py b/django/contrib/gis/gdal/error.py index ed4b035f9c..ffcc4dfed5 100644 --- a/django/contrib/gis/gdal/error.py +++ b/django/contrib/gis/gdal/error.py @@ -1,10 +1,9 @@ """ - This module houses the OGR & SRS Exception objects, and the - check_err() routine which checks the status code returned by - OGR methods. + This module houses the OGR & SRS Exception objects, and the + check_err() routine which checks the status code returned by + OGR methods. """ - -# OGR & SRS Exceptions +#### OGR & SRS Exceptions #### class OGRException(Exception): pass class SRSException(Exception): pass class OGRIndexError(OGRException, KeyError): @@ -16,6 +15,8 @@ class OGRIndexError(OGRException, KeyError): """ silent_variable_failure = True +#### OGR error checking codes and routine #### + # OGR Error Codes OGRERR_DICT = { 1 : (OGRException, 'Not enough data.'), 2 : (OGRException, 'Not enough memory.'), @@ -36,4 +37,4 @@ def check_err(code): e, msg = OGRERR_DICT[code] raise e, msg else: - raise OGRException, 'Unknown error code: "%s"' % code + raise OGRException('Unknown error code: "%s"' % code) diff --git a/django/contrib/gis/gdal/feature.py b/django/contrib/gis/gdal/feature.py index dac69e2d66..eeab6a8b6a 100644 --- a/django/contrib/gis/gdal/feature.py +++ b/django/contrib/gis/gdal/feature.py @@ -1,14 +1,17 @@ -# types and ctypes -from types import StringType -from ctypes import c_char_p, c_int, c_void_p, string_at - # The GDAL C library, OGR exception, and the Field object -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 +# 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 +from django.contrib.gis.gdal.prototypes.geom import clone_geom, get_geom_srs +from django.contrib.gis.gdal.prototypes.srs import clone_srs + # For more information, see the OGR C API source code: # http://www.gdal.org/ogr/ogr__api_8h.html # @@ -18,33 +21,31 @@ class Feature(object): #### Python 'magic' routines #### def __init__(self, feat, fdefn): - "Needs a C pointer (Python integer in ctypes) in order to initialize." - self._feat = None # Initially NULL - self._fdefn = None + "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._feat = feat + self._ptr = feat self._fdefn = fdefn def __del__(self): "Releases a reference to this object." - if self._feat: lgdal.OGR_F_Destroy(self._feat) + if self._ptr: destroy_feature(self._ptr) def __getitem__(self, index): "Gets the Field at the specified index." - if isinstance(index, StringType): + if isinstance(index, basestring): i = self.index(index) else: if index < 0 or index > self.num_fields: 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)))) + return Field(self._ptr, i) def __iter__(self): "Iterates over each field in the Feature." for i in xrange(self.num_fields): - yield self.__getitem__(i) + yield self[i] def __len__(self): "Returns the count of fields in this feature." @@ -56,54 +57,49 @@ class Feature(object): def __eq__(self, other): "Does equivalence testing on the features." - if lgdal.OGR_F_Equal(self._feat, other._feat): - return True - else: - return False + return bool(feature_equal(self._ptr, other._ptr)) #### Feature Properties #### @property def fid(self): "Returns the feature identifier." - return lgdal.OGR_F_GetFID(self._feat) + return get_fid(self._ptr) @property def layer_name(self): "Returns the name of the layer for the feature." - return string_at(lgdal.OGR_FD_GetName(self._fdefn)) + return get_feat_name(self._fdefn) @property def num_fields(self): "Returns the number of fields in the Feature." - return lgdal.OGR_F_GetFieldCount(self._feat) + return get_feat_field_count(self._ptr) @property def fields(self): "Returns a list of fields in the Feature." - return [ string_at(lgdal.OGR_Fld_GetNameRef(lgdal.OGR_FD_GetFieldDefn(self._fdefn, i))) - for i in xrange(self.num_fields) ] + return [get_field_name(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 = lgdal.OGR_F_GetGeometryRef(self._feat) - if not geom_ptr: - raise OGRException('Cannot retrieve Geometry from the feature.') + geom_ptr = get_feat_geom_ref(self._ptr) # Attempting to retrieve the Spatial Reference for the geometry. - srs_ptr = lgdal.OSRClone(lgdal.OGR_G_GetSpatialReference(geom_ptr)) - if srs_ptr: - srs = SpatialReference(srs_ptr, 'ogr') - else: + try: + srs_ptr = get_geom_srs(geom_ptr) + srs = SpatialReference(clone_srs(srs_ptr)) + except OGRException: srs = None - - # Geometry is cloned so the feature isn't invalidated. - return OGRGeometry(c_void_p(lgdal.OGR_G_Clone(geom_ptr)), srs) - + # Geometry is cloned so the feature isn't invalidated. + return OGRGeometry(clone_geom(geom_ptr), srs) + @property def geom_type(self): "Returns the OGR Geometry Type for this Feture." - return OGRGeomType(lgdal.OGR_FD_GetGeomType(self._fdefn)) + return OGRGeomType(get_fd_geom_type(self._fdefn)) #### Feature Methods #### def get(self, field): @@ -113,14 +109,10 @@ class Feature(object): parameters. """ field_name = getattr(field, 'name', field) - return self.__getitem__(field_name).value + return self[field_name].value 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)) + i = get_field_index(self._ptr, field_name) if i < 0: raise OGRIndexError('invalid OFT field name given: "%s"' % field_name) return i - - def clone(self): - "Clones this Feature." - return Feature(lgdal.OGR_F_Clone(self._feat)) diff --git a/django/contrib/gis/gdal/field.py b/django/contrib/gis/gdal/field.py index 8a659fa37d..993d1d6b18 100644 --- a/django/contrib/gis/gdal/field.py +++ b/django/contrib/gis/gdal/field.py @@ -1,6 +1,10 @@ -from ctypes import string_at -from django.contrib.gis.gdal.libgdal import lgdal +from ctypes import byref, c_int +from datetime import date, datetime, time 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 # For more information, see the OGR C API source code: # http://www.gdal.org/ogr/ogr__api_8h.html @@ -10,70 +14,145 @@ class Field(object): "A class that wraps an OGR Field, needs to be instantiated from a Feature object." #### Python 'magic' routines #### - def __init__(self, fld, val=''): - "Needs a C pointer (Python integer in ctypes) in order to initialize." - self._fld = None # Initially NULL - + def __init__(self, feat, index): + """ + Initializes on the feature pointer and the integer index of + the field within the feature. + """ + # Setting the feature pointer and index. + self._feat = feat + self._index = index + + # Getting the pointer for this field. + fld = get_feat_field_defn(feat, index) if not fld: raise OGRException('Cannot create OGR Field, invalid pointer given.') - self._fld = fld - self._val = val + self._ptr = fld # Setting the class depending upon the OGR Field Type (OFT) self.__class__ = FIELD_CLASSES[self.type] + # OFTReal with no precision should be an OFTInteger. + if isinstance(self, OFTReal) and self.precision == 0: + self.__class__ = OFTInteger + def __str__(self): "Returns the string representation of the Field." - return '%s (%s)' % (self.name, self.value) + return str(self.value).strip() + + #### Field Methods #### + def as_double(self): + "Retrieves the Field's value as a double (float)." + return 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) + + def as_string(self): + "Retrieves the Field's value as a string." + return 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)) + if status: + return (yy, mm, dd, hh, mn, ss, tz) + else: + raise OGRException('Unable to retrieve date & time information from the field.') #### Field Properties #### @property def name(self): - "Returns the name of the field." - return string_at(lgdal.OGR_Fld_GetNameRef(self._fld)) + "Returns the name of this Field." + return get_field_name(self._ptr) + + @property + def precision(self): + "Returns the precision of this Field." + return get_field_precision(self._ptr) @property def type(self): - "Returns the type of this field." - return lgdal.OGR_Fld_GetType(self._fld) + "Returns the OGR type of this Field." + return 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) @property def value(self): - "Returns the value of this type of field." - return self._val + "Returns the value of this Field." + # Default is to get the field as a string. + return self.as_string() -# The Field sub-classes for each OGR Field type. + @property + def width(self): + "Returns the width of this Field." + return get_field_width(self._ptr) + +### The Field sub-classes for each OGR Field type. ### class OFTInteger(Field): @property def value(self): "Returns an integer contained in this field." - try: - return int(self._val) - except ValueError: - return None -class OFTIntegerList(Field): pass + return self.as_int() + + @property + def type(self): + """ + GDAL uses OFTReals to represent OFTIntegers in created + shapefiles -- forcing the type here since the underlying field + type may actually be OFTReal. + """ + return 0 class OFTReal(Field): @property def value(self): "Returns a float contained in this field." - try: - return float(self._val) - except ValueError: - return None -class OFTRealList(Field): pass + return self.as_double() -class OFTString(Field): - def __str__(self): - return '%s ("%s")' % (self.name, self.value) - -class OFTStringList(Field): pass +# String & Binary fields, just subclasses +class OFTString(Field): pass class OFTWideString(Field): pass -class OFTWideStringList(Field): pass class OFTBinary(Field): pass -class OFTDate(Field): pass -class OFTTime(Field): pass -class OFTDateTime(Field): pass + +# OFTDate, OFTTime, OFTDateTime fields. +class OFTDate(Field): + @property + def value(self): + "Returns a Python `date` object for the OFTDate field." + yy, mm, dd, hh, mn, ss, tz = self.as_datetime() + return date(yy.value, mm.value, dd.value) + +class OFTDateTime(Field): + @property + def value(self): + "Returns a Python `datetime` object for this OFTDateTime field." + yy, mm, dd, hh, mn, ss, tz = self.as_datetime() + # TODO: Adapt timezone information. + # See http://lists.maptools.org/pipermail/gdal-dev/2006-February/007990.html + # The `tz` variable has values of: 0=unknown, 1=localtime (ambiguous), + # 100=GMT, 104=GMT+1, 80=GMT-5, etc. + return datetime(yy.value, mm.value, dd.value, hh.value, mn.value, ss.value) + +class OFTTime(Field): + @property + def value(self): + "Returns a Python `time` object for this OFTTime field." + yy, mm, dd, hh, mn, ss, tz = self.as_datetime() + return time(hh.value, mn.value, ss.value) + +# List fields are also just subclasses +class OFTIntegerList(Field): pass +class OFTRealList(Field): pass +class OFTStringList(Field): pass +class OFTWideStringList(Field): pass # Class mapping dictionary for OFT Types FIELD_CLASSES = { 0 : OFTInteger, diff --git a/django/contrib/gis/gdal/geometries.py b/django/contrib/gis/gdal/geometries.py index 129c88cd43..34b775e7f3 100644 --- a/django/contrib/gis/gdal/geometries.py +++ b/django/contrib/gis/gdal/geometries.py @@ -1,79 +1,67 @@ """ - 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). + 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). - 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. + 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. - Example: - >>> from django.contrib.gis.gdal import OGRGeometry, OGRGeomType, SpatialReference - >>> wkt1, wkt2 = 'POINT(-90 30)', 'POLYGON((0 0, 5 0, 5 5, 0 5)' - >>> pnt = OGRGeometry(wkt1) - >>> print pnt - POINT (-90 30) - >>> mpnt = OGRGeometry(OGRGeomType('MultiPoint'), SpatialReference('WGS84')) - >>> mpnt.add(wkt1) - >>> mpnt.add(wkt1) - >>> print mpnt - MULTIPOINT (-90 30,-90 30) - >>> print mpnt.srs.name - WGS 84 - >>> print mpnt.srs.proj - +proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs - >>> mpnt.transform_to(SpatialReference('NAD27')) - >>> print mpnt.proj - +proj=longlat +ellps=clrk66 +datum=NAD27 +no_defs - >>> print mpnt - MULTIPOINT (-89.999930378602485 29.999797886557641,-89.999930378602485 29.999797886557641) - + Example: + >>> from django.contrib.gis.gdal import OGRGeometry, OGRGeomType, SpatialReference + >>> wkt1, wkt2 = 'POINT(-90 30)', 'POLYGON((0 0, 5 0, 5 5, 0 5)' + >>> pnt = OGRGeometry(wkt1) + >>> print pnt + POINT (-90 30) + >>> mpnt = OGRGeometry(OGRGeomType('MultiPoint'), SpatialReference('WGS84')) + >>> mpnt.add(wkt1) + >>> mpnt.add(wkt1) + >>> print mpnt + MULTIPOINT (-90 30,-90 30) + >>> print mpnt.srs.name + WGS 84 + >>> print mpnt.srs.proj + +proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs + >>> mpnt.transform_to(SpatialReference('NAD27')) + >>> print mpnt.proj + +proj=longlat +ellps=clrk66 +datum=NAD27 +no_defs + >>> print mpnt + MULTIPOINT (-89.999930378602485 29.999797886557641,-89.999930378602485 29.999797886557641) + The OGRGeomType class is to make it easy to specify an OGR geometry type: - >>> from django.contrib.gis.gdal import OGRGeomType - >>> gt1 = OGRGeomType(3) # Using an integer for the type - >>> gt2 = OGRGeomType('Polygon') # Using a string - >>> gt3 = OGRGeomType('POLYGON') # It's case-insensitive - >>> print gt1 == 3, gt1 == 'Polygon' # Equivalence works w/non-OGRGeomType objects - True + >>> from django.contrib.gis.gdal import OGRGeomType + >>> gt1 = OGRGeomType(3) # Using an integer for the type + >>> gt2 = OGRGeomType('Polygon') # Using a string + >>> gt3 = OGRGeomType('POLYGON') # It's case-insensitive + >>> print gt1 == 3, gt1 == 'Polygon' # Equivalence works w/non-OGRGeomType objects + True """ -# Python library imports +# Python library requisites. import re, sys -from binascii import a2b_hex, b2a_hex -from ctypes import byref, create_string_buffer, string_at, c_char_p, c_double, c_int, c_void_p +from binascii import a2b_hex +from ctypes import byref, string_at, c_char_p, c_double, c_ubyte, c_void_p from types import BufferType, IntType, StringType, UnicodeType # 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.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 + # For more information, see the OGR C API source code: # http://www.gdal.org/ogr/ogr__api_8h.html # # The OGR_G_* routines are relevant here. -#### ctypes prototypes for functions that return double values #### -def pnt_func(f): - "For accessing point information." - f.restype = c_double - f.argtypes = [c_void_p, c_int] - return f -# GetX, GetY, GetZ all return doubles. -getx = pnt_func(lgdal.OGR_G_GetX) -gety = pnt_func(lgdal.OGR_G_GetY) -getz = pnt_func(lgdal.OGR_G_GetZ) - -# GetArea returns a double. -get_area = lgdal.OGR_G_GetArea -get_area.restype = c_double -get_area.argtypes = [c_void_p] - -# Regular expression for determining whether the input is HEXEWKB. +# Regular expressions for recognizing HEXEWKB and WKT. hex_regex = re.compile(r'^[0-9A-F]+$', re.I) +wkt_regex = re.compile(r'^(?PPOINT|LINESTRING|LINEARRING|POLYGON|MULTIPOINT|MULTILINESTRING|MULTIPOLYGON|GEOMETRYCOLLECTION)[ACEGIMLONPSRUTY\d,\.\-\(\) ]+$', re.I) #### OGRGeometry Class #### class OGRGeometry(object): @@ -82,7 +70,7 @@ class OGRGeometry(object): def __init__(self, geom_input, srs=None): "Initializes Geometry on either WKT or an OGR pointer as input." - self._g = c_void_p(None) # Initially NULL + self._ptr = c_void_p(None) # Initially NULL # Checking if unicode if isinstance(geom_input, UnicodeType): @@ -94,52 +82,47 @@ class OGRGeometry(object): geom_input = buffer(a2b_hex(geom_input.upper())) if isinstance(geom_input, StringType): - # First, trying the input as WKT - buf = c_char_p(geom_input) - g = c_void_p() - - try: - check_err(lgdal.OGR_G_CreateFromWkt(byref(buf), c_void_p(), byref(g))) - except OGRException: - try: - # 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 OGR Geometry from: %s' % geom_input) + m = wkt_regex.match(geom_input) + if m: + if 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(m.group('type')).num) + import_wkt(g, byref(c_char_p(geom_input))) + else: + g = from_wkt(byref(c_char_p(geom_input)), None, byref(c_void_p())) + 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) elif isinstance(geom_input, BufferType): # WKB was passed in - g = c_void_p() - check_err(lgdal.OGR_G_CreateFromWkb(c_char_p(str(geom_input)), c_void_p(), byref(g), len(geom_input))) + g = 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 = lgdal.OGR_G_CreateGeometry(geom_input.num) + g = create_geom(geom_input.num) elif isinstance(geom_input, c_void_p): # OGR pointer (c_void_p) was the input. g = geom_input else: - 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) + raise OGRException('Invalid input type for OGR Geometry construction: %s' % type(geom_input)) # Now checking the Geometry pointer before finishing initialization + # by setting the pointer for the object. if not g: raise OGRException('Cannot create OGR Geometry from input: %s' % str(geom_input)) - self._g = g + self._ptr = g + + # Assigning the SpatialReference object to the geometry, if valid. + if bool(srs): self.srs = srs # Setting the class depending upon the OGR Geometry Type self.__class__ = GEO_CLASSES[self.geom_type.num] def __del__(self): "Deletes this Geometry." - if self._g: lgdal.OGR_G_DestroyGeometry(self._g) + if self._ptr: destroy_geom(self._ptr) ### Geometry set-like operations ### # g = g1 | g2 @@ -178,71 +161,74 @@ class OGRGeometry(object): @property def dimension(self): "Returns 0 for points, 1 for lines, and 2 for surfaces." - return lgdal.OGR_G_GetDimension(self._g) + return get_dims(self._ptr) @property def coord_dim(self): "Returns the coordinate dimension of the Geometry." - return lgdal.OGR_G_GetCoordinateDimension(self._g) + return get_coord_dims(self._ptr) @property def geom_count(self): "The number of elements in this Geometry." - return lgdal.OGR_G_GetGeometryCount(self._g) + return get_geom_count(self._ptr) @property def point_count(self): "Returns the number of Points in this Geometry." - return lgdal.OGR_G_GetPointCount(self._g) + return get_point_count(self._ptr) + + @property + def num_points(self): + "Alias for `point_count` (same name method in GEOS API.)" + return self.point_count @property def num_coords(self): - "Returns the number of Points in this Geometry." + "Alais for `point_count`." return self.point_count @property def geom_type(self): "Returns the Type for this Geometry." - return OGRGeomType(lgdal.OGR_G_GetGeometryType(self._g)) + return OGRGeomType(get_geom_type(self._ptr)) @property def geom_name(self): "Returns the Name of this Geometry." - return string_at(lgdal.OGR_G_GetGeometryName(self._g)) + return get_geom_name(self._ptr) @property def area(self): "Returns the area for a LinearRing, Polygon, or MultiPolygon; 0 otherwise." - return get_area(self._g) + return get_area(self._ptr) @property def envelope(self): "Returns the envelope for this Geometry." - env = OGREnvelope() - lgdal.OGR_G_GetEnvelope(self._g, byref(env)) - return Envelope(env) + return Envelope(get_envelope(self._ptr, byref(OGREnvelope()))) #### SpatialReference-related Properties #### # The SRS property def get_srs(self): "Returns the Spatial Reference for this Geometry." - srs_ptr = lgdal.OGR_G_GetSpatialReference(self._g) - if srs_ptr: - return SpatialReference(lgdal.OSRClone(srs_ptr), 'ogr') - else: + try: + srs_ptr = get_geom_srs(self._ptr) + return SpatialReference(clone_srs(srs_ptr)) + except SRSException: return None def set_srs(self, srs): "Sets the SpatialReference for this geometry." if isinstance(srs, SpatialReference): - srs_ptr = lgdal.OSRClone(srs._srs) + srs_ptr = clone_srs(srs._ptr) elif isinstance(srs, (StringType, UnicodeType, IntType)): sr = SpatialReference(srs) - srs_ptr = lgdal.OSRClone(sr._srs) + srs_ptr = clone_srs(sr._ptr) else: raise TypeError('Cannot assign spatial reference with object of type: %s' % type(srs)) - lgdal.OGR_G_AssignSpatialReference(self._g, srs_ptr) + assign_srs(self._ptr, srs_ptr) srs = property(get_srs, set_srs) @@ -260,151 +246,168 @@ class OGRGeometry(object): srid = property(get_srid, set_srid) #### Output Methods #### + @property + def geos(self): + "Returns a GEOSGeometry object from this OGRGeometry." + from django.contrib.gis.geos import GEOSGeometry + return GEOSGeometry(self.wkb, self.srid) + @property def gml(self): "Returns the GML representation of the Geometry." - buf = lgdal.OGR_G_ExportToGML(self._g) - if buf: return string_at(buf) - else: return None + return to_gml(self._ptr) @property def hex(self): "Returns the hexadecimal representation of the WKB (a string)." - return b2a_hex(self.wkb).upper() + return str(self.wkb).encode('hex').upper() + #return b2a_hex(self.wkb).upper() @property def wkb_size(self): "Returns the size of the WKB buffer." - return lgdal.OGR_G_WkbSize(self._g) + return get_wkbsize(self._ptr) @property def wkb(self): "Returns the WKB representation of the Geometry." if sys.byteorder == 'little': - byteorder = c_int(1) # wkbNDR (from ogr_core.h) + byteorder = 1 # wkbNDR (from ogr_core.h) else: - byteorder = c_int(0) # wkbXDR (from ogr_core.h) - # Creating a mutable string buffer of the given size, exporting - # to WKB, and returning a Python buffer of the WKB. + byteorder = 0 # wkbXDR sz = self.wkb_size - wkb = create_string_buffer(sz) - check_err(lgdal.OGR_G_ExportToWkb(self._g, byteorder, byref(wkb))) - return buffer(string_at(wkb, sz)) + # Creating the unsigned character buffer, and passing it in by reference. + buf = (c_ubyte * sz)() + wkb = 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." - buf = c_char_p() - check_err(lgdal.OGR_G_ExportToWkt(self._g, byref(buf))) - return string_at(buf) + return to_wkt(self._ptr, byref(c_char_p())) #### Geometry Methods #### def clone(self): "Clones this OGR Geometry." - return OGRGeometry(c_void_p(lgdal.OGR_G_Clone(self._g))) + return OGRGeometry(clone_geom(self._ptr), self.srs) def close_rings(self): - """If there are any rings within this geometry that have not been + """ + If there are any rings within this geometry that have not been closed, this routine will do so by adding the starting point at the - end.""" + end. + """ # Closing the open rings. - lgdal.OGR_G_CloseRings(self._g) + geom_close_rings(self._ptr) 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.') - check_err(lgdal.OGR_G_Transform(self._g, coord_trans._ct)) + """ + Transforms this geometry to a different spatial reference system. May take + either a CoordTransform object or a SpatialReference object. + """ + if isinstance(coord_trans, CoordTransform): + geom_transform(self._ptr, coord_trans._ptr) + elif isinstance(coord_trans, SpatialReference): + geom_transform_to(self._ptr, coord_trans._ptr) + else: + raise TypeError('Either a CoordTransform or a SpatialReference object required for transformation.') 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.') - check_err(lgdal.OGR_G_TransformTo(self._g, srs._srs)) + "For backwards-compatibility." + self.transform(srs) #### Topology Methods #### - def _topology(self, topo_func, other): + def _topology(self, func, other): """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 TypeError('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) - - # Returning based on the status code (an integer) - if status: return True - else: return False + # Returning the output of the given function with the other geometry's + # pointer. + return func(self._ptr, other._ptr) def intersects(self, other): "Returns True if this geometry intersects with the other." - return self._topology(lgdal.OGR_G_Intersects, other) + return self._topology(ogr_intersects, other) def equals(self, other): "Returns True if this geometry is equivalent to the other." - return self._topology(lgdal.OGR_G_Equals, other) + return self._topology(ogr_equals, other) def disjoint(self, other): "Returns True if this geometry and the other are spatially disjoint." - return self._topology(lgdal.OGR_G_Disjoint, other) + return self._topology(ogr_disjoint, other) def touches(self, other): "Returns True if this geometry touches the other." - return self._topology(lgdal.OGR_G_Touches, other) + return self._topology(ogr_touches, other) def crosses(self, other): "Returns True if this geometry crosses the other." - return self._topology(lgdal.OGR_G_Crosses, other) + return self._topology(ogr_crosses, other) def within(self, other): "Returns True if this geometry is within the other." - return self._topology(lgdal.OGR_G_Within, other) + return self._topology(ogr_within, other) def contains(self, other): "Returns True if this geometry contains the other." - return self._topology(lgdal.OGR_G_Contains, other) + return self._topology(ogr_contains, other) def overlaps(self, other): "Returns True if this geometry overlaps the other." - return self._topology(lgdal.OGR_G_Overlaps, other) + return self._topology(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(c_void_p(gen_func(self._g, other._g))) + return OGRGeometry(gen_func(self._ptr, other._ptr), self.srs) else: - return OGRGeometry(c_void_p(gen_func(self._g))) + return OGRGeometry(gen_func(self._ptr), self.srs) @property def boundary(self): "Returns the boundary of this geometry." - return self._geomgen(lgdal.OGR_G_GetBoundary) + return self._geomgen(get_boundary) @property def convex_hull(self): - "Returns the smallest convex Polygon that contains all the points in the Geometry." - return self._geomgen(lgdal.OGR_G_ConvexHull) + """ + Returns the smallest convex Polygon that contains all the points in + this Geometry. + """ + return self._geomgen(geom_convex_hull) - 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(lgdal.OGR_G_Union, other) - 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(lgdal.OGR_G_Difference, other) - - def sym_difference(self, other): - """Returns a new geometry which is the symmetric difference of this - geometry and the other.""" - return self._geomgen(lgdal.OGR_G_SymmetricDifference, 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) def intersection(self, other): - """Returns a new geometry consisting of the region of intersection of this - geometry and the other.""" - return self._geomgen(lgdal.OGR_G_Intersection, other) + """ + Returns a new geometry consisting of the region of intersection of this + geometry and the other. + """ + return self._geomgen(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) + + 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) # The subclasses for OGR Geometry. class Point(OGRGeometry): @@ -412,17 +415,17 @@ class Point(OGRGeometry): @property def x(self): "Returns the X coordinate for this Point." - return getx(self._g, c_int(0)) + return getx(self._ptr, 0) @property def y(self): "Returns the Y coordinate for this Point." - return gety(self._g, c_int(0)) + return gety(self._ptr, 0) @property def z(self): "Returns the Z coordinate for this Point." - return getz(self._g, c_int(0)) + return getz(self._ptr, 0) @property def tuple(self): @@ -436,17 +439,15 @@ class LineString(OGRGeometry): def __getitem__(self, index): "Returns the Point at the given index." - if index > 0 or index < self.point_count: - x = c_double() - y = c_double() - z = c_double() - lgdal.OGR_G_GetPoint(self._g, c_int(index), - byref(x), byref(y), byref(z)) - if self.coord_dim == 1: + 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)) + dim = self.coord_dim + if dim == 1: return (x.value,) - elif self.coord_dim == 2: + elif dim == 2: return (x.value, y.value) - elif self.coord_dim == 3: + elif dim == 3: return (x.value, y.value, z.value) else: raise OGRIndexError('index out of range: %s' % str(index)) @@ -454,16 +455,16 @@ class LineString(OGRGeometry): def __iter__(self): "Iterates over each point in the LineString." for i in xrange(self.point_count): - yield self.__getitem__(i) + yield self[i] - def __len__(self, index): + def __len__(self): "The length returns the number of points in the LineString." return self.point_count @property def tuple(self): "Returns the tuple representation of this LineString." - return tuple(self.__getitem__(i) for i in xrange(self.point_count)) + return tuple(self[i] for i in xrange(len(self))) # LinearRings are used in Polygons. class LinearRing(LineString): pass @@ -477,38 +478,38 @@ class Polygon(OGRGeometry): def __iter__(self): "Iterates through each ring in the Polygon." for i in xrange(self.geom_count): - yield self.__getitem__(i) + yield self[i] 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' % index) else: - return OGRGeometry(c_void_p(lgdal.OGR_G_Clone(lgdal.OGR_G_GetGeometryRef(self._g, c_int(index)))), self.srs) + return OGRGeometry(clone_geom(get_geom_ref(self._ptr, index)), self.srs) # Polygon Properties @property def shell(self): "Returns the shell of this Polygon." - return self.__getitem__(0) # First ring is the shell + return self[0] # First ring is the shell @property def tuple(self): "Returns a tuple of LinearRing coordinate tuples." - return tuple(self.__getitem__(i).tuple for i in xrange(self.geom_count)) + return tuple(self[i].tuple for i in xrange(self.geom_count)) @property def point_count(self): "The number of Points in this Polygon." # Summing up the number of points in each ring of the Polygon. - return sum([self.__getitem__(i).point_count for i in xrange(self.geom_count)]) + return sum([self[i].point_count for i in xrange(self.geom_count)]) @property def centroid(self): "Returns the centroid (a Point) of this Polygon." # The centroid is a Point, create a geometry for this. p = OGRGeometry(OGRGeomType('Point')) - check_err(lgdal.OGR_G_Centroid(self._g, p._g)) + get_centroid(self._ptr, p._ptr) return p # Geometry Collection base class. @@ -518,14 +519,14 @@ 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' % index) else: - return OGRGeometry(c_void_p(lgdal.OGR_G_Clone(lgdal.OGR_G_GetGeometryRef(self._g, c_int(index)))), self.srs) + return OGRGeometry(clone_geom(get_geom_ref(self._ptr, index)), self.srs) def __iter__(self): "Iterates over each Geometry." for i in xrange(self.geom_count): - yield self.__getitem__(i) + yield self[i] def __len__(self): "The number of geometries in this Geometry Collection." @@ -534,24 +535,24 @@ class GeometryCollection(OGRGeometry): def add(self, geom): "Add the geometry to this Geometry Collection." if isinstance(geom, OGRGeometry): - ptr = geom._g + ptr = geom._ptr elif isinstance(geom, (StringType, UnicodeType)): tmp = OGRGeometry(geom) - ptr = tmp._g + ptr = tmp._ptr else: raise OGRException('Must add an OGRGeometry.') - lgdal.OGR_G_AddGeometry(self._g, ptr) + add_geom(self._ptr, ptr) @property def point_count(self): "The number of Points in this Geometry Collection." # Summing up the number of points in each geometry in this collection - return sum([self.__getitem__(i).point_count for i in xrange(self.geom_count)]) + return sum([self[i].point_count for i in xrange(self.geom_count)]) @property def tuple(self): "Returns a tuple representation of this Geometry Collection." - return tuple(self.__getitem__(i).tuple for i in xrange(self.geom_count)) + return tuple(self[i].tuple for i in xrange(self.geom_count)) # Multiple Geometry types. class MultiPoint(GeometryCollection): pass @@ -566,4 +567,5 @@ GEO_CLASSES = {1 : Point, 5 : MultiLineString, 6 : MultiPolygon, 7 : GeometryCollection, + 101: LinearRing, } diff --git a/django/contrib/gis/gdal/geomtype.py b/django/contrib/gis/gdal/geomtype.py index 17f6918840..1062723f09 100644 --- a/django/contrib/gis/gdal/geomtype.py +++ b/django/contrib/gis/gdal/geomtype.py @@ -1,4 +1,3 @@ -from types import StringType from django.contrib.gis.gdal.error import OGRException #### OGRGeomType #### @@ -15,7 +14,7 @@ class OGRGeomType(object): "Figures out the correct OGR Type based upon the input." if isinstance(type_input, OGRGeomType): self._index = type_input._index - elif isinstance(type_input, StringType): + elif isinstance(type_input, basestring): idx = self._has_str(self.__ogr_str, type_input) if idx == None: raise OGRException('Invalid OGR String Type "%s"' % type_input) @@ -25,18 +24,20 @@ class OGRGeomType(object): 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." return self.__ogr_str[self._index] def __eq__(self, other): - """Does an equivalence test on the OGR type with the given - other OGRGeomType, the short-hand string, or the integer.""" + """ + Does an equivalence test on the OGR type with the given + other OGRGeomType, the short-hand string, or the integer. + """ if isinstance(other, OGRGeomType): return self._index == other._index - elif isinstance(other, StringType): + elif isinstance(other, basestring): idx = self._has_str(self.__ogr_str, other) if not (idx == None): return self._index == idx return False diff --git a/django/contrib/gis/gdal/layer.py b/django/contrib/gis/gdal/layer.py index 8445de753d..a3ef97e85e 100644 --- a/django/contrib/gis/gdal/layer.py +++ b/django/contrib/gis/gdal/layer.py @@ -1,38 +1,36 @@ # Needed ctypes routines -from ctypes import c_int, c_long, c_void_p, byref, string_at - -# The GDAL C Library -from django.contrib.gis.gdal.libgdal import lgdal +from ctypes import byref # Other GDAL imports. from django.contrib.gis.gdal.envelope import Envelope, OGREnvelope +from django.contrib.gis.gdal.error import OGRException, OGRIndexError from django.contrib.gis.gdal.feature import Feature from django.contrib.gis.gdal.geometries import OGRGeomType -from django.contrib.gis.gdal.error import OGRException, OGRIndexError, check_err 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_layer_defn, get_layer_srs, get_next_feature, \ + reset_reading +from django.contrib.gis.gdal.prototypes.srs import clone_srs + # 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. - -# function prototype for obtaining the spatial reference system -get_srs = lgdal.OGR_L_GetSpatialRef -get_srs.restype = c_void_p -get_srs.argtypes = [c_void_p] - class Layer(object): "A class that wraps an OGR Layer, needs to be instantiated from a DataSource object." #### Python 'magic' routines #### - def __init__(self, l): + def __init__(self, layer_ptr): "Needs a C pointer (Python/ctypes integer) in order to initialize." - self._layer = None # Initially NULL - self._ldefn = None - if not l: - raise OGRException, 'Cannot create Layer, invalid pointer given' - self._layer = l - self._ldefn = lgdal.OGR_L_GetLayerDefn(l) + self._ptr = None # Initially NULL + if not layer_ptr: + raise OGRException('Cannot create Layer, invalid pointer given') + self._ptr = layer_ptr + self._ldefn = get_layer_defn(self._ptr) def __getitem__(self, index): "Gets the Feature at the specified index." @@ -44,7 +42,7 @@ class Layer(object): if index < 0: index = end - index if index < 0 or index >= self.num_feat: - raise OGRIndexError, 'index out of range' + raise OGRIndexError('index out of range') return self._make_feature(index) else: # A slice was given @@ -54,9 +52,9 @@ class Layer(object): def __iter__(self): "Iterates over each Feature in the Layer." # ResetReading() must be called before iteration is to begin. - lgdal.OGR_L_ResetReading(self._layer) + reset_reading(self._ptr) for i in range(self.num_feat): - yield Feature(lgdal.OGR_L_GetNextFeature(self._layer), self._ldefn) + yield Feature(get_next_feature(self._ptr), self._ldefn) def __len__(self): "The length is the number of features." @@ -68,76 +66,80 @@ class Layer(object): def _make_feature(self, offset): "Helper routine for __getitem__ that makes a feature from an offset." - return Feature(lgdal.OGR_L_GetFeature(self._layer, c_long(offset)), self._ldefn) + return Feature(get_feature(self._ptr, offset), self._ldefn) #### Layer properties #### @property def extent(self): "Returns the extent (an Envelope) of this layer." env = OGREnvelope() - check_err(lgdal.OGR_L_GetExtent(self._layer, byref(env), c_int(1))) + 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 string_at(lgdal.OGR_FD_GetName(self._ldefn)) + return get_fd_name(self._ldefn) @property def num_feat(self, force=1): "Returns the number of features in the Layer." - return lgdal.OGR_L_GetFeatureCount(self._layer, c_int(force)) + return get_feature_count(self._ptr, force) @property def num_fields(self): "Returns the number of fields in the Layer." - return lgdal.OGR_FD_GetFieldCount(self._ldefn) + return get_field_count(self._ldefn) @property def geom_type(self): "Returns the geometry type (OGRGeomType) of the Layer." - return OGRGeomType(lgdal.OGR_FD_GetGeomType(self._ldefn)) + return OGRGeomType(get_fd_geom_type(self._ldefn)) @property def srs(self): "Returns the Spatial Reference used in this Layer." - ptr = lgdal.OGR_L_GetSpatialRef(self._layer) + ptr = get_layer_srs(self._ptr) if ptr: - return SpatialReference(lgdal.OSRClone(ptr), 'ogr') + return SpatialReference(clone_srs(ptr)) else: return None @property def fields(self): "Returns a list of the fields available in this Layer." - return [ string_at(lgdal.OGR_Fld_GetNameRef(lgdal.OGR_FD_GetFieldDefn(self._ldefn, i))) - for i in xrange(self.num_fields) ] + return [get_field_name(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 [ int(lgdal.OGR_Fld_GetWidth(lgdal.OGR_FD_GetFieldDefn(self._ldefn, i))) - for i in xrange(self.num_fields) ] + return [get_field_width(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 [ int(lgdal.OGR_Fld_GetPrecision(lgdal.OGR_FD_GetFieldDefn(self._ldefn, i))) - for i in xrange(self.num_fields) ] + return [get_field_precision(get_field_defn(self._ldefn, i)) + for i in xrange(self.num_fields)] #### Layer Methods #### def get_fields(self, field_name): - """Returns a list containing the given field name for every Feature - in the Layer.""" + """ + Returns a list containing the given field name for every Feature + in the Layer. + """ if not field_name in self.fields: - raise OGRException, 'invalid field name: %s' % field_name + raise OGRException('invalid field name: %s' % field_name) return [feat.get(field_name) for feat in self] def get_geoms(self, geos=False): - """Returns a list containing the OGRGeometry for every Feature in - the Layer.""" + """ + Returns a list containing the OGRGeometry for every Feature in + the Layer. + """ if geos: - from django.contrib.gis.geos import fromstr - return [fromstr(feat.geom.wkt) for feat in self] + from django.contrib.gis.geos import GEOSGeometry + return [GEOSGeometry(feat.geom.wkb) for feat in self] else: return [feat.geom for feat in self] diff --git a/django/contrib/gis/gdal/libgdal.py b/django/contrib/gis/gdal/libgdal.py index 098a9996cc..5ebc699701 100644 --- a/django/contrib/gis/gdal/libgdal.py +++ b/django/contrib/gis/gdal/libgdal.py @@ -1,5 +1,6 @@ import os, sys from ctypes import CDLL, string_at +from ctypes.util import find_library from django.contrib.gis.gdal.error import OGRException if os.name == 'nt': @@ -7,16 +8,14 @@ if os.name == 'nt': lib_name = 'libgdal-1.dll' elif os.name == 'posix': platform = os.uname()[0] - if platform in ('Linux', 'SunOS'): - # Linux or Solaris shared library - lib_name = 'libgdal.so' - elif platform == 'Darwin': + if platform == 'Darwin': # Mac OSX shared library lib_name = 'libgdal.dylib' - else: - raise OGRException, 'Unknown POSIX platform "%s"' % platform + else: + # Attempting to use .so extension for all other platforms. + lib_name = 'libgdal.so' else: - raise OGRException, 'Unsupported OS "%s"' % os.name + raise OGRException('Unsupported OS "%s"' % os.name) # This loads the GDAL/OGR C library lgdal = CDLL(lib_name) diff --git a/django/contrib/gis/gdal/prototypes/__init__.py b/django/contrib/gis/gdal/prototypes/__init__.py new file mode 100644 index 0000000000..1ae38d4f82 --- /dev/null +++ b/django/contrib/gis/gdal/prototypes/__init__.py @@ -0,0 +1,16 @@ +""" + This routine provides shortuct functions to generate ctypes prototypes + for the GDAL routines. +""" +# OGR Geometry prototypes. +from django.contrib.gis.gdal.prototypes.geom import \ + assign_srs, clone_geom, create_geom, destroy_geom, from_wkb, from_wkt, \ + get_area, get_coord_dims, get_dims, get_envelope, get_geom_count, get_geom_name, get_geom_srs, get_geom_type, get_point_count, get_wkbsize, \ + getx, get_geom_ref, gety, getz, to_gml, to_wkt + +# Spatial Reference prototypes. +from django.contrib.gis.gdal.prototypes.srs import \ + clone_srs + +# TEMPORARY +from generation import double_output, string_output, void_output diff --git a/django/contrib/gis/gdal/prototypes/ds.py b/django/contrib/gis/gdal/prototypes/ds.py new file mode 100644 index 0000000000..1f55f4b58d --- /dev/null +++ b/django/contrib/gis/gdal/prototypes/ds.py @@ -0,0 +1,67 @@ +""" + This module houses the ctypes function prototypes for OGR DataSource + related data structures. OGR_Dr_*, OGR_DS_*, OGR_L_*, OGR_F_*, + OGR_Fld_* routines are relevant here. +""" +from ctypes import c_char_p, c_int, c_long, c_void_p, POINTER +from django.contrib.gis.gdal.envelope import OGREnvelope +from django.contrib.gis.gdal.libgdal import lgdal +from django.contrib.gis.gdal.prototypes.generation import \ + const_string_output, double_output, geom_output, int_output, \ + srs_output, void_output, voidptr_output + +c_int_p = POINTER(c_int) # shortcut type + +### Driver Routines ### +register_all = void_output(lgdal.OGRRegisterAll, [], errcheck=False) +cleanup_all = void_output(lgdal.OGRCleanupAll, [], errcheck=False) +get_driver = voidptr_output(lgdal.OGRGetDriver, [c_int]) +get_driver_by_name = voidptr_output(lgdal.OGRGetDriverByName, [c_char_p]) +get_driver_count = int_output(lgdal.OGRGetDriverCount, []) +get_driver_name = const_string_output(lgdal.OGR_Dr_GetName, [c_void_p]) + +### DataSource ### +open_ds = voidptr_output(lgdal.OGROpen, [c_char_p, c_int, POINTER(c_void_p)]) +destroy_ds = void_output(lgdal.OGR_DS_Destroy, [c_void_p], errcheck=False) +release_ds = void_output(lgdal.OGRReleaseDataSource, [c_void_p]) +get_ds_name = const_string_output(lgdal.OGR_DS_GetName, [c_void_p]) +get_layer = voidptr_output(lgdal.OGR_DS_GetLayer, [c_void_p, c_int]) +get_layer_by_name = voidptr_output(lgdal.OGR_DS_GetLayerByName, [c_void_p, c_char_p]) +get_layer_count = int_output(lgdal.OGR_DS_GetLayerCount, [c_void_p]) + +### Layer Routines ### +get_extent = void_output(lgdal.OGR_L_GetExtent, [c_void_p, POINTER(OGREnvelope), c_int]) +get_feature = voidptr_output(lgdal.OGR_L_GetFeature, [c_void_p, c_long]) +get_feature_count = int_output(lgdal.OGR_L_GetFeatureCount, [c_void_p, c_int]) +get_layer_defn = voidptr_output(lgdal.OGR_L_GetLayerDefn, [c_void_p]) +get_layer_srs = srs_output(lgdal.OGR_L_GetSpatialRef, [c_void_p]) +get_next_feature = voidptr_output(lgdal.OGR_L_GetNextFeature, [c_void_p]) +reset_reading = void_output(lgdal.OGR_L_ResetReading, [c_void_p], errcheck=False) + +### Feature Definition Routines ### +get_fd_geom_type = int_output(lgdal.OGR_FD_GetGeomType, [c_void_p]) +get_fd_name = const_string_output(lgdal.OGR_FD_GetName, [c_void_p]) +get_feat_name = const_string_output(lgdal.OGR_FD_GetName, [c_void_p]) +get_field_count = int_output(lgdal.OGR_FD_GetFieldCount, [c_void_p]) +get_field_defn = voidptr_output(lgdal.OGR_FD_GetFieldDefn, [c_void_p, c_int]) + +### Feature Routines ### +clone_feature = voidptr_output(lgdal.OGR_F_Clone, [c_void_p]) +destroy_feature = void_output(lgdal.OGR_F_Destroy, [c_void_p]) +feature_equal = int_output(lgdal.OGR_F_Equal, [c_void_p, c_void_p]) +get_feat_geom_ref = geom_output(lgdal.OGR_F_GetGeometryRef, [c_void_p]) +get_feat_field_count = int_output(lgdal.OGR_F_GetFieldCount, [c_void_p]) +get_feat_field_defn = voidptr_output(lgdal.OGR_F_GetFieldDefnRef, [c_void_p, c_int]) +get_fid = int_output(lgdal.OGR_F_GetFID, [c_void_p]) +get_field_as_datetime = int_output(lgdal.OGR_F_GetFieldAsDateTime, [c_void_p, c_int, c_int_p, c_int_p, c_int_p, c_int_p, c_int_p, c_int_p]) +get_field_as_double = double_output(lgdal.OGR_F_GetFieldAsDouble, [c_void_p, c_int]) +get_field_as_integer = int_output(lgdal.OGR_F_GetFieldAsInteger, [c_void_p, c_int]) +get_field_as_string = const_string_output(lgdal.OGR_F_GetFieldAsString, [c_void_p, c_int]) +get_field_index = int_output(lgdal.OGR_F_GetFieldIndex, [c_void_p, c_char_p]) + +### Field Routines ### +get_field_name = const_string_output(lgdal.OGR_Fld_GetNameRef, [c_void_p]) +get_field_precision = int_output(lgdal.OGR_Fld_GetPrecision, [c_void_p]) +get_field_type = int_output(lgdal.OGR_Fld_GetType, [c_void_p]) +get_field_type_name = const_string_output(lgdal.OGR_GetFieldTypeName, [c_int]) +get_field_width = int_output(lgdal.OGR_Fld_GetWidth, [c_void_p]) diff --git a/django/contrib/gis/gdal/prototypes/errcheck.py b/django/contrib/gis/gdal/prototypes/errcheck.py new file mode 100644 index 0000000000..c66be9adfb --- /dev/null +++ b/django/contrib/gis/gdal/prototypes/errcheck.py @@ -0,0 +1,125 @@ +""" + This module houses the error-checking routines used by the GDAL + ctypes prototypes. +""" +from ctypes import c_void_p, string_at +from django.contrib.gis.gdal.error import check_err, OGRException, SRSException +from django.contrib.gis.gdal.libgdal import lgdal + +# Helper routines for retrieving pointers and/or values from +# arguments passed in by reference. +def arg_byref(args, offset=-1): + "Returns the pointer argument's by-refernece value." + return args[offset]._obj.value + +def ptr_byref(args, offset=-1): + "Returns the pointer argument passed in by-reference." + return args[offset]._obj + +def check_bool(result, func, cargs): + "Returns the boolean evaluation of the value." + if bool(result): return True + else: return False + +### String checking Routines ### +def check_const_string(result, func, cargs, offset=None): + """ + Similar functionality to `check_string`, but does not free the pointer. + """ + if offset: + check_err(result) + ptr = ptr_byref(cargs, offset) + return ptr.value + else: + return result + +def check_string(result, func, cargs, offset=-1, str_result=False): + """ + Checks the string output returned from the given function, and frees + the string pointer allocated by OGR. The `str_result` keyword + may be used when the result is the string pointer, otherwise + the OGR error code is assumed. The `offset` keyword may be used + to extract the string pointer passed in by-reference at the given + slice offset in the function arguments. + """ + if str_result: + # For routines that return a string. + ptr = result + if not ptr: s = None + else: s = string_at(result) + else: + # Error-code return specified. + check_err(result) + ptr = ptr_byref(cargs, offset) + # Getting the string value + s = ptr.value + # Correctly freeing the allocated memory beind GDAL pointer + # w/the VSIFree routine. + if ptr: lgdal.VSIFree(ptr) + return s + +### DataSource, Layer error-checking ### + +### Envelope checking ### +def check_envelope(result, func, cargs, offset=-1): + "Checks a function that returns an OGR Envelope by reference." + env = ptr_byref(cargs, offset) + return env + +### Geometry error-checking routines ### +def check_geom(result, func, cargs): + "Checks a function that returns a geometry." + # OGR_G_Clone may return an integer, even though the + # restype is set to c_void_p + if isinstance(result, int): + result = c_void_p(result) + if not result: + raise OGRException('Invalid geometry pointer returned from "%s".' % func.__name__) + return result + +def check_geom_offset(result, func, cargs, offset=-1): + "Chcks the geometry at the given offset in the C parameter list." + check_err(result) + geom = ptr_byref(cargs, offset=offset) + return check_geom(geom, func, cargs) + +### Spatial Reference error-checking routines ### +def check_srs(result, func, cargs): + if isinstance(result, int): + result = c_void_p(result) + if not result: + raise SRSException('Invalid spatial reference pointer returned from "%s".' % func.__name__) + return result + +### Other error-checking routines ### +def check_arg_errcode(result, func, cargs): + """ + The error code is returned in the last argument, by reference. + Check its value with `check_err` before returning the result. + """ + check_err(arg_byref(cargs)) + return result + +def check_errcode(result, func, cargs): + """ + Check the error code returned (c_int). + """ + check_err(result) + return + +def check_pointer(result, func, cargs): + "Makes sure the result pointer is valid." + if bool(result): + return result + else: + raise OGRException('Invalid pointer returned from "%s"' % func.__name__) + +def check_str_arg(result, func, cargs): + """ + This is for the OSRGet[Angular|Linear]Units functions, which + require that the returned string pointer not be freed. This + returns both the double and tring values. + """ + dbl = result + ptr = cargs[-1]._obj + return dbl, ptr.value diff --git a/django/contrib/gis/gdal/prototypes/generation.py b/django/contrib/gis/gdal/prototypes/generation.py new file mode 100644 index 0000000000..485cc29245 --- /dev/null +++ b/django/contrib/gis/gdal/prototypes/generation.py @@ -0,0 +1,113 @@ +""" + This module contains functions that generate ctypes prototypes for the + GDAL routines. +""" + +from ctypes import c_char_p, c_double, c_int, c_void_p +from django.contrib.gis.gdal.prototypes.errcheck import \ + check_arg_errcode, check_errcode, check_geom, check_geom_offset, \ + check_pointer, check_srs, check_str_arg, check_string, check_const_string + +def double_output(func, argtypes, errcheck=False, strarg=False): + "Generates a ctypes function that returns a double value." + func.argtypes = argtypes + func.restype = c_double + if errcheck: func.errcheck = check_arg_errcode + if strarg: func.errcheck = check_str_arg + return func + +def geom_output(func, argtypes, offset=None): + """ + Generates a function that returns a Geometry either by reference + or directly (if the return_geom keyword is set to True). + """ + # Setting the argument types + func.argtypes = argtypes + + if not offset: + # When a geometry pointer is directly returned. + func.restype = c_void_p + func.errcheck = check_geom + else: + # Error code returned, geometry is returned by-reference. + func.restype = c_int + def geomerrcheck(result, func, cargs): + return check_geom_offset(result, func, cargs, offset) + func.errcheck = geomerrcheck + + return func + +def int_output(func, argtypes): + "Generates a ctypes function that returns an integer value." + func.argtypes = argtypes + func.restype = c_int + return func + +def srs_output(func, argtypes): + """ + Generates a ctypes prototype for the given function with + the given C arguments that returns a pointer to an OGR + Spatial Reference System. + """ + func.argtypes = argtypes + func.restype = c_void_p + func.errcheck = check_srs + return func + +def const_string_output(func, argtypes, offset=None): + func.argtypes = argtypes + if offset: + func.restype = c_int + else: + func.restype = c_char_p + + def _check_const(result, func, cargs): + return check_const_string(result, func, cargs, offset=offset) + func.errcheck = _check_const + + return func + +def string_output(func, argtypes, offset=-1, str_result=False): + """ + Generates a ctypes prototype for the given function with the + given argument types that returns a string from a GDAL pointer. + The `const` flag indicates whether the allocated pointer should + be freed via the GDAL library routine VSIFree -- but only applies + only when `str_result` is True. + """ + func.argtypes = argtypes + if str_result: + # String is the result, don't explicitly define + # the argument type so we can get the pointer. + pass + else: + # Error code is returned + func.restype = c_int + + # Dynamically defining our error-checking function with the + # given offset. + def _check_str(result, func, cargs): + return check_string(result, func, cargs, + offset=offset, str_result=str_result) + func.errcheck = _check_str + return func + +def void_output(func, argtypes, errcheck=True): + """ + For functions that don't only return an error code that needs to + be examined. + """ + if argtypes: func.argtypes = argtypes + if errcheck: + # `errcheck` keyword may be set to False for routines that + # return void, rather than a status code. + func.restype = c_int + func.errcheck = check_errcode + return func + +def voidptr_output(func, argtypes): + "For functions that return c_void_p." + func.argtypes = argtypes + func.restype = c_void_p + func.errcheck = check_pointer + return func diff --git a/django/contrib/gis/gdal/prototypes/geom.py b/django/contrib/gis/gdal/prototypes/geom.py new file mode 100644 index 0000000000..c3deb3aa6b --- /dev/null +++ b/django/contrib/gis/gdal/prototypes/geom.py @@ -0,0 +1,95 @@ +from ctypes import c_char, c_char_p, c_double, c_int, c_ubyte, c_void_p, POINTER +from django.contrib.gis.gdal.envelope import OGREnvelope +from django.contrib.gis.gdal.libgdal import lgdal +from django.contrib.gis.gdal.prototypes.errcheck import check_bool, check_envelope +from django.contrib.gis.gdal.prototypes.generation import \ + const_string_output, double_output, geom_output, int_output, \ + srs_output, string_output, void_output + +### Generation routines specific to this module ### +def env_func(f, argtypes): + "For getting OGREnvelopes." + f.argtypes = argtypes + f.restype = None + f.errcheck = check_envelope + return f + +def pnt_func(f): + "For accessing point information." + return double_output(f, [c_void_p, c_int]) + +def topology_func(f): + f.argtypes = [c_void_p, c_void_p] + f.restype = c_int + f.errchck = check_bool + return f + +### OGR_G ctypes function prototypes ### + +# GetX, GetY, GetZ all return doubles. +getx = pnt_func(lgdal.OGR_G_GetX) +gety = pnt_func(lgdal.OGR_G_GetY) +getz = pnt_func(lgdal.OGR_G_GetZ) + +# Geometry creation routines. +from_wkb = geom_output(lgdal.OGR_G_CreateFromWkb, [c_char_p, c_void_p, POINTER(c_void_p), c_int], offset=-2) +from_wkt = geom_output(lgdal.OGR_G_CreateFromWkt, [POINTER(c_char_p), c_void_p, POINTER(c_void_p)], offset=-1) +create_geom = geom_output(lgdal.OGR_G_CreateGeometry, [c_int]) +clone_geom = geom_output(lgdal.OGR_G_Clone, [c_void_p]) +get_geom_ref = geom_output(lgdal.OGR_G_GetGeometryRef, [c_void_p, c_int]) +get_boundary = geom_output(lgdal.OGR_G_GetBoundary, [c_void_p]) +geom_convex_hull = geom_output(lgdal.OGR_G_ConvexHull, [c_void_p]) +geom_diff = geom_output(lgdal.OGR_G_Difference, [c_void_p, c_void_p]) +geom_intersection = geom_output(lgdal.OGR_G_Intersection, [c_void_p, c_void_p]) +geom_sym_diff = geom_output(lgdal.OGR_G_SymmetricDifference, [c_void_p, c_void_p]) +geom_union = geom_output(lgdal.OGR_G_Union, [c_void_p, c_void_p]) + +# Geometry modification routines. +add_geom = void_output(lgdal.OGR_G_AddGeometry, [c_void_p, c_void_p]) +import_wkt = void_output(lgdal.OGR_G_ImportFromWkt, [c_void_p, POINTER(c_char_p)]) + +# Destroys a geometry +destroy_geom = void_output(lgdal.OGR_G_DestroyGeometry, [c_void_p], errcheck=False) + +# Geometry export routines. +to_wkb = void_output(lgdal.OGR_G_ExportToWkb, None, errcheck=True) # special handling for WKB. +to_wkt = string_output(lgdal.OGR_G_ExportToWkt, [c_void_p, POINTER(c_char_p)]) +to_gml = string_output(lgdal.OGR_G_ExportToGML, [c_void_p], str_result=True) +get_wkbsize = int_output(lgdal.OGR_G_WkbSize, [c_void_p]) + +# Geometry spatial-reference related routines. +assign_srs = void_output(lgdal.OGR_G_AssignSpatialReference, [c_void_p, c_void_p], errcheck=False) +get_geom_srs = srs_output(lgdal.OGR_G_GetSpatialReference, [c_void_p]) + +# Geometry properties +get_area = double_output(lgdal.OGR_G_GetArea, [c_void_p]) +get_centroid = void_output(lgdal.OGR_G_Centroid, [c_void_p, c_void_p]) +get_dims = int_output(lgdal.OGR_G_GetDimension, [c_void_p]) +get_coord_dims = int_output(lgdal.OGR_G_GetCoordinateDimension, [c_void_p]) + +get_geom_count = int_output(lgdal.OGR_G_GetGeometryCount, [c_void_p]) +get_geom_name = const_string_output(lgdal.OGR_G_GetGeometryName, [c_void_p]) +get_geom_type = int_output(lgdal.OGR_G_GetGeometryType, [c_void_p]) +get_point_count = int_output(lgdal.OGR_G_GetPointCount, [c_void_p]) +get_point = void_output(lgdal.OGR_G_GetPoint, [c_void_p, c_int, POINTER(c_double), POINTER(c_double), POINTER(c_double)], errcheck=False) +geom_close_rings = void_output(lgdal.OGR_G_CloseRings, [c_void_p]) + +# Topology routines. +ogr_contains = topology_func(lgdal.OGR_G_Contains) +ogr_crosses = topology_func(lgdal.OGR_G_Crosses) +ogr_disjoint = topology_func(lgdal.OGR_G_Disjoint) +ogr_equals = topology_func(lgdal.OGR_G_Equals) +ogr_intersects = topology_func(lgdal.OGR_G_Intersects) +ogr_overlaps = topology_func(lgdal.OGR_G_Overlaps) +ogr_touches = topology_func(lgdal.OGR_G_Touches) +ogr_within = topology_func(lgdal.OGR_G_Within) + +# Transformation routines. +geom_transform = void_output(lgdal.OGR_G_Transform, [c_void_p, c_void_p], errcheck=True) +geom_transform_to = void_output(lgdal.OGR_G_TransformTo, [c_void_p, c_void_p], errcheck=True) + +# For retrieving the envelope of the geometry. +get_envelope = lgdal.OGR_G_GetEnvelope +get_envelope.restype = None +get_envelope.argtypes = [c_void_p, POINTER(OGREnvelope)] +get_envelope.errcheck = check_envelope diff --git a/django/contrib/gis/gdal/prototypes/srs.py b/django/contrib/gis/gdal/prototypes/srs.py new file mode 100644 index 0000000000..acbf9665d8 --- /dev/null +++ b/django/contrib/gis/gdal/prototypes/srs.py @@ -0,0 +1,70 @@ +from ctypes import c_char_p, c_int, c_void_p, POINTER +from django.contrib.gis.gdal.libgdal import lgdal +from django.contrib.gis.gdal.prototypes.generation import \ + const_string_output, double_output, int_output, \ + srs_output, string_output, void_output + +## Shortcut generation for routines with known parameters. +def srs_double(f): + """ + Creates a function prototype for the OSR routines that take + the OSRSpatialReference object and + """ + return double_output(f, [c_void_p, POINTER(c_int)], errcheck=True) + +def units_func(f): + """ + Creates a ctypes function prototype for OSR units functions, e.g., + OSRGetAngularUnits, OSRGetLinearUnits. + """ + return double_output(f, [c_void_p, POINTER(c_char_p)], strarg=True) + +# Creation & destruction. +clone_srs = srs_output(lgdal.OSRClone, [c_void_p]) +new_srs = srs_output(lgdal.OSRNewSpatialReference, [c_char_p]) +release_srs = void_output(lgdal.OSRRelease, [c_void_p]) +srs_validate = void_output(lgdal.OSRValidate, [c_void_p], errcheck=True) + +# Getting the semi_major, semi_minor, and flattening functions. +semi_major = srs_double(lgdal.OSRGetSemiMajor) +semi_minor = srs_double(lgdal.OSRGetSemiMinor) +invflattening = srs_double(lgdal.OSRGetInvFlattening) + +# WKT, PROJ, EPSG, XML importation routines. +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(lgdal.OSRImportFromEPSG, [c_void_p, c_int]) +from_xml = void_output(lgdal.OSRImportFromXML, [c_void_p, c_char_p]) + +# Morphing to/from ESRI WKT. +morph_to_esri = void_output(lgdal.OSRMorphToESRI, [c_void_p]) +morph_from_esri = void_output(lgdal.OSRMorphFromESRI, [c_void_p]) + +# Identifying the EPSG +identify_epsg = void_output(lgdal.OSRAutoIdentifyEPSG, [c_void_p]) + +# Getting the angular_units, linear_units functions +linear_units = units_func(lgdal.OSRGetLinearUnits) +angular_units = units_func(lgdal.OSRGetAngularUnits) + +# For exporting to WKT, PROJ.4, "Pretty" WKT, and XML. +to_wkt = string_output(lgdal.OSRExportToWkt, [c_void_p, POINTER(c_char_p)]) +to_proj = string_output(lgdal.OSRExportToProj4, [c_void_p, POINTER(c_char_p)]) +to_pretty_wkt = string_output(lgdal.OSRExportToPrettyWkt, [c_void_p, POINTER(c_char_p), c_int], offset=-2) + +# FIXME: This leaks memory, still don't know why. +to_xml = string_output(lgdal.OSRExportToXML, [c_void_p, POINTER(c_char_p), c_char_p], offset=-2) + +# String attribute retrival routines. +get_attr_value = const_string_output(lgdal.OSRGetAttrValue, [c_void_p, c_char_p, c_int]) +get_auth_name = const_string_output(lgdal.OSRGetAuthorityName, [c_void_p, c_char_p]) +get_auth_code = const_string_output(lgdal.OSRGetAuthorityCode, [c_void_p, c_char_p]) + +# SRS Properties +isgeographic = int_output(lgdal.OSRIsGeographic, [c_void_p]) +islocal = int_output(lgdal.OSRIsLocal, [c_void_p]) +isprojected = int_output(lgdal.OSRIsProjected, [c_void_p]) + +# Coordinate transformation +new_ct= srs_output(lgdal.OCTNewCoordinateTransformation, [c_void_p, c_void_p]) +destroy_ct = void_output(lgdal.OCTDestroyCoordinateTransformation, [c_void_p]) diff --git a/django/contrib/gis/gdal/srs.py b/django/contrib/gis/gdal/srs.py index 65bdafa4c7..242ccbc3b1 100644 --- a/django/contrib/gis/gdal/srs.py +++ b/django/contrib/gis/gdal/srs.py @@ -28,50 +28,18 @@ """ import re from types import StringType, UnicodeType, TupleType -from ctypes import \ - c_char_p, c_int, c_double, c_void_p, POINTER, \ - byref, string_at, create_string_buffer - -# Getting the GDAL C Library -from django.contrib.gis.gdal.libgdal import lgdal +from ctypes import byref, c_char_p, c_int, c_void_p # Getting the error checking routine and exceptions -from django.contrib.gis.gdal.error import check_err, OGRException, SRSException - -#### ctypes function prototypes #### -def ellipsis_func(f): - """ - Creates a ctypes function prototype for OSR ellipsis property functions, e.g., - OSRGetSemiMajor, OSRGetSemiMinor, OSRGetInvFlattening. - """ - f.restype = c_double - f.argtypes = [c_void_p, POINTER(c_int)] - return f - -# Getting the semi_major, semi_minor, and flattening functions. -semi_major = ellipsis_func(lgdal.OSRGetSemiMajor) -semi_minor = ellipsis_func(lgdal.OSRGetSemiMinor) -invflattening = ellipsis_func(lgdal.OSRGetInvFlattening) - -def units_func(f): - """ - Creates a ctypes function prototype for OSR units functions, e.g., - OSRGetAngularUnits, OSRGetLinearUnits. - """ - f.restype = c_double - f.argtypes = [c_void_p, POINTER(c_char_p)] - return f - -# Getting the angular_units, linear_units functions -linear_units = units_func(lgdal.OSRGetLinearUnits) -angular_units = units_func(lgdal.OSRGetAngularUnits) +from django.contrib.gis.gdal.error import OGRException, SRSException +from django.contrib.gis.gdal.prototypes.srs import * #### Spatial Reference class. #### class SpatialReference(object): """ 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.' + the SpatialReference object "provide[s] services to represent coordinate + systems (projections and datums) and to transform between them." """ # Well-Known Geographical Coordinate System Name @@ -82,9 +50,8 @@ class SpatialReference(object): def __init__(self, srs_input='', srs_type='wkt'): "Creates a spatial reference object from the given OGC Well Known Text (WKT)." - self._srs = None # Initially NULL - - # Creating an initial empty string buffer. + # Intializing pointer and string buffer. + self._ptr = None buf = c_char_p('') # Encoding to ASCII if unicode passed in. @@ -92,37 +59,40 @@ class SpatialReference(object): srs_input = srs_input.encode('ascii') if isinstance(srs_input, StringType): - # Is this an EPSG well known name? m = self._epsg_regex.match(srs_input) if m: + # Is this an EPSG well known name? srs_type = 'epsg' srs_input = int(m.group('epsg')) - # Is this a short-hand well known name? 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': pass else: + # Setting the buffer with WKT, PROJ.4 string, etc. 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".') + # EPSG integer code was input. + if srs_type != 'epsg': srs_type = 'epsg' + elif isinstance(srs_input, c_void_p): + srs_type = 'ogr' else: raise TypeError('Invalid SRS type "%s"' % srs_type) - # Calling OSRNewSpatialReference with the string buffer. if srs_type == 'ogr': - srs = srs_input # SRS input is OGR pointer + # SRS input is OGR pointer + srs = srs_input else: - srs = lgdal.OSRNewSpatialReference(buf) + # Creating a new pointer, using the string buffer. + srs = 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._srs = srs + self._ptr = srs # Post-processing if in PROJ.4 or EPSG formats. if srs_type == 'proj': self.import_proj(srs_input) @@ -130,7 +100,7 @@ class SpatialReference(object): def __del__(self): "Destroys this spatial reference." - if self._srs: lgdal.OSRRelease(self._srs) + if self._ptr: release_srs(self._ptr) def __getitem__(self, target): """ @@ -172,43 +142,48 @@ class SpatialReference(object): "The string representation uses 'pretty' WKT." return self.pretty_wkt - def _string_ptr(self, ptr): - """ - Returns the string at the pointer if it is valid, None if the pointer - is NULL. - """ - if not ptr: return None - else: return string_at(ptr) - #### SpatialReference Methods #### - def auth_name(self, target): - "Getting the authority name for the target node." - ptr = lgdal.OSRGetAuthorityName(self._srs, c_char_p(target)) - return self._string_ptr(ptr) - - def auth_code(self, target): - "Getting the authority code for the given target node." - ptr = lgdal.OSRGetAuthorityCode(self._srs, c_char_p(target)) - return self._string_ptr(ptr) - def attr_value(self, target, index=0): """ 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): - raise TypeError('Attribute target must be a string') - ptr = lgdal.OSRGetAttrValue(self._srs, c_char_p(target), c_int(index)) - return self._string_ptr(ptr) + if not isinstance(target, str) or not isinstance(index, int): + raise TypeError + return 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) + + def auth_code(self, target): + "Returns the authority code for the given string target node." + return get_auth_code(self._ptr, target) + + def clone(self): + "Returns a clone of this SpatialReference object." + return SpatialReference(clone_srs(self._ptr)) + + def from_esri(self): + "Morphs this SpatialReference from ESRI's format to EPSG." + 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) + + def to_esri(self): + "Morphs this SpatialReference to ESRI's format." + morph_to_esri(self._ptr) def validate(self): "Checks to see if the given spatial reference is valid." - check_err(lgdal.OSRValidate(self._srs)) + srs_validate(self._ptr) - def clone(self): - "Returns a clone of this Spatial Reference." - return SpatialReference(lgdal.OSRClone(self._srs), 'ogr') - + #### Name & SRID properties #### @property def name(self): "Returns the name of this Spatial Reference." @@ -226,43 +201,29 @@ class SpatialReference(object): return None #### Unit Properties #### - def _cache_linear(self): - "Caches the linear units value and name." - if not hasattr(self, '_linear_units') or not hasattr(self, '_linear_name'): - name_buf = c_char_p() - self._linear_units = linear_units(self._srs, byref(name_buf)) - self._linear_name = string_at(name_buf) - @property def linear_name(self): "Returns the name of the linear units." - self._cache_linear() - return self._linear_name + units, name = linear_units(self._ptr, byref(c_char_p())) + return name @property def linear_units(self): "Returns the value of the linear units." - self._cache_linear() - return self._linear_units - - def _cache_angular(self): - "Caches the angular units value and name." - name_buf = c_char_p() - if not hasattr(self, '_angular_units') or not hasattr(self, '_angular_name'): - self._angular_units = angular_units(self._srs, byref(name_buf)) - self._angular_name = string_at(name_buf) + units, name = linear_units(self._ptr, byref(c_char_p())) + return units @property def angular_name(self): "Returns the name of the angular units." - self._cache_angular() - return self._angular_name + units, name = angular_units(self._ptr, byref(c_char_p())) + return name @property def angular_units(self): "Returns the value of the angular units." - self._cache_angular() - return self._angular_units + units, name = angular_units(self._ptr, byref(c_char_p())) + return units #### Spheroid/Ellipsoid Properties #### @property @@ -276,26 +237,17 @@ class SpatialReference(object): @property def semi_major(self): "Returns the Semi Major Axis for this Spatial Reference." - err = c_int(0) - sm = semi_major(self._srs, byref(err)) - check_err(err.value) - return sm + return semi_major(self._ptr, byref(c_int())) @property def semi_minor(self): "Returns the Semi Minor Axis for this Spatial Reference." - err = c_int() - sm = semi_minor(self._srs, byref(err)) - check_err(err.value) - return sm + return semi_minor(self._ptr, byref(c_int())) @property def inverse_flattening(self): "Returns the Inverse Flattening for this Spatial Reference." - err = c_int() - inv_flat = invflattening(self._srs, byref(err)) - check_err(err.value) - return inv_flat + return invflattening(self._ptr, byref(c_int())) #### Boolean Properties #### @property @@ -304,14 +256,12 @@ class SpatialReference(object): Returns True if this SpatialReference is geographic (root node is GEOGCS). """ - if lgdal.OSRIsGeographic(self._srs): return True - else: return False + return bool(isgeographic(self._ptr)) @property def local(self): "Returns True if this SpatialReference is local (root node is LOCAL_CS)." - if lgdal.OSRIsLocal(self._srs): return True - else: return False + return bool(islocal(self._ptr)) @property def projected(self): @@ -319,48 +269,40 @@ class SpatialReference(object): Returns True if this SpatialReference is a projected coordinate system (root node is PROJCS). """ - if lgdal.OSRIsProjected(self._srs): return True - else: return False + return bool(isprojected(self._ptr)) #### Import Routines ##### def import_wkt(self, wkt): "Imports the Spatial Reference from OGC WKT (string)" - buf = create_string_buffer(wkt) - check_err(lgdal.OSRImportFromWkt(self._srs, byref(buf))) + from_wkt(self._ptr, byref(c_char_p(wkt))) def import_proj(self, proj): "Imports the Spatial Reference from a PROJ.4 string." - check_err(lgdal.OSRImportFromProj4(self._srs, create_string_buffer(proj))) + from_proj(self._ptr, proj) def import_epsg(self, epsg): "Imports the Spatial Reference from the EPSG code (an integer)." - check_err(lgdal.OSRImportFromEPSG(self._srs, c_int(epsg))) + from_epsg(self._ptr, epsg) def import_xml(self, xml): "Imports the Spatial Reference from an XML string." - check_err(lgdal.OSRImportFromXML(self._srs, create_string_buffer(xml))) + from_xml(self._ptr, xml) #### Export Properties #### @property def wkt(self): "Returns the WKT representation of this Spatial Reference." - w = c_char_p() - check_err(lgdal.OSRExportToWkt(self._srs, byref(w))) - if w: return string_at(w) + return to_wkt(self._ptr, byref(c_char_p())) @property def pretty_wkt(self, simplify=0): "Returns the 'pretty' representation of the WKT." - w = c_char_p() - check_err(lgdal.OSRExportToPrettyWkt(self._srs, byref(w), c_int(simplify))) - if w: return string_at(w) + return to_pretty_wkt(self._ptr, byref(c_char_p()), simplify) @property def proj(self): "Returns the PROJ.4 representation for this Spatial Reference." - w = c_char_p() - check_err(lgdal.OSRExportToProj4(self._srs, byref(w))) - if w: return string_at(w) + return to_proj(self._ptr, byref(c_char_p())) @property def proj4(self): @@ -370,28 +312,34 @@ class SpatialReference(object): @property def xml(self, dialect=''): "Returns the XML representation of this Spatial Reference." - w = c_char_p() - check_err(lgdal.OSRExportToXML(self._srs, byref(w), create_string_buffer(dialect))) - return string_at(w) + # FIXME: This leaks memory, have to figure out why. + return 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): - "A coordinate system transformation object." + "The coordinate system transformation object." def __init__(self, source, target): "Initializes on a source and target SpatialReference objects." - self._ct = 0 # Initially NULL + 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') - ct = lgdal.OCTNewCoordinateTransformation(source._srs, target._srs) - if not ct: + self._ptr = new_ct(source._ptr, target._ptr) + if not self._ptr: raise SRSException('could not intialize CoordTransform object') - self._ct = ct self._srs1_name = source.name self._srs2_name = target.name def __del__(self): "Deletes this Coordinate Transformation object." - if self._ct: lgdal.OCTDestroyCoordinateTransformation(self._ct) + if self._ptr: destroy_ct(self._ptr) def __str__(self): - return 'Transform from "%s" to "%s"' % (str(self._srs1_name), str(self._srs2_name)) + return 'Transform from "%s" to "%s"' % (self._srs1_name, self._srs2_name) diff --git a/django/contrib/gis/tests/test_gdal_ds.py b/django/contrib/gis/tests/test_gdal_ds.py index beb5b9eaf1..361c762281 100644 --- a/django/contrib/gis/tests/test_gdal_ds.py +++ b/django/contrib/gis/tests/test_gdal_ds.py @@ -15,10 +15,10 @@ class TestSHP: setattr(self, key, value) # List of acceptable data sources. -ds_list = (TestSHP('test_point', nfeat=5, nfld=3, geom='POINT', gtype=1, fields={'dbl' : OFTReal, 'int' : OFTReal, 'str' : OFTString,}, +ds_list = (TestSHP('test_point', nfeat=5, nfld=3, geom='POINT', gtype=1, fields={'dbl' : OFTReal, 'int' : OFTInteger, 'str' : OFTString,}, extent=(-1.35011,0.166623,-0.524093,0.824508), # Got extent from QGIS srs_wkt='GEOGCS["GCS_WGS_1984",DATUM["WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]]'), - TestSHP('test_poly', nfeat=3, nfld=3, geom='POLYGON', gtype=3, fields={'float' : OFTReal, 'int' : OFTReal, 'str' : OFTString,}, + TestSHP('test_poly', nfeat=3, nfld=3, geom='POLYGON', gtype=3, fields={'float' : OFTReal, 'int' : OFTInteger, 'str' : OFTString,}, extent=(-1.01513,-0.558245,0.161876,0.839637), # Got extent from QGIS srs_wkt='GEOGCS["GCS_WGS_1984",DATUM["WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]]'), ) @@ -101,17 +101,12 @@ class DataSourceTest(unittest.TestCase): # Making sure the fields match to an appropriate OFT type. for k, v in source.fields.items(): - fld = feat[k] # Indexing with string value - - # Asserting the string representation, and making sure we get - # the proper OGR Field instance. - 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)) + # Making sure we get the proper OGR Field instance, using + # a string value index for the feature. + self.assertEqual(True, isinstance(feat[k], v)) # Testing __iter__ on the Feature - for fld in feat: self.assertEqual(fld.name in source.fields.keys(), True) + for fld in feat: self.assertEqual(True, fld.name in source.fields.keys()) def test05_geometries(self): "Testing Geometries from Data Source Features." diff --git a/django/contrib/gis/tests/test_gdal_geom.py b/django/contrib/gis/tests/test_gdal_geom.py index 0a7816ed81..7890435acd 100644 --- a/django/contrib/gis/tests/test_gdal_geom.py +++ b/django/contrib/gis/tests/test_gdal_geom.py @@ -1,5 +1,6 @@ import unittest -from django.contrib.gis.gdal import OGRGeometry, OGRGeomType, OGRException, SpatialReference +from django.contrib.gis.gdal import OGRGeometry, OGRGeomType, \ + OGRException, OGRIndexError, SpatialReference from django.contrib.gis.tests.geometries import * class OGRGeomTest(unittest.TestCase): @@ -94,41 +95,78 @@ class OGRGeomTest(unittest.TestCase): def test04_linestring(self): "Testing LineString objects." + prev = OGRGeometry('POINT(0 0)') for ls in linestrings: linestr = OGRGeometry(ls.wkt) self.assertEqual(2, linestr.geom_type) self.assertEqual('LINESTRING', linestr.geom_name) self.assertEqual(ls.n_p, linestr.point_count) self.assertEqual(ls.tup, linestr.tuple) + self.assertEqual(True, linestr == OGRGeometry(ls.wkt)) + self.assertEqual(True, linestr != prev) + self.assertRaises(OGRIndexError, linestr.__getitem__, len(linestr)) + prev = linestr def test05_multilinestring(self): "Testing MultiLineString objects." + prev = OGRGeometry('POINT(0 0)') for mls in multilinestrings: mlinestr = OGRGeometry(mls.wkt) self.assertEqual(5, mlinestr.geom_type) self.assertEqual('MULTILINESTRING', mlinestr.geom_name) self.assertEqual(mls.n_p, mlinestr.point_count) self.assertEqual(mls.tup, mlinestr.tuple) + self.assertEqual(True, mlinestr == OGRGeometry(mls.wkt)) + self.assertEqual(True, mlinestr != prev) + prev = mlinestr + for ls in mlinestr: + self.assertEqual(2, ls.geom_type) + self.assertEqual('LINESTRING', ls.geom_name) + self.assertRaises(OGRIndexError, mlinestr.__getitem__, len(mlinestr)) - def test06_polygons(self): + def test06_linearring(self): + "Testing LinearRing objects." + prev = OGRGeometry('POINT(0 0)') + for rr in linearrings: + lr = OGRGeometry(rr.wkt) + #self.assertEqual(101, lr.geom_type.num) + self.assertEqual('LINEARRING', lr.geom_name) + self.assertEqual(rr.n_p, len(lr)) + self.assertEqual(True, lr == OGRGeometry(rr.wkt)) + self.assertEqual(True, lr != prev) + prev = lr + + def test07a_polygons(self): "Testing Polygon objects." + prev = OGRGeometry('POINT(0 0)') for p in polygons: poly = OGRGeometry(p.wkt) self.assertEqual(3, poly.geom_type) self.assertEqual('POLYGON', poly.geom_name) self.assertEqual(p.n_p, poly.point_count) - first = True + self.assertEqual(p.n_i + 1, len(poly)) + + # Testing area & centroid. + self.assertAlmostEqual(p.area, poly.area, 9) + x, y = poly.centroid.tuple + self.assertAlmostEqual(p.centroid[0], x, 9) + self.assertAlmostEqual(p.centroid[1], y, 9) + + # Testing equivalence + self.assertEqual(True, poly == OGRGeometry(p.wkt)) + self.assertEqual(True, poly != prev) + + if p.ext_ring_cs: + ring = poly[0] + self.assertEqual(p.ext_ring_cs, ring.tuple) + self.assertEqual(p.ext_ring_cs, poly[0].tuple) + self.assertEqual(len(p.ext_ring_cs), ring.point_count) + for r in poly: - if first and p.ext_ring_cs: - first = False - # Testing the equivilance of the exerior rings - # since the first iteration will be the exterior ring. - self.assertEqual(len(p.ext_ring_cs), r.point_count) - self.assertEqual(p.ext_ring_cs, r.tuple) + self.assertEqual('LINEARRING', r.geom_name) - def test07_closepolygons(self): + def test07b_closepolygons(self): "Testing closing Polygon objects." - # Both rings in this geometry are not closed. poly = OGRGeometry('POLYGON((0 0, 5 0, 5 5, 0 5), (1 1, 2 1, 2 2, 2 1))') self.assertEqual(8, poly.point_count) @@ -149,6 +187,7 @@ class OGRGeomTest(unittest.TestCase): def test08_multipolygons(self): "Testing MultiPolygon objects." + prev = OGRGeometry('POINT(0 0)') for mp in multipolygons: mpoly = OGRGeometry(mp.wkt) self.assertEqual(6, mpoly.geom_type) @@ -156,27 +195,53 @@ class OGRGeomTest(unittest.TestCase): if mp.valid: self.assertEqual(mp.n_p, mpoly.point_count) self.assertEqual(mp.num_geom, len(mpoly)) + self.assertRaises(OGRIndexError, mpoly.__getitem__, len(mpoly)) + for p in mpoly: + self.assertEqual('POLYGON', p.geom_name) + self.assertEqual(3, p.geom_type) + self.assertEqual(mpoly.wkt, OGRGeometry(mp.wkt).wkt) def test09_srs(self): "Testing OGR Geometries with Spatial Reference objects." for mp in multipolygons: + # Creating a geometry w/spatial reference sr = SpatialReference('WGS84') mpoly = OGRGeometry(mp.wkt, sr) self.assertEqual(sr.wkt, mpoly.srs.wkt) + + # Ensuring that SRS is propagated to clones. + klone = mpoly.clone() + self.assertEqual(sr.wkt, klone.srs.wkt) + + # Ensuring all children geometries (polygons and their rings) all + # return the assigned spatial reference as well. for poly in mpoly: self.assertEqual(sr.wkt, poly.srs.wkt) for ring in poly: self.assertEqual(sr.wkt, ring.srs.wkt) + # Ensuring SRS propagate in topological ops. + a, b = topology_geoms[0] + a, b = OGRGeometry(a.wkt, sr), OGRGeometry(b.wkt, sr) + diff = a.difference(b) + union = a.union(b) + self.assertEqual(sr.wkt, diff.srs.wkt) + self.assertEqual(sr.srid, union.srs.srid) + + # Instantiating w/an integer SRID mpoly = OGRGeometry(mp.wkt, 4326) self.assertEqual(4326, mpoly.srid) mpoly.srs = SpatialReference(4269) self.assertEqual(4269, mpoly.srid) self.assertEqual('NAD83', mpoly.srs.name) + + # Incrementing through the multipolyogn after the spatial reference + # has been re-assigned. for poly in mpoly: self.assertEqual(mpoly.srs.wkt, poly.srs.wkt) poly.srs = 32140 for ring in poly: + # Changing each ring in the polygon self.assertEqual(32140, ring.srs.srid) self.assertEqual('NAD83 / Texas South Central', ring.srs.name) ring.srs = str(SpatialReference(4326)) # back to WGS84 @@ -186,8 +251,59 @@ class OGRGeomTest(unittest.TestCase): ring.srid = 4322 self.assertEqual('WGS 72', ring.srs.name) self.assertEqual(4322, ring.srid) - + def test10_difference(self): + "Testing difference()." + for i in xrange(len(topology_geoms)): + g_tup = topology_geoms[i] + a = OGRGeometry(g_tup[0].wkt) + b = OGRGeometry(g_tup[1].wkt) + d1 = OGRGeometry(diff_geoms[i].wkt) + d2 = a.difference(b) + self.assertEqual(d1, d2) + self.assertEqual(d1, a - b) # __sub__ is difference operator + a -= b # testing __isub__ + self.assertEqual(d1, a) + + def test11_intersection(self): + "Testing intersects() and intersection()." + for i in xrange(len(topology_geoms)): + g_tup = topology_geoms[i] + a = OGRGeometry(g_tup[0].wkt) + b = OGRGeometry(g_tup[1].wkt) + i1 = OGRGeometry(intersect_geoms[i].wkt) + self.assertEqual(True, a.intersects(b)) + i2 = a.intersection(b) + self.assertEqual(i1, i2) + self.assertEqual(i1, a & b) # __and__ is intersection operator + a &= b # testing __iand__ + self.assertEqual(i1, a) + + def test12_symdifference(self): + "Testing sym_difference()." + for i in xrange(len(topology_geoms)): + g_tup = topology_geoms[i] + a = OGRGeometry(g_tup[0].wkt) + b = OGRGeometry(g_tup[1].wkt) + d1 = OGRGeometry(sdiff_geoms[i].wkt) + d2 = a.sym_difference(b) + self.assertEqual(d1, d2) + self.assertEqual(d1, a ^ b) # __xor__ is symmetric difference operator + a ^= b # testing __ixor__ + self.assertEqual(d1, a) + + def test13_union(self): + "Testing union()." + for i in xrange(len(topology_geoms)): + g_tup = topology_geoms[i] + a = OGRGeometry(g_tup[0].wkt) + b = OGRGeometry(g_tup[1].wkt) + u1 = OGRGeometry(union_geoms[i].wkt) + u2 = a.union(b) + self.assertEqual(u1, u2) + self.assertEqual(u1, a | b) # __or__ is union operator + a |= b # testing __ior__ + self.assertEqual(u1, a) def suite(): s = unittest.TestSuite()