Permalink
Browse files

gis: Added an intial ctypes interface to GDAL/OGR

  (1) SpatialReference allows for rich access to properties of spatial 
      reference systems.
  (2) OGRGeometry provides access to the OGR Geometry classes -- may be
      accessed from models with the get_GEOM_ogr() extra instance method.


git-svn-id: http://code.djangoproject.com/svn/django/branches/gis@5397 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
1 parent 38ff3cf commit b962a44a4d4bb6944c786d3b09277c5746c5755e @jbronn jbronn committed Jun 1, 2007
@@ -1,5 +1,6 @@
# GEOS Routines
from django.contrib.gis.geos import GEOSGeometry, hex_to_wkt, centroid, area
+from django.contrib.gis.gdal import OGRGeometry, SpatialReference
# Until model subclassing is a possibility, a mixin class is used to add
# the necessary functions that may be contributed for geographic objects.
@@ -12,6 +13,11 @@ def _get_GEOM_geos(self, field):
"Gets a GEOS Python object for the geometry."
return GEOSGeometry(getattr(self, field.attname), 'hex')
+ def _get_GEOM_ogr(self, field, srid):
+ "Gets an OGR Python object for the geometry."
+ return OGRGeometry(hex_to_wkt(getattr(self, field.attname)),
+ SpatialReference('EPSG:%d' % srid))
+
def _get_GEOM_wkt(self, field):
"Gets the WKT of the geometry."
hex = getattr(self, field.attname)
@@ -6,7 +6,7 @@
from django.contrib.gis.geos import GEOSGeometry, GEOSException
#TODO: Flesh out widgets.
-#TODO: geos operations through fields as proxy.
+#TODO: GEOS and GDAL/OGR operations through fields as proxy.
#TODO: pythonic usage, like "for point in zip.polygon" and "if point in polygon".
class GeometryField(Field):
@@ -38,8 +38,7 @@ def _add_geom(self, style, db_table, field):
AddGeometryColumn(...) PostGIS (and OGC standard) function.
Takes the style object (provides syntax highlighting) as well as the
- database table and field. The dimensions can be specified via
- the dim keyword as well.
+ database table and field.
"""
sql = style.SQL_KEYWORD('SELECT ') + \
style.SQL_TABLE('AddGeometryColumn') + '(' + \
@@ -81,8 +80,9 @@ def _post_create_sql(self, style, db_table, field):
def contribute_to_class(self, cls, name):
super(GeometryField, self).contribute_to_class(cls, name)
- # Adding the WKT accessor function for geometry
+ # Adding needed accessor functions
setattr(cls, 'get_%s_geos' % self.name, curry(cls._get_GEOM_geos, field=self))
+ setattr(cls, 'get_%s_ogr' % self.name, curry(cls._get_GEOM_ogr, field=self, srid=self._srid))
setattr(cls, 'get_%s_wkt' % self.name, curry(cls._get_GEOM_wkt, field=self))
setattr(cls, 'get_%s_centroid' % self.name, curry(cls._get_GEOM_centroid, field=self))
setattr(cls, 'get_%s_area' % self.name, curry(cls._get_GEOM_area, field=self))
@@ -1,6 +1,5 @@
# This module is meant to re-define the helper routines used by the
# django.db.models.query objects to be customized for PostGIS.
-from copy import copy
from django.db import backend
from django.db.models.query import LOOKUP_SEPARATOR, find_field, FieldFound, QUERY_TERMS, get_where_clause
from django.utils.datastructures import SortedDict
@@ -0,0 +1,103 @@
+# 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
+from django.contrib.gis.gdal.OGRError import OGRException, check_err
+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).
+
+ 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']
+
+ More documentation forthcoming.
+"""
+
+# 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."
+
+ _ds = 0 # Initially NULL
+
+ #### Python 'magic' routines ####
+ def __init__(self, ds_file):
+
+ # Registering all the drivers, this needs to be done
+ # _before_ we try to open up a data source.
+ if not lgdal.OGRRegisterAll():
+ raise OGRException, 'Could not register all data source drivers!'
+
+ # 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_file), c_int(0), byref(ds_driver))
+
+ # Raise an exception if the returned pointer is NULL
+ if not ds:
+ self._ds = False
+ raise OGRException, 'Invalid data source file "%s"' % ds_file
+ else:
+ self._ds = ds
+ self._driver = ds_driver
+
+ 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)
+
+ def __iter__(self):
+ "Allows for iteration over the layers in a data source."
+ for i in xrange(self.layer_count):
+ yield self.__getitem__(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 not l: raise IndexError, 'invalid OGR Layer name given: "%s"' % index
+ else:
+ if index < 0 or index >= self.layer_count:
+ raise IndexError, 'index out of range'
+ l = lgdal.OGR_DS_GetLayer(self._ds, c_int(index))
+ return Layer(l)
+
+ def __len__(self):
+ "Returns the number of layers within the data source."
+ return self.layer_count
+
+ def __str__(self):
+ "Returns OGR GetName and Driver for the Data Source."
+ return '%s (%s)' % (self.name, self.driver)
+
+ #### DataSource Properties ####
+ @property
+ def driver(self):
+ "Returns the name of the data source driver."
+ return string_at(lgdal.OGR_Dr_GetName(self._driver))
+
+ @property
+ def layer_count(self):
+ "Returns the number of layers in the data source."
+ return lgdal.OGR_DS_GetLayerCount(self._ds)
+
+ @property
+ def name(self):
+ "Returns the name of the data source."
+ return string_at(lgdal.OGR_DS_GetName(self._ds))
+
@@ -0,0 +1,104 @@
+# types and ctypes
+import types
+from ctypes import c_char_p, c_int, 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.OGRError import OGRException
+from django.contrib.gis.gdal.Field import Field
+from django.contrib.gis.gdal.OGRGeometry import OGRGeometry, OGRGeomType
+
+# For more information, see the OGR C API source code:
+# http://www.gdal.org/ogr/ogr__api_8h.html
+#
+# The OGR_F_* routines are relevant here.
+class Feature(object):
+ "A class that wraps an OGR Feature, needs to be instantiated from a Layer object."
+
+ _feat = 0 # Initially NULL
+
+ #### Python 'magic' routines ####
+ def __init__(self, f):
+ "Needs a C pointer (Python integer in ctypes) in order to initialize."
+ if not f:
+ raise OGRException, 'Cannot create OGR Feature, invalid pointer given.'
+ self._feat = f
+ self._fdefn = lgdal.OGR_F_GetDefnRef(f)
+
+ def __del__(self):
+ "Releases a reference to this object."
+ if self._fdefn: lgdal.OGR_FD_Release(self._fdefn)
+
+ def __getitem__(self, index):
+ "Gets the Field at the specified index."
+ if isinstance(index, types.StringType):
+ i = self.index(index)
+ else:
+ if index < 0 or index > self.num_fields:
+ raise IndexError, '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))))
+
+ def __iter__(self):
+ "Iterates over each field in the Feature."
+ for i in xrange(self.num_fields):
+ yield self.__getitem__(i)
+
+ def __len__(self):
+ "Returns the count of fields in this feature."
+ return self.num_fields
+
+ def __str__(self):
+ "The string name of the feature."
+ return 'Feature FID %d in Layer<%s>' % (self.fid, self.layer_name)
+
+ def __eq__(self, other):
+ "Does equivalence testing on the features."
+ if lgdal.OGR_F_Equal(self._feat, other._feat):
+ return True
+ else:
+ return False
+
+ #### Feature Properties ####
+ @property
+ def fid(self):
+ "Returns the feature identifier."
+ return lgdal.OGR_F_GetFID(self._feat)
+
+ @property
+ def layer_name(self):
+ "Returns the name of the layer for the feature."
+ return string_at(lgdal.OGR_FD_GetName(self._fdefn))
+
+ @property
+ def num_fields(self):
+ "Returns the number of fields in the Feature."
+ return lgdal.OGR_F_GetFieldCount(self._feat)
+
+ @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) ]
+ @property
+ def geom(self):
+ "Returns the OGR Geometry for this Feature."
+ # A clone is used, so destruction of the Geometry won't bork the Feature.
+ return OGRGeometry(lgdal.OGR_G_Clone(lgdal.OGR_F_GetGeometryRef(self._feat)))
+
+ @property
+ def geom_type(self):
+ "Returns the OGR Geometry Type for this Feture."
+ return OGRGeomType(lgdal.OGR_FD_GetGeomType(self._fdefn))
+
+ #### Feature Methods ####
+ def index(self, field_name):
+ "Returns the index of the given field name."
+ i = lgdal.OGR_F_GetFieldIndex(self._feat, c_char_p(field_name))
+ if i < 0: raise IndexError, 'invalid OFT field name given: "%s"' % field_name
+ return i
+
+ def clone(self):
+ "Clones this Feature."
+ return Feature(lgdal.OGR_F_Clone(self._feat))
@@ -0,0 +1,83 @@
+from ctypes import string_at
+
+from django.contrib.gis.gdal.libgdal import lgdal
+from django.contrib.gis.gdal.OGRError import OGRException
+
+# For more information, see the OGR C API source code:
+# http://www.gdal.org/ogr/ogr__api_8h.html
+#
+# The OGR_Fld_* routines are relevant here.
+class Field(object):
+ "A class that wraps an OGR Field."
+
+ _fld = 0 # Initially NULL
+
+ #### Python 'magic' routines ####
+ def __init__(self, fld, val=''):
+ "Needs a C pointer (Python integer in ctypes) in order to initialize."
+ if not fld:
+ raise OGRException, 'Cannot create OGR Field, invalid pointer given.'
+ self._fld = fld
+ self._val = val
+
+ # Setting the class depending upon the OGR Field Type (OFT)
+ self.__class__ = FIELD_CLASSES[self.type]
+
+ def __str__(self):
+ "Returns the string representation of the Field."
+ return '%s (%s)' % (self.name, self.__class__.__name__)
+
+ #### Field Properties ####
+ @property
+ def name(self):
+ "Returns the name of the field."
+ return string_at(lgdal.OGR_Fld_GetNameRef(self._fld))
+
+ @property
+ def type(self):
+ "Returns the type of this field."
+ return lgdal.OGR_Fld_GetType(self._fld)
+
+ @property
+ def value(self):
+ "Returns the value of this type of field."
+ return self._val
+
+# The Field sub-classes for each OGR Field type.
+class OFTInteger(Field):
+ @property
+ def value(self):
+ "Returns an integer contained in this field."
+ return int(self._val)
+
+class OFTIntegerList(Field): pass
+class OFTReal(Field):
+ @property
+ def value(self):
+ "Returns a float contained in this field."
+ return float(self._val)
+
+class OFTRealList(Field): pass
+class OFTString(Field): pass
+class OFTStringList(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
+
+# Class mapping dictionary for OFT Types
+FIELD_CLASSES = { 0 : OFTInteger,
+ 1 : OFTIntegerList,
+ 2 : OFTReal,
+ 3 : OFTRealList,
+ 4 : OFTString,
+ 5 : OFTStringList,
+ 6 : OFTWideString,
+ 7 : OFTWideStringList,
+ 8 : OFTBinary,
+ 9 : OFTDate,
+ 10 : OFTTime,
+ 11 : OFTDateTime,
+ }
@@ -0,0 +1,28 @@
+Copyright (c) 2007, Justin Bronn
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ 3. Neither the name of OGRGeometry nor the names of its contributors may be used
+ to endorse or promote products derived from this software without
+ specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
Oops, something went wrong.

0 comments on commit b962a44

Please sign in to comment.