Skip to content

Commit b962a44

Browse files
committed
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
1 parent 38ff3cf commit b962a44

27 files changed

+1674
-31
lines changed

django/contrib/gis/db/models/GeoMixin.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# GEOS Routines
22
from django.contrib.gis.geos import GEOSGeometry, hex_to_wkt, centroid, area
3+
from django.contrib.gis.gdal import OGRGeometry, SpatialReference
34

45
# Until model subclassing is a possibility, a mixin class is used to add
56
# the necessary functions that may be contributed for geographic objects.
@@ -12,6 +13,11 @@ def _get_GEOM_geos(self, field):
1213
"Gets a GEOS Python object for the geometry."
1314
return GEOSGeometry(getattr(self, field.attname), 'hex')
1415

16+
def _get_GEOM_ogr(self, field, srid):
17+
"Gets an OGR Python object for the geometry."
18+
return OGRGeometry(hex_to_wkt(getattr(self, field.attname)),
19+
SpatialReference('EPSG:%d' % srid))
20+
1521
def _get_GEOM_wkt(self, field):
1622
"Gets the WKT of the geometry."
1723
hex = getattr(self, field.attname)

django/contrib/gis/db/models/fields/__init__.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from django.contrib.gis.geos import GEOSGeometry, GEOSException
77

88
#TODO: Flesh out widgets.
9-
#TODO: geos operations through fields as proxy.
9+
#TODO: GEOS and GDAL/OGR operations through fields as proxy.
1010
#TODO: pythonic usage, like "for point in zip.polygon" and "if point in polygon".
1111

1212
class GeometryField(Field):
@@ -38,8 +38,7 @@ def _add_geom(self, style, db_table, field):
3838
AddGeometryColumn(...) PostGIS (and OGC standard) function.
3939
4040
Takes the style object (provides syntax highlighting) as well as the
41-
database table and field. The dimensions can be specified via
42-
the dim keyword as well.
41+
database table and field.
4342
"""
4443
sql = style.SQL_KEYWORD('SELECT ') + \
4544
style.SQL_TABLE('AddGeometryColumn') + '(' + \
@@ -81,8 +80,9 @@ def _post_create_sql(self, style, db_table, field):
8180
def contribute_to_class(self, cls, name):
8281
super(GeometryField, self).contribute_to_class(cls, name)
8382

84-
# Adding the WKT accessor function for geometry
83+
# Adding needed accessor functions
8584
setattr(cls, 'get_%s_geos' % self.name, curry(cls._get_GEOM_geos, field=self))
85+
setattr(cls, 'get_%s_ogr' % self.name, curry(cls._get_GEOM_ogr, field=self, srid=self._srid))
8686
setattr(cls, 'get_%s_wkt' % self.name, curry(cls._get_GEOM_wkt, field=self))
8787
setattr(cls, 'get_%s_centroid' % self.name, curry(cls._get_GEOM_centroid, field=self))
8888
setattr(cls, 'get_%s_area' % self.name, curry(cls._get_GEOM_area, field=self))

django/contrib/gis/db/models/postgis.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
# This module is meant to re-define the helper routines used by the
22
# django.db.models.query objects to be customized for PostGIS.
3-
from copy import copy
43
from django.db import backend
54
from django.db.models.query import LOOKUP_SEPARATOR, find_field, FieldFound, QUERY_TERMS, get_where_clause
65
from django.utils.datastructures import SortedDict

django/contrib/gis/gdal/DataSource.py

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# types and ctypes
2+
from types import StringType
3+
from ctypes import c_char_p, c_int, c_void_p, byref, string_at
4+
5+
# The GDAL C library, OGR exceptions, and the Layer object.
6+
from django.contrib.gis.gdal.libgdal import lgdal
7+
from django.contrib.gis.gdal.OGRError import OGRException, check_err
8+
from django.contrib.gis.gdal.Layer import Layer
9+
10+
"""
11+
DataSource is a wrapper for the OGR Data Source object, which provides
12+
an interface for reading vector geometry data from many different file
13+
formats (including ESRI shapefiles).
14+
15+
Example:
16+
ds = DataSource('/home/foo/bar.shp')
17+
for layer in ds:
18+
for feature in layer:
19+
# Getting the geometry for the feature.
20+
g = feature.geom
21+
22+
# Getting the 'description' field for the feature.
23+
desc = feature['description']
24+
25+
More documentation forthcoming.
26+
"""
27+
28+
# For more information, see the OGR C API source code:
29+
# http://www.gdal.org/ogr/ogr__api_8h.html
30+
#
31+
# The OGR_DS* routines are relevant here.
32+
33+
class DataSource(object):
34+
"Wraps an OGR Data Source object."
35+
36+
_ds = 0 # Initially NULL
37+
38+
#### Python 'magic' routines ####
39+
def __init__(self, ds_file):
40+
41+
# Registering all the drivers, this needs to be done
42+
# _before_ we try to open up a data source.
43+
if not lgdal.OGRRegisterAll():
44+
raise OGRException, 'Could not register all data source drivers!'
45+
46+
# The data source driver is a void pointer.
47+
ds_driver = c_void_p()
48+
49+
# OGROpen will auto-detect the data source type.
50+
ds = lgdal.OGROpen(c_char_p(ds_file), c_int(0), byref(ds_driver))
51+
52+
# Raise an exception if the returned pointer is NULL
53+
if not ds:
54+
self._ds = False
55+
raise OGRException, 'Invalid data source file "%s"' % ds_file
56+
else:
57+
self._ds = ds
58+
self._driver = ds_driver
59+
60+
def __del__(self):
61+
"This releases the reference to the data source (destroying it if it's the only one)."
62+
if self._ds: lgdal.OGRReleaseDataSource(self._ds)
63+
64+
def __iter__(self):
65+
"Allows for iteration over the layers in a data source."
66+
for i in xrange(self.layer_count):
67+
yield self.__getitem__(i)
68+
69+
def __getitem__(self, index):
70+
"Allows use of the index [] operator to get a layer at the index."
71+
if isinstance(index, StringType):
72+
l = lgdal.OGR_DS_GetLayerByName(self._ds, c_char_p(index))
73+
if not l: raise IndexError, 'invalid OGR Layer name given: "%s"' % index
74+
else:
75+
if index < 0 or index >= self.layer_count:
76+
raise IndexError, 'index out of range'
77+
l = lgdal.OGR_DS_GetLayer(self._ds, c_int(index))
78+
return Layer(l)
79+
80+
def __len__(self):
81+
"Returns the number of layers within the data source."
82+
return self.layer_count
83+
84+
def __str__(self):
85+
"Returns OGR GetName and Driver for the Data Source."
86+
return '%s (%s)' % (self.name, self.driver)
87+
88+
#### DataSource Properties ####
89+
@property
90+
def driver(self):
91+
"Returns the name of the data source driver."
92+
return string_at(lgdal.OGR_Dr_GetName(self._driver))
93+
94+
@property
95+
def layer_count(self):
96+
"Returns the number of layers in the data source."
97+
return lgdal.OGR_DS_GetLayerCount(self._ds)
98+
99+
@property
100+
def name(self):
101+
"Returns the name of the data source."
102+
return string_at(lgdal.OGR_DS_GetName(self._ds))
103+

django/contrib/gis/gdal/Feature.py

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
# types and ctypes
2+
import types
3+
from ctypes import c_char_p, c_int, string_at
4+
5+
# The GDAL C library, OGR exception, and the Field object
6+
from django.contrib.gis.gdal.libgdal import lgdal
7+
from django.contrib.gis.gdal.OGRError import OGRException
8+
from django.contrib.gis.gdal.Field import Field
9+
from django.contrib.gis.gdal.OGRGeometry import OGRGeometry, OGRGeomType
10+
11+
# For more information, see the OGR C API source code:
12+
# http://www.gdal.org/ogr/ogr__api_8h.html
13+
#
14+
# The OGR_F_* routines are relevant here.
15+
class Feature(object):
16+
"A class that wraps an OGR Feature, needs to be instantiated from a Layer object."
17+
18+
_feat = 0 # Initially NULL
19+
20+
#### Python 'magic' routines ####
21+
def __init__(self, f):
22+
"Needs a C pointer (Python integer in ctypes) in order to initialize."
23+
if not f:
24+
raise OGRException, 'Cannot create OGR Feature, invalid pointer given.'
25+
self._feat = f
26+
self._fdefn = lgdal.OGR_F_GetDefnRef(f)
27+
28+
def __del__(self):
29+
"Releases a reference to this object."
30+
if self._fdefn: lgdal.OGR_FD_Release(self._fdefn)
31+
32+
def __getitem__(self, index):
33+
"Gets the Field at the specified index."
34+
if isinstance(index, types.StringType):
35+
i = self.index(index)
36+
else:
37+
if index < 0 or index > self.num_fields:
38+
raise IndexError, 'index out of range'
39+
i = index
40+
return Field(lgdal.OGR_F_GetFieldDefnRef(self._feat, c_int(i)),
41+
string_at(lgdal.OGR_F_GetFieldAsString(self._feat, c_int(i))))
42+
43+
def __iter__(self):
44+
"Iterates over each field in the Feature."
45+
for i in xrange(self.num_fields):
46+
yield self.__getitem__(i)
47+
48+
def __len__(self):
49+
"Returns the count of fields in this feature."
50+
return self.num_fields
51+
52+
def __str__(self):
53+
"The string name of the feature."
54+
return 'Feature FID %d in Layer<%s>' % (self.fid, self.layer_name)
55+
56+
def __eq__(self, other):
57+
"Does equivalence testing on the features."
58+
if lgdal.OGR_F_Equal(self._feat, other._feat):
59+
return True
60+
else:
61+
return False
62+
63+
#### Feature Properties ####
64+
@property
65+
def fid(self):
66+
"Returns the feature identifier."
67+
return lgdal.OGR_F_GetFID(self._feat)
68+
69+
@property
70+
def layer_name(self):
71+
"Returns the name of the layer for the feature."
72+
return string_at(lgdal.OGR_FD_GetName(self._fdefn))
73+
74+
@property
75+
def num_fields(self):
76+
"Returns the number of fields in the Feature."
77+
return lgdal.OGR_F_GetFieldCount(self._feat)
78+
79+
@property
80+
def fields(self):
81+
"Returns a list of fields in the Feature."
82+
return [ string_at(lgdal.OGR_Fld_GetNameRef(lgdal.OGR_FD_GetFieldDefn(self._fdefn, i)))
83+
for i in xrange(self.num_fields) ]
84+
@property
85+
def geom(self):
86+
"Returns the OGR Geometry for this Feature."
87+
# A clone is used, so destruction of the Geometry won't bork the Feature.
88+
return OGRGeometry(lgdal.OGR_G_Clone(lgdal.OGR_F_GetGeometryRef(self._feat)))
89+
90+
@property
91+
def geom_type(self):
92+
"Returns the OGR Geometry Type for this Feture."
93+
return OGRGeomType(lgdal.OGR_FD_GetGeomType(self._fdefn))
94+
95+
#### Feature Methods ####
96+
def index(self, field_name):
97+
"Returns the index of the given field name."
98+
i = lgdal.OGR_F_GetFieldIndex(self._feat, c_char_p(field_name))
99+
if i < 0: raise IndexError, 'invalid OFT field name given: "%s"' % field_name
100+
return i
101+
102+
def clone(self):
103+
"Clones this Feature."
104+
return Feature(lgdal.OGR_F_Clone(self._feat))

django/contrib/gis/gdal/Field.py

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
from ctypes import string_at
2+
3+
from django.contrib.gis.gdal.libgdal import lgdal
4+
from django.contrib.gis.gdal.OGRError import OGRException
5+
6+
# For more information, see the OGR C API source code:
7+
# http://www.gdal.org/ogr/ogr__api_8h.html
8+
#
9+
# The OGR_Fld_* routines are relevant here.
10+
class Field(object):
11+
"A class that wraps an OGR Field."
12+
13+
_fld = 0 # Initially NULL
14+
15+
#### Python 'magic' routines ####
16+
def __init__(self, fld, val=''):
17+
"Needs a C pointer (Python integer in ctypes) in order to initialize."
18+
if not fld:
19+
raise OGRException, 'Cannot create OGR Field, invalid pointer given.'
20+
self._fld = fld
21+
self._val = val
22+
23+
# Setting the class depending upon the OGR Field Type (OFT)
24+
self.__class__ = FIELD_CLASSES[self.type]
25+
26+
def __str__(self):
27+
"Returns the string representation of the Field."
28+
return '%s (%s)' % (self.name, self.__class__.__name__)
29+
30+
#### Field Properties ####
31+
@property
32+
def name(self):
33+
"Returns the name of the field."
34+
return string_at(lgdal.OGR_Fld_GetNameRef(self._fld))
35+
36+
@property
37+
def type(self):
38+
"Returns the type of this field."
39+
return lgdal.OGR_Fld_GetType(self._fld)
40+
41+
@property
42+
def value(self):
43+
"Returns the value of this type of field."
44+
return self._val
45+
46+
# The Field sub-classes for each OGR Field type.
47+
class OFTInteger(Field):
48+
@property
49+
def value(self):
50+
"Returns an integer contained in this field."
51+
return int(self._val)
52+
53+
class OFTIntegerList(Field): pass
54+
class OFTReal(Field):
55+
@property
56+
def value(self):
57+
"Returns a float contained in this field."
58+
return float(self._val)
59+
60+
class OFTRealList(Field): pass
61+
class OFTString(Field): pass
62+
class OFTStringList(Field): pass
63+
class OFTWideString(Field): pass
64+
class OFTWideStringList(Field): pass
65+
class OFTBinary(Field): pass
66+
class OFTDate(Field): pass
67+
class OFTTime(Field): pass
68+
class OFTDateTime(Field): pass
69+
70+
# Class mapping dictionary for OFT Types
71+
FIELD_CLASSES = { 0 : OFTInteger,
72+
1 : OFTIntegerList,
73+
2 : OFTReal,
74+
3 : OFTRealList,
75+
4 : OFTString,
76+
5 : OFTStringList,
77+
6 : OFTWideString,
78+
7 : OFTWideStringList,
79+
8 : OFTBinary,
80+
9 : OFTDate,
81+
10 : OFTTime,
82+
11 : OFTDateTime,
83+
}

django/contrib/gis/gdal/LICENSE

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
Copyright (c) 2007, Justin Bronn
2+
All rights reserved.
3+
4+
Redistribution and use in source and binary forms, with or without modification,
5+
are permitted provided that the following conditions are met:
6+
7+
1. Redistributions of source code must retain the above copyright notice,
8+
this list of conditions and the following disclaimer.
9+
10+
2. Redistributions in binary form must reproduce the above copyright
11+
notice, this list of conditions and the following disclaimer in the
12+
documentation and/or other materials provided with the distribution.
13+
14+
3. Neither the name of OGRGeometry nor the names of its contributors may be used
15+
to endorse or promote products derived from this software without
16+
specific prior written permission.
17+
18+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
22+
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
25+
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28+

0 commit comments

Comments
 (0)