Maintenance refactor of the GDAL (OGR) ctypes interface. Changes include:

* All C API method explictly called from their prototype module, no longer imported via *.
* Applied DRY to C pointer management, classes that do so subclass from `GDALBase`.
* `OGRGeometry`: Added `from_bbox` class method (patch from Christopher Schmidt) and `kml` property.
* `SpatialReference`: Now initialize with `SetFromUserInput` (initialization is now more simple and flexible); removed duplicate methods.
* `Envelope`: Added `expand_to_include` method and now allow same coordinates for lower left and upper right points.  Thanks to Paul Smith for tickets and patches.
* `OGRGeomType`: Now treat OGC 'Geometry' type as 'Unknown'.

Fixed #9855, #10368, #10380.  Refs #9806.


git-svn-id: http://code.djangoproject.com/svn/django/trunk@9985 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Justin Bronn 2009-03-07 23:02:48 +00:00
parent 53da1e4794
commit f1ada99997
22 changed files with 515 additions and 372 deletions

View File

@ -1,4 +1,4 @@
Copyright (c) 2007, Justin Bronn Copyright (c) 2007-2009, Justin Bronn
All rights reserved. All rights reserved.
Redistribution and use in source and binary forms, with or without modification, Redistribution and use in source and binary forms, with or without modification,

View File

@ -0,0 +1,35 @@
from ctypes import c_void_p
from types import NoneType
from django.contrib.gis.gdal.error import GDALException
class GDALBase(object):
"""
Base object for GDAL objects that has a pointer access property
that controls access to the underlying C pointer.
"""
# Initially the pointer is NULL.
_ptr = None
# Default allowed pointer type.
ptr_type = c_void_p
# Pointer access property.
def _get_ptr(self):
# Raise an exception if the pointer isn't valid don't
# want to be passing NULL pointers to routines --
# that's very bad.
if self._ptr: return self._ptr
else: raise GDALException('GDAL %s pointer no longer valid.' % self.__class__.__name__)
def _set_ptr(self, ptr):
# Only allow the pointer to be set with pointers of the
# compatible type or None (NULL).
if isinstance(ptr, int):
self._ptr = self.ptr_type(ptr)
elif isinstance(ptr, (self.ptr_type, NoneType)):
self._ptr = ptr
else:
raise TypeError('Incompatible pointer type')
ptr = property(_get_ptr, _set_ptr)

View File

@ -37,28 +37,23 @@
from ctypes import byref, c_void_p from ctypes import byref, c_void_p
# The GDAL C library, OGR exceptions, and the Layer object. # The GDAL C library, OGR exceptions, and the Layer object.
from django.contrib.gis.gdal.base import GDALBase
from django.contrib.gis.gdal.driver import Driver from django.contrib.gis.gdal.driver import Driver
from django.contrib.gis.gdal.error import OGRException, OGRIndexError from django.contrib.gis.gdal.error import OGRException, OGRIndexError
from django.contrib.gis.gdal.layer import Layer from django.contrib.gis.gdal.layer import Layer
# Getting the ctypes prototypes for the DataSource. # Getting the ctypes prototypes for the DataSource.
from django.contrib.gis.gdal.prototypes.ds import \ from django.contrib.gis.gdal.prototypes import ds as capi
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: # For more information, see the OGR C API source code:
# http://www.gdal.org/ogr/ogr__api_8h.html # http://www.gdal.org/ogr/ogr__api_8h.html
# #
# The OGR_DS_* routines are relevant here. # The OGR_DS_* routines are relevant here.
class DataSource(object): class DataSource(GDALBase):
"Wraps an OGR Data Source object." "Wraps an OGR Data Source object."
#### Python 'magic' routines #### #### Python 'magic' routines ####
def __init__(self, ds_input, ds_driver=False, write=False): def __init__(self, ds_input, ds_driver=False, write=False):
# DataSource pointer is initially NULL.
self._ptr = None
# The write flag. # The write flag.
if write: if write:
self._write = 1 self._write = 1
@ -67,33 +62,34 @@ class DataSource(object):
# Registering all the drivers, this needs to be done # Registering all the drivers, this needs to be done
# _before_ we try to open up a data source. # _before_ we try to open up a data source.
if not get_driver_count(): register_all() if not capi.get_driver_count():
capi.register_all()
if isinstance(ds_input, basestring): if isinstance(ds_input, basestring):
# The data source driver is a void pointer. # The data source driver is a void pointer.
ds_driver = c_void_p() ds_driver = Driver.ptr_type()
try: try:
# OGROpen will auto-detect the data source type. # OGROpen will auto-detect the data source type.
ds = open_ds(ds_input, self._write, byref(ds_driver)) ds = capi.open_ds(ds_input, self._write, byref(ds_driver))
except OGRException: except OGRException:
# Making the error message more clear rather than something # Making the error message more clear rather than something
# like "Invalid pointer returned from OGROpen". # like "Invalid pointer returned from OGROpen".
raise OGRException('Could not open the datasource at "%s"' % ds_input) raise OGRException('Could not open the datasource at "%s"' % ds_input)
elif isinstance(ds_input, c_void_p) and isinstance(ds_driver, c_void_p): elif isinstance(ds_input, self.ptr_type) and isinstance(ds_driver, Driver.ptr_type):
ds = ds_input ds = ds_input
else: else:
raise OGRException('Invalid data source input type: %s' % type(ds_input)) raise OGRException('Invalid data source input type: %s' % type(ds_input))
if bool(ds): if bool(ds):
self._ptr = ds self.ptr = ds
self._driver = Driver(ds_driver) self.driver = Driver(ds_driver)
else: else:
# Raise an exception if the returned pointer is NULL # Raise an exception if the returned pointer is NULL
raise OGRException('Invalid data source file "%s"' % ds_input) raise OGRException('Invalid data source file "%s"' % ds_input)
def __del__(self): def __del__(self):
"Destroys this DataStructure object." "Destroys this DataStructure object."
if self._ptr: destroy_ds(self._ptr) if self._ptr: capi.destroy_ds(self._ptr)
def __iter__(self): def __iter__(self):
"Allows for iteration over the layers in a data source." "Allows for iteration over the layers in a data source."
@ -103,12 +99,12 @@ class DataSource(object):
def __getitem__(self, index): def __getitem__(self, index):
"Allows use of the index [] operator to get a layer at the index." "Allows use of the index [] operator to get a layer at the index."
if isinstance(index, basestring): if isinstance(index, basestring):
l = get_layer_by_name(self._ptr, index) l = capi.get_layer_by_name(self.ptr, index)
if not l: raise OGRIndexError('invalid OGR Layer name given: "%s"' % index) if not l: raise OGRIndexError('invalid OGR Layer name given: "%s"' % index)
elif isinstance(index, int): elif isinstance(index, int):
if index < 0 or index >= self.layer_count: if index < 0 or index >= self.layer_count:
raise OGRIndexError('index out of range') raise OGRIndexError('index out of range')
l = get_layer(self._ptr, index) l = capi.get_layer(self._ptr, index)
else: else:
raise TypeError('Invalid index type: %s' % type(index)) raise TypeError('Invalid index type: %s' % type(index))
return Layer(l, self) return Layer(l, self)
@ -121,18 +117,12 @@ class DataSource(object):
"Returns OGR GetName and Driver for the Data Source." "Returns OGR GetName and Driver for the Data Source."
return '%s (%s)' % (self.name, str(self.driver)) return '%s (%s)' % (self.name, str(self.driver))
#### DataSource Properties ####
@property
def driver(self):
"Returns the Driver object for this Data Source."
return self._driver
@property @property
def layer_count(self): def layer_count(self):
"Returns the number of layers in the data source." "Returns the number of layers in the data source."
return get_layer_count(self._ptr) return capi.get_layer_count(self._ptr)
@property @property
def name(self): def name(self):
"Returns the name of the data source." "Returns the name of the data source."
return get_ds_name(self._ptr) return capi.get_ds_name(self._ptr)

View File

@ -1,14 +1,14 @@
# prerequisites imports # prerequisites imports
from ctypes import c_void_p from ctypes import c_void_p
from django.contrib.gis.gdal.base import GDALBase
from django.contrib.gis.gdal.error import OGRException from django.contrib.gis.gdal.error import OGRException
from django.contrib.gis.gdal.prototypes.ds import \ from django.contrib.gis.gdal.prototypes import ds as capi
get_driver, get_driver_by_name, get_driver_count, get_driver_name, register_all
# For more information, see the OGR C API source code: # For more information, see the OGR C API source code:
# http://www.gdal.org/ogr/ogr__api_8h.html # http://www.gdal.org/ogr/ogr__api_8h.html
# #
# The OGR_Dr_* routines are relevant here. # The OGR_Dr_* routines are relevant here.
class Driver(object): class Driver(GDALBase):
"Wraps an OGR Data Source Driver." "Wraps an OGR Data Source Driver."
# Case-insensitive aliases for OGR Drivers. # Case-insensitive aliases for OGR Drivers.
@ -24,7 +24,6 @@ class Driver(object):
if isinstance(dr_input, basestring): if isinstance(dr_input, basestring):
# If a string name of the driver was passed in # If a string name of the driver was passed in
self._ptr = None # Initially NULL
self._register() self._register()
# Checking the alias dictionary (case-insensitive) to see if an alias # Checking the alias dictionary (case-insensitive) to see if an alias
@ -35,10 +34,10 @@ class Driver(object):
name = dr_input name = dr_input
# Attempting to get the OGR driver by the string name. # Attempting to get the OGR driver by the string name.
dr = get_driver_by_name(name) dr = capi.get_driver_by_name(name)
elif isinstance(dr_input, int): elif isinstance(dr_input, int):
self._register() self._register()
dr = get_driver(dr_input) dr = capi.get_driver(dr_input)
elif isinstance(dr_input, c_void_p): elif isinstance(dr_input, c_void_p):
dr = dr_input dr = dr_input
else: else:
@ -47,20 +46,20 @@ class Driver(object):
# Making sure we get a valid pointer to the OGR Driver # Making sure we get a valid pointer to the OGR Driver
if not dr: if not dr:
raise OGRException('Could not initialize OGR Driver on input: %s' % str(dr_input)) raise OGRException('Could not initialize OGR Driver on input: %s' % str(dr_input))
self._ptr = dr self.ptr = dr
def __str__(self): def __str__(self):
"Returns the string name of the OGR Driver." "Returns the string name of the OGR Driver."
return get_driver_name(self._ptr) return capi.get_driver_name(self.ptr)
def _register(self): def _register(self):
"Attempts to register all the data source drivers." "Attempts to register all the data source drivers."
# Only register all if the driver count is 0 (or else all drivers # Only register all if the driver count is 0 (or else all drivers
# will be registered over and over again) # will be registered over and over again)
if not self.driver_count: register_all() if not self.driver_count: capi.register_all()
# Driver properties # Driver properties
@property @property
def driver_count(self): def driver_count(self):
"Returns the number of OGR data source drivers registered." "Returns the number of OGR data source drivers registered."
return get_driver_count() return capi.get_driver_count()

View File

@ -11,7 +11,6 @@
Lower left (min_x, min_y) o----------+ Lower left (min_x, min_y) o----------+
""" """
from ctypes import Structure, c_double from ctypes import Structure, c_double
from types import TupleType, ListType
from django.contrib.gis.gdal.error import OGRException from django.contrib.gis.gdal.error import OGRException
# The OGR definition of an Envelope is a C structure containing four doubles. # The OGR definition of an Envelope is a C structure containing four doubles.
@ -42,7 +41,7 @@ class Envelope(object):
if isinstance(args[0], OGREnvelope): if isinstance(args[0], OGREnvelope):
# OGREnvelope (a ctypes Structure) was passed in. # OGREnvelope (a ctypes Structure) was passed in.
self._envelope = args[0] self._envelope = args[0]
elif isinstance(args[0], (TupleType, ListType)): elif isinstance(args[0], (tuple, list)):
# A tuple was passed in. # A tuple was passed in.
if len(args[0]) != 4: if len(args[0]) != 4:
raise OGRException('Incorrect number of tuple elements (%d).' % len(args[0])) raise OGRException('Incorrect number of tuple elements (%d).' % len(args[0]))
@ -58,10 +57,10 @@ class Envelope(object):
raise OGRException('Incorrect number (%d) of arguments.' % len(args)) raise OGRException('Incorrect number (%d) of arguments.' % len(args))
# Checking the x,y coordinates # Checking the x,y coordinates
if self.min_x >= self.max_x: if self.min_x > self.max_x:
raise OGRException('Envelope minimum X >= maximum X.') raise OGRException('Envelope minimum X > maximum X.')
if self.min_y >= self.max_y: if self.min_y > self.max_y:
raise OGRException('Envelope minimum Y >= maximum Y.') raise OGRException('Envelope minimum Y > maximum Y.')
def __eq__(self, other): def __eq__(self, other):
""" """
@ -71,7 +70,7 @@ class Envelope(object):
if isinstance(other, Envelope): if isinstance(other, Envelope):
return (self.min_x == other.min_x) and (self.min_y == other.min_y) and \ return (self.min_x == other.min_x) and (self.min_y == other.min_y) and \
(self.max_x == other.max_x) and (self.max_y == other.max_y) (self.max_x == other.max_x) and (self.max_y == other.max_y)
elif isinstance(other, TupleType) and len(other) == 4: elif isinstance(other, tuple) and len(other) == 4:
return (self.min_x == other[0]) and (self.min_y == other[1]) and \ return (self.min_x == other[0]) and (self.min_y == other[1]) and \
(self.max_x == other[2]) and (self.max_y == other[3]) (self.max_x == other[2]) and (self.max_y == other[3])
else: else:
@ -89,6 +88,48 @@ class Envelope(object):
self._envelope.MaxX = seq[2] self._envelope.MaxX = seq[2]
self._envelope.MaxY = seq[3] self._envelope.MaxY = seq[3]
def expand_to_include(self, *args):
"""
Modifies the envelope to expand to include the boundaries of
the passed-in 2-tuple (a point), 4-tuple (an extent) or
envelope.
"""
# We provide a number of different signatures for this method,
# and the logic here is all about converting them into a
# 4-tuple single parameter which does the actual work of
# expanding the envelope.
if len(args) == 1:
if isinstance(args[0], Envelope):
return self.expand_to_include(args[0].tuple)
elif hasattr(args[0], 'x') and hasattr(args[0], 'y'):
return self.expand_to_include(args[0].x, args[0].y, args[0].x, args[0].y)
elif isinstance(args[0], (tuple, list)):
# A tuple was passed in.
if len(args[0]) == 2:
return self.expand_to_include((args[0][0], args[0][1], args[0][0], args[0][1]))
elif len(args[0]) == 4:
(minx, miny, maxx, maxy) = args[0]
if minx < self._envelope.MinX:
self._envelope.MinX = minx
if miny < self._envelope.MinY:
self._envelope.MinY = miny
if maxx > self._envelope.MaxX:
self._envelope.MaxX = maxx
if maxy > self._envelope.MaxY:
self._envelope.MaxY = maxy
else:
raise OGRException('Incorrect number of tuple elements (%d).' % len(args[0]))
else:
raise TypeError('Incorrect type of argument: %s' % str(type(args[0])))
elif len(args) == 2:
# An x and an y parameter were passed in
return self.expand_to_include((args[0], args[1], args[0], args[1]))
elif len(args) == 4:
# Individiual parameters passed in.
return self.expand_to_include(args)
else:
raise OGRException('Incorrect number (%d) of arguments.' % len(args[0]))
@property @property
def min_x(self): def min_x(self):
"Returns the value of the minimum X coordinate." "Returns the value of the minimum X coordinate."

View File

@ -4,6 +4,7 @@
OGR methods. OGR methods.
""" """
#### OGR & SRS Exceptions #### #### OGR & SRS Exceptions ####
class GDALException(Exception): pass
class OGRException(Exception): pass class OGRException(Exception): pass
class SRSException(Exception): pass class SRSException(Exception): pass
class OGRIndexError(OGRException, KeyError): class OGRIndexError(OGRException, KeyError):

View File

@ -1,36 +1,31 @@
# The GDAL C library, OGR exception, and the Field object # The GDAL C library, OGR exception, and the Field object
from django.contrib.gis.gdal.base import GDALBase
from django.contrib.gis.gdal.error import OGRException, OGRIndexError from django.contrib.gis.gdal.error import OGRException, OGRIndexError
from django.contrib.gis.gdal.field import Field from django.contrib.gis.gdal.field import Field
from django.contrib.gis.gdal.geometries import OGRGeometry, OGRGeomType from django.contrib.gis.gdal.geometries import OGRGeometry, OGRGeomType
from django.contrib.gis.gdal.srs import SpatialReference from django.contrib.gis.gdal.srs import SpatialReference
# ctypes function prototypes # ctypes function prototypes
from django.contrib.gis.gdal.prototypes.ds import \ from django.contrib.gis.gdal.prototypes import ds as capi, geom as geom_api
destroy_feature, feature_equal, get_fd_geom_type, get_feat_geom_ref, \
get_feat_name, get_feat_field_count, get_fid, get_field_defn, \
get_field_index, get_field_name
from django.contrib.gis.gdal.prototypes.geom import clone_geom, get_geom_srs
from django.contrib.gis.gdal.prototypes.srs import clone_srs
# For more information, see the OGR C API source code: # For more information, see the OGR C API source code:
# http://www.gdal.org/ogr/ogr__api_8h.html # http://www.gdal.org/ogr/ogr__api_8h.html
# #
# The OGR_F_* routines are relevant here. # The OGR_F_* routines are relevant here.
class Feature(object): class Feature(GDALBase):
"A class that wraps an OGR Feature, needs to be instantiated from a Layer object." "A class that wraps an OGR Feature, needs to be instantiated from a Layer object."
#### Python 'magic' routines #### #### Python 'magic' routines ####
def __init__(self, feat, fdefn): def __init__(self, feat, fdefn):
"Initializes on the pointers for the feature and the layer definition." "Initializes on the pointers for the feature and the layer definition."
self._ptr = None # Initially NULL
if not feat or not fdefn: if not feat or not fdefn:
raise OGRException('Cannot create OGR Feature, invalid pointer given.') raise OGRException('Cannot create OGR Feature, invalid pointer given.')
self._ptr = feat self.ptr = feat
self._fdefn = fdefn self._fdefn = fdefn
def __del__(self): def __del__(self):
"Releases a reference to this object." "Releases a reference to this object."
if self._ptr: destroy_feature(self._ptr) if self._ptr: capi.destroy_feature(self._ptr)
def __getitem__(self, index): def __getitem__(self, index):
""" """
@ -45,7 +40,7 @@ class Feature(object):
if index < 0 or index > self.num_fields: if index < 0 or index > self.num_fields:
raise OGRIndexError('index out of range') raise OGRIndexError('index out of range')
i = index i = index
return Field(self._ptr, i) return Field(self.ptr, i)
def __iter__(self): def __iter__(self):
"Iterates over each field in the Feature." "Iterates over each field in the Feature."
@ -62,41 +57,41 @@ class Feature(object):
def __eq__(self, other): def __eq__(self, other):
"Does equivalence testing on the features." "Does equivalence testing on the features."
return bool(feature_equal(self._ptr, other._ptr)) return bool(capi.feature_equal(self.ptr, other._ptr))
#### Feature Properties #### #### Feature Properties ####
@property @property
def fid(self): def fid(self):
"Returns the feature identifier." "Returns the feature identifier."
return get_fid(self._ptr) return capi.get_fid(self.ptr)
@property @property
def layer_name(self): def layer_name(self):
"Returns the name of the layer for the feature." "Returns the name of the layer for the feature."
return get_feat_name(self._fdefn) return capi.get_feat_name(self._fdefn)
@property @property
def num_fields(self): def num_fields(self):
"Returns the number of fields in the Feature." "Returns the number of fields in the Feature."
return get_feat_field_count(self._ptr) return capi.get_feat_field_count(self.ptr)
@property @property
def fields(self): def fields(self):
"Returns a list of fields in the Feature." "Returns a list of fields in the Feature."
return [get_field_name(get_field_defn(self._fdefn, i)) return [capi.get_field_name(capi.get_field_defn(self._fdefn, i))
for i in xrange(self.num_fields)] for i in xrange(self.num_fields)]
@property @property
def geom(self): def geom(self):
"Returns the OGR Geometry for this Feature." "Returns the OGR Geometry for this Feature."
# Retrieving the geometry pointer for the feature. # Retrieving the geometry pointer for the feature.
geom_ptr = get_feat_geom_ref(self._ptr) geom_ptr = capi.get_feat_geom_ref(self.ptr)
return OGRGeometry(clone_geom(geom_ptr)) return OGRGeometry(geom_api.clone_geom(geom_ptr))
@property @property
def geom_type(self): def geom_type(self):
"Returns the OGR Geometry Type for this Feture." "Returns the OGR Geometry Type for this Feture."
return OGRGeomType(get_fd_geom_type(self._fdefn)) return OGRGeomType(capi.get_fd_geom_type(self._fdefn))
#### Feature Methods #### #### Feature Methods ####
def get(self, field): def get(self, field):
@ -110,6 +105,6 @@ class Feature(object):
def index(self, field_name): def index(self, field_name):
"Returns the index of the given field name." "Returns the index of the given field name."
i = get_field_index(self._ptr, field_name) i = capi.get_field_index(self.ptr, field_name)
if i < 0: raise OGRIndexError('invalid OFT field name given: "%s"' % field_name) if i < 0: raise OGRIndexError('invalid OFT field name given: "%s"' % field_name)
return i return i

View File

@ -1,16 +1,14 @@
from ctypes import byref, c_int from ctypes import byref, c_int
from datetime import date, datetime, time from datetime import date, datetime, time
from django.contrib.gis.gdal.base import GDALBase
from django.contrib.gis.gdal.error import OGRException from django.contrib.gis.gdal.error import OGRException
from django.contrib.gis.gdal.prototypes.ds import \ from django.contrib.gis.gdal.prototypes import ds as capi
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: # For more information, see the OGR C API source code:
# http://www.gdal.org/ogr/ogr__api_8h.html # http://www.gdal.org/ogr/ogr__api_8h.html
# #
# The OGR_Fld_* routines are relevant here. # The OGR_Fld_* routines are relevant here.
class Field(object): class Field(GDALBase):
"A class that wraps an OGR Field, needs to be instantiated from a Feature object." "A class that wraps an OGR Field, needs to be instantiated from a Feature object."
#### Python 'magic' routines #### #### Python 'magic' routines ####
@ -24,13 +22,13 @@ class Field(object):
self._index = index self._index = index
# Getting the pointer for this field. # Getting the pointer for this field.
fld = get_feat_field_defn(feat, index) fld_ptr = capi.get_feat_field_defn(feat, index)
if not fld: if not fld_ptr:
raise OGRException('Cannot create OGR Field, invalid pointer given.') raise OGRException('Cannot create OGR Field, invalid pointer given.')
self._ptr = fld self.ptr = fld_ptr
# Setting the class depending upon the OGR Field Type (OFT) # Setting the class depending upon the OGR Field Type (OFT)
self.__class__ = FIELD_CLASSES[self.type] self.__class__ = OGRFieldTypes[self.type]
# OFTReal with no precision should be an OFTInteger. # OFTReal with no precision should be an OFTInteger.
if isinstance(self, OFTReal) and self.precision == 0: if isinstance(self, OFTReal) and self.precision == 0:
@ -43,21 +41,21 @@ class Field(object):
#### Field Methods #### #### Field Methods ####
def as_double(self): def as_double(self):
"Retrieves the Field's value as a double (float)." "Retrieves the Field's value as a double (float)."
return get_field_as_double(self._feat, self._index) return capi.get_field_as_double(self._feat, self._index)
def as_int(self): def as_int(self):
"Retrieves the Field's value as an integer." "Retrieves the Field's value as an integer."
return get_field_as_integer(self._feat, self._index) return capi.get_field_as_integer(self._feat, self._index)
def as_string(self): def as_string(self):
"Retrieves the Field's value as a string." "Retrieves the Field's value as a string."
return get_field_as_string(self._feat, self._index) return capi.get_field_as_string(self._feat, self._index)
def as_datetime(self): def as_datetime(self):
"Retrieves the Field's value as a tuple of date & time components." "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)] 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), status = capi.get_field_as_datetime(self._feat, self._index, byref(yy), byref(mm), byref(dd),
byref(hh), byref(mn), byref(ss), byref(tz)) byref(hh), byref(mn), byref(ss), byref(tz))
if status: if status:
return (yy, mm, dd, hh, mn, ss, tz) return (yy, mm, dd, hh, mn, ss, tz)
else: else:
@ -67,22 +65,22 @@ class Field(object):
@property @property
def name(self): def name(self):
"Returns the name of this Field." "Returns the name of this Field."
return get_field_name(self._ptr) return capi.get_field_name(self.ptr)
@property @property
def precision(self): def precision(self):
"Returns the precision of this Field." "Returns the precision of this Field."
return get_field_precision(self._ptr) return capi.get_field_precision(self.ptr)
@property @property
def type(self): def type(self):
"Returns the OGR type of this Field." "Returns the OGR type of this Field."
return get_field_type(self._ptr) return capi.get_field_type(self.ptr)
@property @property
def type_name(self): def type_name(self):
"Return the OGR field type name for this Field." "Return the OGR field type name for this Field."
return get_field_type_name(self.type) return capi.get_field_type_name(self.type)
@property @property
def value(self): def value(self):
@ -93,7 +91,7 @@ class Field(object):
@property @property
def width(self): def width(self):
"Returns the width of this Field." "Returns the width of this Field."
return get_field_width(self._ptr) return capi.get_field_width(self.ptr)
### The Field sub-classes for each OGR Field type. ### ### The Field sub-classes for each OGR Field type. ###
class OFTInteger(Field): class OFTInteger(Field):
@ -163,8 +161,8 @@ class OFTRealList(Field): pass
class OFTStringList(Field): pass class OFTStringList(Field): pass
class OFTWideStringList(Field): pass class OFTWideStringList(Field): pass
# Class mapping dictionary for OFT Types # Class mapping dictionary for OFT Types and reverse mapping.
FIELD_CLASSES = { 0 : OFTInteger, OGRFieldTypes = { 0 : OFTInteger,
1 : OFTIntegerList, 1 : OFTIntegerList,
2 : OFTReal, 2 : OFTReal,
3 : OFTRealList, 3 : OFTRealList,
@ -177,3 +175,4 @@ FIELD_CLASSES = { 0 : OFTInteger,
10 : OFTTime, 10 : OFTTime,
11 : OFTDateTime, 11 : OFTDateTime,
} }
ROGRFieldTypes = dict([(cls, num) for num, cls in OGRFieldTypes.items()])

View File

@ -44,14 +44,15 @@ from binascii import a2b_hex
from ctypes import byref, string_at, c_char_p, c_double, c_ubyte, c_void_p from ctypes import byref, string_at, c_char_p, c_double, c_ubyte, c_void_p
# Getting GDAL prerequisites # Getting GDAL prerequisites
from django.contrib.gis.gdal.base import GDALBase
from django.contrib.gis.gdal.envelope import Envelope, OGREnvelope from django.contrib.gis.gdal.envelope import Envelope, OGREnvelope
from django.contrib.gis.gdal.error import OGRException, OGRIndexError, SRSException from django.contrib.gis.gdal.error import OGRException, OGRIndexError, SRSException
from django.contrib.gis.gdal.geomtype import OGRGeomType from django.contrib.gis.gdal.geomtype import OGRGeomType
from django.contrib.gis.gdal.srs import SpatialReference, CoordTransform from django.contrib.gis.gdal.srs import SpatialReference, CoordTransform
# Getting the ctypes prototype functions that interface w/the GDAL C library. # 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 import geom as capi, srs as srs_api
from django.contrib.gis.gdal.prototypes.srs import clone_srs GEOJSON = capi.GEOJSON
# For more information, see the OGR C API source code: # For more information, see the OGR C API source code:
# http://www.gdal.org/ogr/ogr__api_8h.html # http://www.gdal.org/ogr/ogr__api_8h.html
@ -64,13 +65,12 @@ wkt_regex = re.compile(r'^(?P<type>POINT|LINESTRING|LINEARRING|POLYGON|MULTIPOIN
json_regex = re.compile(r'^(\s+)?\{[\s\w,\[\]\{\}\-\."\':]+\}(\s+)?$') json_regex = re.compile(r'^(\s+)?\{[\s\w,\[\]\{\}\-\."\':]+\}(\s+)?$')
#### OGRGeometry Class #### #### OGRGeometry Class ####
class OGRGeometry(object): class OGRGeometry(GDALBase):
"Generally encapsulates an OGR geometry." "Generally encapsulates an OGR geometry."
def __init__(self, geom_input, srs=None): def __init__(self, geom_input, srs=None):
"Initializes Geometry on either WKT or an OGR pointer as input." "Initializes Geometry on either WKT or an OGR pointer as input."
self._ptr = c_void_p(None) # Initially NULL
str_instance = isinstance(geom_input, basestring) str_instance = isinstance(geom_input, basestring)
# If HEX, unpack input to to a binary buffer. # If HEX, unpack input to to a binary buffer.
@ -91,27 +91,27 @@ class OGRGeometry(object):
if wkt_m.group('type').upper() == 'LINEARRING': if wkt_m.group('type').upper() == 'LINEARRING':
# OGR_G_CreateFromWkt doesn't work with LINEARRING WKT. # OGR_G_CreateFromWkt doesn't work with LINEARRING WKT.
# See http://trac.osgeo.org/gdal/ticket/1992. # See http://trac.osgeo.org/gdal/ticket/1992.
g = create_geom(OGRGeomType(wkt_m.group('type')).num) g = capi.create_geom(OGRGeomType(wkt_m.group('type')).num)
import_wkt(g, byref(c_char_p(geom_input))) capi.import_wkt(g, byref(c_char_p(geom_input)))
else: else:
g = from_wkt(byref(c_char_p(geom_input)), None, byref(c_void_p())) g = capi.from_wkt(byref(c_char_p(geom_input)), None, byref(c_void_p()))
elif json_m: elif json_m:
if GEOJSON: if GEOJSON:
g = from_json(geom_input) g = capi.from_json(geom_input)
else: else:
raise NotImplementedError('GeoJSON input only supported on GDAL 1.5+.') raise NotImplementedError('GeoJSON input only supported on GDAL 1.5+.')
else: else:
# Seeing if the input is a valid short-hand string # Seeing if the input is a valid short-hand string
# (e.g., 'Point', 'POLYGON'). # (e.g., 'Point', 'POLYGON').
ogr_t = OGRGeomType(geom_input) ogr_t = OGRGeomType(geom_input)
g = create_geom(OGRGeomType(geom_input).num) g = capi.create_geom(OGRGeomType(geom_input).num)
elif isinstance(geom_input, buffer): elif isinstance(geom_input, buffer):
# WKB was passed in # WKB was passed in
g = from_wkb(str(geom_input), None, byref(c_void_p()), len(geom_input)) g = capi.from_wkb(str(geom_input), None, byref(c_void_p()), len(geom_input))
elif isinstance(geom_input, OGRGeomType): elif isinstance(geom_input, OGRGeomType):
# OGRGeomType was passed in, an empty geometry will be created. # OGRGeomType was passed in, an empty geometry will be created.
g = create_geom(geom_input.num) g = capi.create_geom(geom_input.num)
elif isinstance(geom_input, c_void_p): elif isinstance(geom_input, self.ptr_type):
# OGR pointer (c_void_p) was the input. # OGR pointer (c_void_p) was the input.
g = geom_input g = geom_input
else: else:
@ -121,7 +121,7 @@ class OGRGeometry(object):
# by setting the pointer for the object. # by setting the pointer for the object.
if not g: if not g:
raise OGRException('Cannot create OGR Geometry from input: %s' % str(geom_input)) raise OGRException('Cannot create OGR Geometry from input: %s' % str(geom_input))
self._ptr = g self.ptr = g
# Assigning the SpatialReference object to the geometry, if valid. # Assigning the SpatialReference object to the geometry, if valid.
if bool(srs): self.srs = srs if bool(srs): self.srs = srs
@ -129,9 +129,16 @@ class OGRGeometry(object):
# Setting the class depending upon the OGR Geometry Type # Setting the class depending upon the OGR Geometry Type
self.__class__ = GEO_CLASSES[self.geom_type.num] self.__class__ = GEO_CLASSES[self.geom_type.num]
@classmethod
def from_bbox(cls, bbox):
"Constructs a Polygon from a bounding box (4-tuple)."
x0, y0, x1, y1 = bbox
return OGRGeometry( 'POLYGON((%s %s, %s %s, %s %s, %s %s, %s %s))' % (
x0, y0, x0, y1, x1, y1, x1, y0, x0, y0) )
def __del__(self): def __del__(self):
"Deletes this Geometry." "Deletes this Geometry."
if self._ptr: destroy_geom(self._ptr) if self._ptr: capi.destroy_geom(self._ptr)
### Geometry set-like operations ### ### Geometry set-like operations ###
# g = g1 | g2 # g = g1 | g2
@ -170,22 +177,22 @@ class OGRGeometry(object):
@property @property
def dimension(self): def dimension(self):
"Returns 0 for points, 1 for lines, and 2 for surfaces." "Returns 0 for points, 1 for lines, and 2 for surfaces."
return get_dims(self._ptr) return capi.get_dims(self.ptr)
@property @property
def coord_dim(self): def coord_dim(self):
"Returns the coordinate dimension of the Geometry." "Returns the coordinate dimension of the Geometry."
return get_coord_dims(self._ptr) return capi.get_coord_dims(self.ptr)
@property @property
def geom_count(self): def geom_count(self):
"The number of elements in this Geometry." "The number of elements in this Geometry."
return get_geom_count(self._ptr) return capi.get_geom_count(self.ptr)
@property @property
def point_count(self): def point_count(self):
"Returns the number of Points in this Geometry." "Returns the number of Points in this Geometry."
return get_point_count(self._ptr) return capi.get_point_count(self.ptr)
@property @property
def num_points(self): def num_points(self):
@ -201,28 +208,28 @@ class OGRGeometry(object):
def geom_type(self): def geom_type(self):
"Returns the Type for this Geometry." "Returns the Type for this Geometry."
try: try:
return OGRGeomType(get_geom_type(self._ptr)) return OGRGeomType(capi.get_geom_type(self.ptr))
except OGRException: except OGRException:
# VRT datasources return an invalid geometry type # VRT datasources return an invalid geometry type
# number, but a valid name -- we'll try that instead. # number, but a valid name -- we'll try that instead.
# See: http://trac.osgeo.org/gdal/ticket/2491 # See: http://trac.osgeo.org/gdal/ticket/2491
return OGRGeomType(get_geom_name(self._ptr)) return OGRGeomType(capi.get_geom_name(self.ptr))
@property @property
def geom_name(self): def geom_name(self):
"Returns the Name of this Geometry." "Returns the Name of this Geometry."
return get_geom_name(self._ptr) return capi.get_geom_name(self.ptr)
@property @property
def area(self): def area(self):
"Returns the area for a LinearRing, Polygon, or MultiPolygon; 0 otherwise." "Returns the area for a LinearRing, Polygon, or MultiPolygon; 0 otherwise."
return get_area(self._ptr) return capi.get_area(self.ptr)
@property @property
def envelope(self): def envelope(self):
"Returns the envelope for this Geometry." "Returns the envelope for this Geometry."
# TODO: Fix Envelope() for Point geometries. # TODO: Fix Envelope() for Point geometries.
return Envelope(get_envelope(self._ptr, byref(OGREnvelope()))) return Envelope(capi.get_envelope(self.ptr, byref(OGREnvelope())))
@property @property
def extent(self): def extent(self):
@ -232,39 +239,40 @@ class OGRGeometry(object):
#### SpatialReference-related Properties #### #### SpatialReference-related Properties ####
# The SRS property # The SRS property
def get_srs(self): def _get_srs(self):
"Returns the Spatial Reference for this Geometry." "Returns the Spatial Reference for this Geometry."
try: try:
srs_ptr = get_geom_srs(self._ptr) srs_ptr = capi.get_geom_srs(self.ptr)
return SpatialReference(clone_srs(srs_ptr)) return SpatialReference(srs_api.clone_srs(srs_ptr))
except SRSException: except SRSException:
return None return None
def set_srs(self, srs): def _set_srs(self, srs):
"Sets the SpatialReference for this geometry." "Sets the SpatialReference for this geometry."
if isinstance(srs, SpatialReference): if isinstance(srs, SpatialReference):
srs_ptr = clone_srs(srs._ptr) srs_ptr = srs_api.clone_srs(srs.ptr)
elif isinstance(srs, (int, long, basestring)): elif isinstance(srs, (int, long, basestring)):
sr = SpatialReference(srs) sr = SpatialReference(srs)
srs_ptr = clone_srs(sr._ptr) srs_ptr = srs_api.clone_srs(sr.ptr)
else: else:
raise TypeError('Cannot assign spatial reference with object of type: %s' % type(srs)) raise TypeError('Cannot assign spatial reference with object of type: %s' % type(srs))
assign_srs(self._ptr, srs_ptr) capi.assign_srs(self.ptr, srs_ptr)
srs = property(get_srs, set_srs) srs = property(_get_srs, _set_srs)
# The SRID property # The SRID property
def get_srid(self): def _get_srid(self):
if self.srs: return self.srs.srid srs = self.srs
else: return None if srs: return srs.srid
return None
def set_srid(self, srid): def _set_srid(self, srid):
if isinstance(srid, (int, long)): if isinstance(srid, (int, long)):
self.srs = srid self.srs = srid
else: else:
raise TypeError('SRID must be set with an integer.') raise TypeError('SRID must be set with an integer.')
srid = property(get_srid, set_srid) srid = property(_get_srid, _set_srid)
#### Output Methods #### #### Output Methods ####
@property @property
@ -276,7 +284,7 @@ class OGRGeometry(object):
@property @property
def gml(self): def gml(self):
"Returns the GML representation of the Geometry." "Returns the GML representation of the Geometry."
return to_gml(self._ptr) return capi.to_gml(self.ptr)
@property @property
def hex(self): def hex(self):
@ -286,16 +294,28 @@ class OGRGeometry(object):
@property @property
def json(self): def json(self):
"""
Returns the GeoJSON representation of this Geometry (requires
GDAL 1.5+).
"""
if GEOJSON: if GEOJSON:
return to_json(self._ptr) return capi.to_json(self.ptr)
else: else:
raise NotImplementedError('GeoJSON output only supported on GDAL 1.5+.') raise NotImplementedError('GeoJSON output only supported on GDAL 1.5+.')
geojson = json geojson = json
@property
def kml(self):
"Returns the KML representation of the Geometry."
if GEOJSON:
return capi.to_kml(self.ptr, None)
else:
raise NotImplementedError('KML output only supported on GDAL 1.5+.')
@property @property
def wkb_size(self): def wkb_size(self):
"Returns the size of the WKB buffer." "Returns the size of the WKB buffer."
return get_wkbsize(self._ptr) return capi.get_wkbsize(self.ptr)
@property @property
def wkb(self): def wkb(self):
@ -307,19 +327,19 @@ class OGRGeometry(object):
sz = self.wkb_size sz = self.wkb_size
# Creating the unsigned character buffer, and passing it in by reference. # Creating the unsigned character buffer, and passing it in by reference.
buf = (c_ubyte * sz)() buf = (c_ubyte * sz)()
wkb = to_wkb(self._ptr, byteorder, byref(buf)) wkb = capi.to_wkb(self.ptr, byteorder, byref(buf))
# Returning a buffer of the string at the pointer. # Returning a buffer of the string at the pointer.
return buffer(string_at(buf, sz)) return buffer(string_at(buf, sz))
@property @property
def wkt(self): def wkt(self):
"Returns the WKT representation of the Geometry." "Returns the WKT representation of the Geometry."
return to_wkt(self._ptr, byref(c_char_p())) return capi.to_wkt(self.ptr, byref(c_char_p()))
#### Geometry Methods #### #### Geometry Methods ####
def clone(self): def clone(self):
"Clones this OGR Geometry." "Clones this OGR Geometry."
return OGRGeometry(clone_geom(self._ptr), self.srs) return OGRGeometry(capi.clone_geom(self.ptr), self.srs)
def close_rings(self): def close_rings(self):
""" """
@ -328,7 +348,7 @@ class OGRGeometry(object):
end. end.
""" """
# Closing the open rings. # Closing the open rings.
geom_close_rings(self._ptr) capi.geom_close_rings(self.ptr)
def transform(self, coord_trans, clone=False): def transform(self, coord_trans, clone=False):
""" """
@ -344,12 +364,12 @@ class OGRGeometry(object):
klone.transform(coord_trans) klone.transform(coord_trans)
return klone return klone
if isinstance(coord_trans, CoordTransform): if isinstance(coord_trans, CoordTransform):
geom_transform(self._ptr, coord_trans._ptr) capi.geom_transform(self.ptr, coord_trans.ptr)
elif isinstance(coord_trans, SpatialReference): elif isinstance(coord_trans, SpatialReference):
geom_transform_to(self._ptr, coord_trans._ptr) capi.geom_transform_to(self.ptr, coord_trans.ptr)
elif isinstance(coord_trans, (int, long, basestring)): elif isinstance(coord_trans, (int, long, basestring)):
sr = SpatialReference(coord_trans) sr = SpatialReference(coord_trans)
geom_transform_to(self._ptr, sr._ptr) capi.geom_transform_to(self.ptr, sr.ptr)
else: else:
raise TypeError('Transform only accepts CoordTransform, SpatialReference, string, and integer objects.') raise TypeError('Transform only accepts CoordTransform, SpatialReference, string, and integer objects.')
@ -366,52 +386,52 @@ class OGRGeometry(object):
# Returning the output of the given function with the other geometry's # Returning the output of the given function with the other geometry's
# pointer. # pointer.
return func(self._ptr, other._ptr) return func(self.ptr, other.ptr)
def intersects(self, other): def intersects(self, other):
"Returns True if this geometry intersects with the other." "Returns True if this geometry intersects with the other."
return self._topology(ogr_intersects, other) return self._topology(capi.ogr_intersects, other)
def equals(self, other): def equals(self, other):
"Returns True if this geometry is equivalent to the other." "Returns True if this geometry is equivalent to the other."
return self._topology(ogr_equals, other) return self._topology(capi.ogr_equals, other)
def disjoint(self, other): def disjoint(self, other):
"Returns True if this geometry and the other are spatially disjoint." "Returns True if this geometry and the other are spatially disjoint."
return self._topology(ogr_disjoint, other) return self._topology(capi.ogr_disjoint, other)
def touches(self, other): def touches(self, other):
"Returns True if this geometry touches the other." "Returns True if this geometry touches the other."
return self._topology(ogr_touches, other) return self._topology(capi.ogr_touches, other)
def crosses(self, other): def crosses(self, other):
"Returns True if this geometry crosses the other." "Returns True if this geometry crosses the other."
return self._topology(ogr_crosses, other) return self._topology(capi.ogr_crosses, other)
def within(self, other): def within(self, other):
"Returns True if this geometry is within the other." "Returns True if this geometry is within the other."
return self._topology(ogr_within, other) return self._topology(capi.ogr_within, other)
def contains(self, other): def contains(self, other):
"Returns True if this geometry contains the other." "Returns True if this geometry contains the other."
return self._topology(ogr_contains, other) return self._topology(capi.ogr_contains, other)
def overlaps(self, other): def overlaps(self, other):
"Returns True if this geometry overlaps the other." "Returns True if this geometry overlaps the other."
return self._topology(ogr_overlaps, other) return self._topology(capi.ogr_overlaps, other)
#### Geometry-generation Methods #### #### Geometry-generation Methods ####
def _geomgen(self, gen_func, other=None): def _geomgen(self, gen_func, other=None):
"A helper routine for the OGR routines that generate geometries." "A helper routine for the OGR routines that generate geometries."
if isinstance(other, OGRGeometry): if isinstance(other, OGRGeometry):
return OGRGeometry(gen_func(self._ptr, other._ptr), self.srs) return OGRGeometry(gen_func(self.ptr, other.ptr), self.srs)
else: else:
return OGRGeometry(gen_func(self._ptr), self.srs) return OGRGeometry(gen_func(self.ptr), self.srs)
@property @property
def boundary(self): def boundary(self):
"Returns the boundary of this geometry." "Returns the boundary of this geometry."
return self._geomgen(get_boundary) return self._geomgen(capi.get_boundary)
@property @property
def convex_hull(self): def convex_hull(self):
@ -419,35 +439,35 @@ class OGRGeometry(object):
Returns the smallest convex Polygon that contains all the points in Returns the smallest convex Polygon that contains all the points in
this Geometry. this Geometry.
""" """
return self._geomgen(geom_convex_hull) return self._geomgen(capi.geom_convex_hull)
def difference(self, other): def difference(self, other):
""" """
Returns a new geometry consisting of the region which is the difference Returns a new geometry consisting of the region which is the difference
of this geometry and the other. of this geometry and the other.
""" """
return self._geomgen(geom_diff, other) return self._geomgen(capi.geom_diff, other)
def intersection(self, other): def intersection(self, other):
""" """
Returns a new geometry consisting of the region of intersection of this Returns a new geometry consisting of the region of intersection of this
geometry and the other. geometry and the other.
""" """
return self._geomgen(geom_intersection, other) return self._geomgen(capi.geom_intersection, other)
def sym_difference(self, other): def sym_difference(self, other):
""" """
Returns a new geometry which is the symmetric difference of this Returns a new geometry which is the symmetric difference of this
geometry and the other. geometry and the other.
""" """
return self._geomgen(geom_sym_diff, other) return self._geomgen(capi.geom_sym_diff, other)
def union(self, other): def union(self, other):
""" """
Returns a new geometry consisting of the region which is the union of Returns a new geometry consisting of the region which is the union of
this geometry and the other. this geometry and the other.
""" """
return self._geomgen(geom_union, other) return self._geomgen(capi.geom_union, other)
# The subclasses for OGR Geometry. # The subclasses for OGR Geometry.
class Point(OGRGeometry): class Point(OGRGeometry):
@ -455,18 +475,18 @@ class Point(OGRGeometry):
@property @property
def x(self): def x(self):
"Returns the X coordinate for this Point." "Returns the X coordinate for this Point."
return getx(self._ptr, 0) return capi.getx(self.ptr, 0)
@property @property
def y(self): def y(self):
"Returns the Y coordinate for this Point." "Returns the Y coordinate for this Point."
return gety(self._ptr, 0) return capi.gety(self.ptr, 0)
@property @property
def z(self): def z(self):
"Returns the Z coordinate for this Point." "Returns the Z coordinate for this Point."
if self.coord_dim == 3: if self.coord_dim == 3:
return getz(self._ptr, 0) return capi.getz(self.ptr, 0)
@property @property
def tuple(self): def tuple(self):
@ -483,7 +503,7 @@ class LineString(OGRGeometry):
"Returns the Point at the given index." "Returns the Point at the given index."
if index >= 0 and index < self.point_count: if index >= 0 and index < self.point_count:
x, y, z = c_double(), c_double(), c_double() x, y, z = c_double(), c_double(), c_double()
get_point(self._ptr, index, byref(x), byref(y), byref(z)) capi.get_point(self.ptr, index, byref(x), byref(y), byref(z))
dim = self.coord_dim dim = self.coord_dim
if dim == 1: if dim == 1:
return (x.value,) return (x.value,)
@ -514,23 +534,23 @@ class LineString(OGRGeometry):
Internal routine that returns a sequence (list) corresponding with Internal routine that returns a sequence (list) corresponding with
the given function. the given function.
""" """
return [func(self._ptr, i) for i in xrange(len(self))] return [func(self.ptr, i) for i in xrange(len(self))]
@property @property
def x(self): def x(self):
"Returns the X coordinates in a list." "Returns the X coordinates in a list."
return self._listarr(getx) return self._listarr(capi.getx)
@property @property
def y(self): def y(self):
"Returns the Y coordinates in a list." "Returns the Y coordinates in a list."
return self._listarr(gety) return self._listarr(capi.gety)
@property @property
def z(self): def z(self):
"Returns the Z coordinates in a list." "Returns the Z coordinates in a list."
if self.coord_dim == 3: if self.coord_dim == 3:
return self._listarr(getz) return self._listarr(capi.getz)
# LinearRings are used in Polygons. # LinearRings are used in Polygons.
class LinearRing(LineString): pass class LinearRing(LineString): pass
@ -551,7 +571,7 @@ class Polygon(OGRGeometry):
if index < 0 or index >= self.geom_count: if index < 0 or index >= self.geom_count:
raise OGRIndexError('index out of range: %s' % index) raise OGRIndexError('index out of range: %s' % index)
else: else:
return OGRGeometry(clone_geom(get_geom_ref(self._ptr, index)), self.srs) return OGRGeometry(capi.clone_geom(capi.get_geom_ref(self.ptr, index)), self.srs)
# Polygon Properties # Polygon Properties
@property @property
@ -577,7 +597,7 @@ class Polygon(OGRGeometry):
"Returns the centroid (a Point) of this Polygon." "Returns the centroid (a Point) of this Polygon."
# The centroid is a Point, create a geometry for this. # The centroid is a Point, create a geometry for this.
p = OGRGeometry(OGRGeomType('Point')) p = OGRGeometry(OGRGeomType('Point'))
get_centroid(self._ptr, p._ptr) capi.get_centroid(self.ptr, p.ptr)
return p return p
# Geometry Collection base class. # Geometry Collection base class.
@ -589,7 +609,7 @@ class GeometryCollection(OGRGeometry):
if index < 0 or index >= self.geom_count: if index < 0 or index >= self.geom_count:
raise OGRIndexError('index out of range: %s' % index) raise OGRIndexError('index out of range: %s' % index)
else: else:
return OGRGeometry(clone_geom(get_geom_ref(self._ptr, index)), self.srs) return OGRGeometry(capi.clone_geom(capi.get_geom_ref(self.ptr, index)), self.srs)
def __iter__(self): def __iter__(self):
"Iterates over each Geometry." "Iterates over each Geometry."
@ -604,12 +624,12 @@ class GeometryCollection(OGRGeometry):
"Add the geometry to this Geometry Collection." "Add the geometry to this Geometry Collection."
if isinstance(geom, OGRGeometry): if isinstance(geom, OGRGeometry):
if isinstance(geom, self.__class__): if isinstance(geom, self.__class__):
for g in geom: add_geom(self._ptr, g._ptr) for g in geom: capi.add_geom(self.ptr, g.ptr)
else: else:
add_geom(self._ptr, geom._ptr) capi.add_geom(self.ptr, geom.ptr)
elif isinstance(geom, basestring): elif isinstance(geom, basestring):
tmp = OGRGeometry(geom) tmp = OGRGeometry(geom)
add_geom(self._ptr, tmp._ptr) capi.add_geom(self.ptr, tmp.ptr)
else: else:
raise OGRException('Must add an OGRGeometry.') raise OGRException('Must add an OGRGeometry.')

View File

@ -24,7 +24,9 @@ class OGRGeomType(object):
if isinstance(type_input, OGRGeomType): if isinstance(type_input, OGRGeomType):
num = type_input.num num = type_input.num
elif isinstance(type_input, basestring): elif isinstance(type_input, basestring):
num = self._str_types.get(type_input.lower(), None) type_input = type_input.lower()
if type_input == 'geometry': type_input='unknown'
num = self._str_types.get(type_input, None)
if num is None: if num is None:
raise OGRException('Invalid OGR String Type "%s"' % type_input) raise OGRException('Invalid OGR String Type "%s"' % type_input)
elif isinstance(type_input, int): elif isinstance(type_input, int):
@ -67,7 +69,8 @@ class OGRGeomType(object):
def django(self): def django(self):
"Returns the Django GeometryField for this OGR Type." "Returns the Django GeometryField for this OGR Type."
s = self.name s = self.name
if s in ('Unknown', 'LinearRing', 'None'): if s in ('LinearRing', 'None'):
return None return None
else: elif s == 'Unknown':
return s + 'Field' s = 'Geometry'
return s + 'Field'

View File

@ -2,26 +2,22 @@
from ctypes import byref from ctypes import byref
# Other GDAL imports. # Other GDAL imports.
from django.contrib.gis.gdal.base import GDALBase
from django.contrib.gis.gdal.envelope import Envelope, OGREnvelope from django.contrib.gis.gdal.envelope import Envelope, OGREnvelope
from django.contrib.gis.gdal.error import OGRException, OGRIndexError, SRSException from django.contrib.gis.gdal.error import OGRException, OGRIndexError, SRSException
from django.contrib.gis.gdal.feature import Feature from django.contrib.gis.gdal.feature import Feature
from django.contrib.gis.gdal.field import FIELD_CLASSES from django.contrib.gis.gdal.field import OGRFieldTypes
from django.contrib.gis.gdal.geometries import OGRGeomType from django.contrib.gis.gdal.geometries import OGRGeomType
from django.contrib.gis.gdal.srs import SpatialReference from django.contrib.gis.gdal.srs import SpatialReference
# GDAL ctypes function prototypes. # GDAL ctypes function prototypes.
from django.contrib.gis.gdal.prototypes.ds import \ from django.contrib.gis.gdal.prototypes import ds as capi, srs as srs_api
get_extent, get_fd_geom_type, get_fd_name, get_feature, get_feature_count, \
get_field_count, get_field_defn, get_field_name, get_field_precision, \
get_field_width, get_field_type, get_layer_defn, get_layer_srs, \
get_next_feature, reset_reading, test_capability
from django.contrib.gis.gdal.prototypes.srs import clone_srs
# For more information, see the OGR C API source code: # For more information, see the OGR C API source code:
# http://www.gdal.org/ogr/ogr__api_8h.html # http://www.gdal.org/ogr/ogr__api_8h.html
# #
# The OGR_L_* routines are relevant here. # The OGR_L_* routines are relevant here.
class Layer(object): class Layer(GDALBase):
"A class that wraps an OGR Layer, needs to be instantiated from a DataSource object." "A class that wraps an OGR Layer, needs to be instantiated from a DataSource object."
#### Python 'magic' routines #### #### Python 'magic' routines ####
@ -32,12 +28,11 @@ class Layer(object):
reference to it is kept with this Layer. This prevents garbage reference to it is kept with this Layer. This prevents garbage
collection of the `DataSource` while this Layer is still active. collection of the `DataSource` while this Layer is still active.
""" """
self._ptr = None # Initially NULL
if not layer_ptr: if not layer_ptr:
raise OGRException('Cannot create Layer, invalid pointer given') raise OGRException('Cannot create Layer, invalid pointer given')
self._ptr = layer_ptr self.ptr = layer_ptr
self._ds = ds self._ds = ds
self._ldefn = get_layer_defn(self._ptr) self._ldefn = capi.get_layer_defn(self._ptr)
# Does the Layer support random reading? # Does the Layer support random reading?
self._random_read = self.test_capability('RandomRead') self._random_read = self.test_capability('RandomRead')
@ -59,9 +54,9 @@ class Layer(object):
def __iter__(self): def __iter__(self):
"Iterates over each Feature in the Layer." "Iterates over each Feature in the Layer."
# ResetReading() must be called before iteration is to begin. # ResetReading() must be called before iteration is to begin.
reset_reading(self._ptr) capi.reset_reading(self._ptr)
for i in xrange(self.num_feat): for i in xrange(self.num_feat):
yield Feature(get_next_feature(self._ptr), self._ldefn) yield Feature(capi.get_next_feature(self._ptr), self._ldefn)
def __len__(self): def __len__(self):
"The length is the number of features." "The length is the number of features."
@ -81,7 +76,7 @@ class Layer(object):
if self._random_read: if self._random_read:
# If the Layer supports random reading, return. # If the Layer supports random reading, return.
try: try:
return Feature(get_feature(self._ptr, feat_id), self._ldefn) return Feature(capi.get_feature(self.ptr, feat_id), self._ldefn)
except OGRException: except OGRException:
pass pass
else: else:
@ -97,35 +92,35 @@ class Layer(object):
def extent(self): def extent(self):
"Returns the extent (an Envelope) of this layer." "Returns the extent (an Envelope) of this layer."
env = OGREnvelope() env = OGREnvelope()
get_extent(self._ptr, byref(env), 1) capi.get_extent(self.ptr, byref(env), 1)
return Envelope(env) return Envelope(env)
@property @property
def name(self): def name(self):
"Returns the name of this layer in the Data Source." "Returns the name of this layer in the Data Source."
return get_fd_name(self._ldefn) return capi.get_fd_name(self._ldefn)
@property @property
def num_feat(self, force=1): def num_feat(self, force=1):
"Returns the number of features in the Layer." "Returns the number of features in the Layer."
return get_feature_count(self._ptr, force) return capi.get_feature_count(self.ptr, force)
@property @property
def num_fields(self): def num_fields(self):
"Returns the number of fields in the Layer." "Returns the number of fields in the Layer."
return get_field_count(self._ldefn) return capi.get_field_count(self._ldefn)
@property @property
def geom_type(self): def geom_type(self):
"Returns the geometry type (OGRGeomType) of the Layer." "Returns the geometry type (OGRGeomType) of the Layer."
return OGRGeomType(get_fd_geom_type(self._ldefn)) return OGRGeomType(capi.get_fd_geom_type(self._ldefn))
@property @property
def srs(self): def srs(self):
"Returns the Spatial Reference used in this Layer." "Returns the Spatial Reference used in this Layer."
try: try:
ptr = get_layer_srs(self._ptr) ptr = capi.get_layer_srs(self.ptr)
return SpatialReference(clone_srs(ptr)) return SpatialReference(srs_api.clone_srs(ptr))
except SRSException: except SRSException:
return None return None
@ -135,7 +130,7 @@ class Layer(object):
Returns a list of string names corresponding to each of the Fields Returns a list of string names corresponding to each of the Fields
available in this Layer. available in this Layer.
""" """
return [get_field_name(get_field_defn(self._ldefn, i)) return [capi.get_field_name(capi.get_field_defn(self._ldefn, i))
for i in xrange(self.num_fields) ] for i in xrange(self.num_fields) ]
@property @property
@ -146,19 +141,19 @@ class Layer(object):
an OGR layer that had an integer, a floating-point, and string an OGR layer that had an integer, a floating-point, and string
fields. fields.
""" """
return [FIELD_CLASSES[get_field_type(get_field_defn(self._ldefn, i))] return [OGRFieldTypes[capi.get_field_type(capi.get_field_defn(self._ldefn, i))]
for i in xrange(self.num_fields)] for i in xrange(self.num_fields)]
@property @property
def field_widths(self): def field_widths(self):
"Returns a list of the maximum field widths for the features." "Returns a list of the maximum field widths for the features."
return [get_field_width(get_field_defn(self._ldefn, i)) return [capi.get_field_width(capi.get_field_defn(self._ldefn, i))
for i in xrange(self.num_fields)] for i in xrange(self.num_fields)]
@property @property
def field_precisions(self): def field_precisions(self):
"Returns the field precisions for the features." "Returns the field precisions for the features."
return [get_field_precision(get_field_defn(self._ldefn, i)) return [capi.get_field_precision(capi.get_field_defn(self._ldefn, i))
for i in xrange(self.num_fields)] for i in xrange(self.num_fields)]
#### Layer Methods #### #### Layer Methods ####
@ -190,4 +185,4 @@ class Layer(object):
'FastFeatureCount', 'FastGetExtent', 'CreateField', 'Transactions', 'FastFeatureCount', 'FastGetExtent', 'CreateField', 'Transactions',
'DeleteFeature', and 'FastSetNextByIndex'. 'DeleteFeature', and 'FastSetNextByIndex'.
""" """
return bool(test_capability(self._ptr, capability)) return bool(capi.test_capability(self.ptr, capability))

View File

@ -38,9 +38,11 @@ def topology_func(f):
if GEOJSON: if GEOJSON:
from_json = geom_output(lgdal.OGR_G_CreateGeometryFromJson, [c_char_p]) from_json = geom_output(lgdal.OGR_G_CreateGeometryFromJson, [c_char_p])
to_json = string_output(lgdal.OGR_G_ExportToJson, [c_void_p], str_result=True) to_json = string_output(lgdal.OGR_G_ExportToJson, [c_void_p], str_result=True)
to_kml = string_output(lgdal.OGR_G_ExportToKML, [c_void_p, c_char_p], str_result=True)
else: else:
from_json = False from_json = False
to_json = False to_json = False
to_kml = False
# GetX, GetY, GetZ all return doubles. # GetX, GetY, GetZ all return doubles.
getx = pnt_func(lgdal.OGR_G_GetX) getx = pnt_func(lgdal.OGR_G_GetX)

View File

@ -36,6 +36,7 @@ from_wkt = void_output(lgdal.OSRImportFromWkt, [c_void_p, POINTER(c_char_p)])
from_proj = void_output(lgdal.OSRImportFromProj4, [c_void_p, c_char_p]) from_proj = void_output(lgdal.OSRImportFromProj4, [c_void_p, c_char_p])
from_epsg = void_output(std_call('OSRImportFromEPSG'), [c_void_p, c_int]) from_epsg = void_output(std_call('OSRImportFromEPSG'), [c_void_p, c_int])
from_xml = void_output(lgdal.OSRImportFromXML, [c_void_p, c_char_p]) from_xml = void_output(lgdal.OSRImportFromXML, [c_void_p, c_char_p])
from_user_input = void_output(std_call('OSRSetFromUserInput'), [c_void_p, c_char_p])
# Morphing to/from ESRI WKT. # Morphing to/from ESRI WKT.
morph_to_esri = void_output(lgdal.OSRMorphToESRI, [c_void_p]) morph_to_esri = void_output(lgdal.OSRMorphToESRI, [c_void_p])

View File

@ -27,89 +27,74 @@
NAD83 / Texas South Central NAD83 / Texas South Central
""" """
import re import re
from types import UnicodeType, TupleType
from ctypes import byref, c_char_p, c_int, c_void_p from ctypes import byref, c_char_p, c_int, c_void_p
# Getting the error checking routine and exceptions # Getting the error checking routine and exceptions
from django.contrib.gis.gdal.base import GDALBase
from django.contrib.gis.gdal.error import OGRException, SRSException from django.contrib.gis.gdal.error import OGRException, SRSException
from django.contrib.gis.gdal.prototypes.srs import * from django.contrib.gis.gdal.prototypes import srs as capi
#### Spatial Reference class. #### #### Spatial Reference class. ####
class SpatialReference(object): class SpatialReference(GDALBase):
""" """
A wrapper for the OGRSpatialReference object. According to the GDAL website, A wrapper for the OGRSpatialReference object. According to the GDAL website,
the SpatialReference object "provide[s] services to represent coordinate the SpatialReference object "provide[s] services to represent coordinate
systems (projections and datums) and to transform between them." systems (projections and datums) and to transform between them."
""" """
# Well-Known Geographical Coordinate System Name
_well_known = {'WGS84':4326, 'WGS72':4322, 'NAD27':4267, 'NAD83':4269}
_epsg_regex = re.compile('^(EPSG:)?(?P<epsg>\d+)$', re.I)
_proj_regex = re.compile(r'^\+proj')
#### Python 'magic' routines #### #### Python 'magic' routines ####
def __init__(self, srs_input='', srs_type='wkt'): def __init__(self, srs_input=''):
""" """
Creates a GDAL OSR Spatial Reference object from the given input. Creates a GDAL OSR Spatial Reference object from the given input.
The input may be string of OGC Well Known Text (WKT), an integer The input may be string of OGC Well Known Text (WKT), an integer
EPSG code, a PROJ.4 string, and/or a projection "well known" shorthand EPSG code, a PROJ.4 string, and/or a projection "well known" shorthand
string (one of 'WGS84', 'WGS72', 'NAD27', 'NAD83'). string (one of 'WGS84', 'WGS72', 'NAD27', 'NAD83').
""" """
# Intializing pointer and string buffer.
self._ptr = None
buf = c_char_p('') buf = c_char_p('')
srs_type = 'user'
if isinstance(srs_input, basestring): if isinstance(srs_input, basestring):
# Encoding to ASCII if unicode passed in. # Encoding to ASCII if unicode passed in.
if isinstance(srs_input, UnicodeType): if isinstance(srs_input, unicode):
srs_input = srs_input.encode('ascii') srs_input = srs_input.encode('ascii')
try:
epsg_m = self._epsg_regex.match(srs_input) # If SRID is a string, e.g., '4326', then make acceptable
proj_m = self._proj_regex.match(srs_input) # as user input.
if epsg_m: srid = int(srs_input)
# Is this an EPSG well known name? srs_input = 'EPSG:%d' % srid
srs_type = 'epsg' except ValueError:
srs_input = int(epsg_m.group('epsg'))
elif proj_m:
# Is the string a PROJ.4 string?
srs_type = 'proj'
elif srs_input in self._well_known:
# Is this a short-hand well known name?
srs_type = 'epsg'
srs_input = self._well_known[srs_input]
elif srs_type == 'proj':
pass pass
else: elif isinstance(srs_input, (int, long)):
# Setting the buffer with WKT, PROJ.4 string, etc.
buf = c_char_p(srs_input)
elif isinstance(srs_input, int):
# EPSG integer code was input. # EPSG integer code was input.
if srs_type != 'epsg': srs_type = 'epsg' srs_type = 'epsg'
elif isinstance(srs_input, c_void_p): elif isinstance(srs_input, self.ptr_type):
srs = srs_input
srs_type = 'ogr' srs_type = 'ogr'
else: else:
raise TypeError('Invalid SRS type "%s"' % srs_type) raise TypeError('Invalid SRS type "%s"' % srs_type)
if srs_type == 'ogr': if srs_type == 'ogr':
# SRS input is OGR pointer # Input is already an SRS pointer.
srs = srs_input srs = srs_input
else: else:
# Creating a new pointer, using the string buffer. # Creating a new SRS pointer, using the string buffer.
srs = new_srs(buf) srs = capi.new_srs(buf)
# If the pointer is NULL, throw an exception. # If the pointer is NULL, throw an exception.
if not srs: if not srs:
raise SRSException('Could not create spatial reference from: %s' % srs_input) raise SRSException('Could not create spatial reference from: %s' % srs_input)
else: else:
self._ptr = srs self.ptr = srs
# Post-processing if in PROJ.4 or EPSG formats. # Importing from either the user input string or an integer SRID.
if srs_type == 'proj': self.import_proj(srs_input) if srs_type == 'user':
elif srs_type == 'epsg': self.import_epsg(srs_input) self.import_user_input(srs_input)
elif srs_type == 'epsg':
self.import_epsg(srs_input)
def __del__(self): def __del__(self):
"Destroys this spatial reference." "Destroys this spatial reference."
if self._ptr: release_srs(self._ptr) if self._ptr: capi.release_srs(self._ptr)
def __getitem__(self, target): def __getitem__(self, target):
""" """
@ -134,7 +119,7 @@ class SpatialReference(object):
>>> print srs['UNIT|AUTHORITY', 1] # The authority value for the untis >>> print srs['UNIT|AUTHORITY', 1] # The authority value for the untis
9122 9122
""" """
if isinstance(target, TupleType): if isinstance(target, tuple):
return self.attr_value(*target) return self.attr_value(*target)
else: else:
return self.attr_value(target) return self.attr_value(target)
@ -149,40 +134,40 @@ class SpatialReference(object):
The attribute value for the given target node (e.g. 'PROJCS'). The index The attribute value for the given target node (e.g. 'PROJCS'). The index
keyword specifies an index of the child node to return. keyword specifies an index of the child node to return.
""" """
if not isinstance(target, str) or not isinstance(index, int): if not isinstance(target, basestring) or not isinstance(index, int):
raise TypeError raise TypeError
return get_attr_value(self._ptr, target, index) return capi.get_attr_value(self.ptr, target, index)
def auth_name(self, target): def auth_name(self, target):
"Returns the authority name for the given string target node." "Returns the authority name for the given string target node."
return get_auth_name(self._ptr, target) return capi.get_auth_name(self.ptr, target)
def auth_code(self, target): def auth_code(self, target):
"Returns the authority code for the given string target node." "Returns the authority code for the given string target node."
return get_auth_code(self._ptr, target) return capi.get_auth_code(self.ptr, target)
def clone(self): def clone(self):
"Returns a clone of this SpatialReference object." "Returns a clone of this SpatialReference object."
return SpatialReference(clone_srs(self._ptr)) return SpatialReference(capi.clone_srs(self.ptr))
def from_esri(self): def from_esri(self):
"Morphs this SpatialReference from ESRI's format to EPSG." "Morphs this SpatialReference from ESRI's format to EPSG."
morph_from_esri(self._ptr) capi.morph_from_esri(self.ptr)
def identify_epsg(self): def identify_epsg(self):
""" """
This method inspects the WKT of this SpatialReference, and will This method inspects the WKT of this SpatialReference, and will
add EPSG authority nodes where an EPSG identifier is applicable. add EPSG authority nodes where an EPSG identifier is applicable.
""" """
identify_epsg(self._ptr) capi.identify_epsg(self.ptr)
def to_esri(self): def to_esri(self):
"Morphs this SpatialReference to ESRI's format." "Morphs this SpatialReference to ESRI's format."
morph_to_esri(self._ptr) capi.morph_to_esri(self.ptr)
def validate(self): def validate(self):
"Checks to see if the given spatial reference is valid." "Checks to see if the given spatial reference is valid."
srs_validate(self._ptr) capi.srs_validate(self.ptr)
#### Name & SRID properties #### #### Name & SRID properties ####
@property @property
@ -205,25 +190,25 @@ class SpatialReference(object):
@property @property
def linear_name(self): def linear_name(self):
"Returns the name of the linear units." "Returns the name of the linear units."
units, name = linear_units(self._ptr, byref(c_char_p())) units, name = capi.linear_units(self.ptr, byref(c_char_p()))
return name return name
@property @property
def linear_units(self): def linear_units(self):
"Returns the value of the linear units." "Returns the value of the linear units."
units, name = linear_units(self._ptr, byref(c_char_p())) units, name = capi.linear_units(self.ptr, byref(c_char_p()))
return units return units
@property @property
def angular_name(self): def angular_name(self):
"Returns the name of the angular units." "Returns the name of the angular units."
units, name = angular_units(self._ptr, byref(c_char_p())) units, name = capi.angular_units(self.ptr, byref(c_char_p()))
return name return name
@property @property
def angular_units(self): def angular_units(self):
"Returns the value of the angular units." "Returns the value of the angular units."
units, name = angular_units(self._ptr, byref(c_char_p())) units, name = capi.angular_units(self.ptr, byref(c_char_p()))
return units return units
@property @property
@ -234,9 +219,9 @@ class SpatialReference(object):
or angular units. or angular units.
""" """
if self.projected or self.local: if self.projected or self.local:
return linear_units(self._ptr, byref(c_char_p())) return capi.linear_units(self.ptr, byref(c_char_p()))
elif self.geographic: elif self.geographic:
return angular_units(self._ptr, byref(c_char_p())) return capi.angular_units(self.ptr, byref(c_char_p()))
else: else:
return (None, None) return (None, None)
@ -252,17 +237,17 @@ class SpatialReference(object):
@property @property
def semi_major(self): def semi_major(self):
"Returns the Semi Major Axis for this Spatial Reference." "Returns the Semi Major Axis for this Spatial Reference."
return semi_major(self._ptr, byref(c_int())) return capi.semi_major(self.ptr, byref(c_int()))
@property @property
def semi_minor(self): def semi_minor(self):
"Returns the Semi Minor Axis for this Spatial Reference." "Returns the Semi Minor Axis for this Spatial Reference."
return semi_minor(self._ptr, byref(c_int())) return capi.semi_minor(self.ptr, byref(c_int()))
@property @property
def inverse_flattening(self): def inverse_flattening(self):
"Returns the Inverse Flattening for this Spatial Reference." "Returns the Inverse Flattening for this Spatial Reference."
return invflattening(self._ptr, byref(c_int())) return capi.invflattening(self.ptr, byref(c_int()))
#### Boolean Properties #### #### Boolean Properties ####
@property @property
@ -271,12 +256,12 @@ class SpatialReference(object):
Returns True if this SpatialReference is geographic Returns True if this SpatialReference is geographic
(root node is GEOGCS). (root node is GEOGCS).
""" """
return bool(isgeographic(self._ptr)) return bool(capi.isgeographic(self.ptr))
@property @property
def local(self): def local(self):
"Returns True if this SpatialReference is local (root node is LOCAL_CS)." "Returns True if this SpatialReference is local (root node is LOCAL_CS)."
return bool(islocal(self._ptr)) return bool(capi.islocal(self.ptr))
@property @property
def projected(self): def projected(self):
@ -284,40 +269,44 @@ class SpatialReference(object):
Returns True if this SpatialReference is a projected coordinate system Returns True if this SpatialReference is a projected coordinate system
(root node is PROJCS). (root node is PROJCS).
""" """
return bool(isprojected(self._ptr)) return bool(capi.isprojected(self.ptr))
#### Import Routines ##### #### Import Routines #####
def import_wkt(self, wkt): def import_epsg(self, epsg):
"Imports the Spatial Reference from OGC WKT (string)" "Imports the Spatial Reference from the EPSG code (an integer)."
from_wkt(self._ptr, byref(c_char_p(wkt))) capi.from_epsg(self.ptr, epsg)
def import_proj(self, proj): def import_proj(self, proj):
"Imports the Spatial Reference from a PROJ.4 string." "Imports the Spatial Reference from a PROJ.4 string."
from_proj(self._ptr, proj) capi.from_proj(self.ptr, proj)
def import_epsg(self, epsg): def import_user_input(self, user_input):
"Imports the Spatial Reference from the EPSG code (an integer)." "Imports the Spatial Reference from the given user input string."
from_epsg(self._ptr, epsg) capi.from_user_input(self.ptr, user_input)
def import_wkt(self, wkt):
"Imports the Spatial Reference from OGC WKT (string)"
capi.from_wkt(self.ptr, byref(c_char_p(wkt)))
def import_xml(self, xml): def import_xml(self, xml):
"Imports the Spatial Reference from an XML string." "Imports the Spatial Reference from an XML string."
from_xml(self._ptr, xml) capi.from_xml(self.ptr, xml)
#### Export Properties #### #### Export Properties ####
@property @property
def wkt(self): def wkt(self):
"Returns the WKT representation of this Spatial Reference." "Returns the WKT representation of this Spatial Reference."
return to_wkt(self._ptr, byref(c_char_p())) return capi.to_wkt(self.ptr, byref(c_char_p()))
@property @property
def pretty_wkt(self, simplify=0): def pretty_wkt(self, simplify=0):
"Returns the 'pretty' representation of the WKT." "Returns the 'pretty' representation of the WKT."
return to_pretty_wkt(self._ptr, byref(c_char_p()), simplify) return capi.to_pretty_wkt(self.ptr, byref(c_char_p()), simplify)
@property @property
def proj(self): def proj(self):
"Returns the PROJ.4 representation for this Spatial Reference." "Returns the PROJ.4 representation for this Spatial Reference."
return to_proj(self._ptr, byref(c_char_p())) return capi.to_proj(self.ptr, byref(c_char_p()))
@property @property
def proj4(self): def proj4(self):
@ -327,34 +316,22 @@ class SpatialReference(object):
@property @property
def xml(self, dialect=''): def xml(self, dialect=''):
"Returns the XML representation of this Spatial Reference." "Returns the XML representation of this Spatial Reference."
# FIXME: This leaks memory, have to figure out why. return capi.to_xml(self.ptr, byref(c_char_p()), dialect)
return to_xml(self._ptr, byref(c_char_p()), dialect)
def to_esri(self): class CoordTransform(GDALBase):
"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):
"The coordinate system transformation object." "The coordinate system transformation object."
def __init__(self, source, target): def __init__(self, source, target):
"Initializes on a source and target SpatialReference objects." "Initializes on a source and target SpatialReference objects."
self._ptr = None # Initially NULL
if not isinstance(source, SpatialReference) or not isinstance(target, SpatialReference): if not isinstance(source, SpatialReference) or not isinstance(target, SpatialReference):
raise SRSException('source and target must be of type SpatialReference') raise TypeError('source and target must be of type SpatialReference')
self._ptr = new_ct(source._ptr, target._ptr) self.ptr = capi.new_ct(source._ptr, target._ptr)
if not self._ptr:
raise SRSException('could not intialize CoordTransform object')
self._srs1_name = source.name self._srs1_name = source.name
self._srs2_name = target.name self._srs2_name = target.name
def __del__(self): def __del__(self):
"Deletes this Coordinate Transformation object." "Deletes this Coordinate Transformation object."
if self._ptr: destroy_ct(self._ptr) if self._ptr: capi.destroy_ct(self._ptr)
def __str__(self): def __str__(self):
return 'Transform from "%s" to "%s"' % (self._srs1_name, self._srs2_name) return 'Transform from "%s" to "%s"' % (self._srs1_name, self._srs2_name)

View File

@ -5,16 +5,13 @@ of these tests require the use of the database.
from unittest import TestSuite, TextTestRunner from unittest import TestSuite, TextTestRunner
# Importing the GDAL test modules. # Importing the GDAL test modules.
from django.contrib.gis.tests import \ import test_driver, test_ds, test_envelope, test_geom, test_srs
test_gdal_driver, test_gdal_ds, test_gdal_envelope, \
test_gdal_geom, test_gdal_srs
test_suites = [test_gdal_driver.suite(), test_suites = [test_driver.suite(),
test_gdal_ds.suite(), test_ds.suite(),
test_gdal_envelope.suite(), test_envelope.suite(),
test_gdal_geom.suite(), test_geom.suite(),
test_gdal_srs.suite(), test_srs.suite(),
] ]
def suite(): def suite():

View File

@ -1,10 +1,13 @@
import os, os.path, unittest import os, os.path, unittest
from django.contrib.gis.gdal import DataSource, Envelope, OGRException, OGRIndexError from django.contrib.gis.gdal import DataSource, Envelope, OGRException, OGRIndexError
from django.contrib.gis.gdal.field import OFTReal, OFTInteger, OFTString from django.contrib.gis.gdal.field import OFTReal, OFTInteger, OFTString
from django.contrib import gis
# Path for SHP files # Path for SHP files
data_path = os.path.join(os.path.dirname(__file__), 'data') data_path = os.path.join(os.path.dirname(gis.__file__), 'tests' + os.sep + 'data')
def get_ds_file(name, ext): def get_ds_file(name, ext):
return os.sep.join([data_path, name, name + '.%s' % ext]) return os.sep.join([data_path, name, name + '.%s' % ext])
# Test SHP data source object # Test SHP data source object

View File

@ -0,0 +1,94 @@
import unittest
from django.contrib.gis.gdal import Envelope, OGRException
class TestPoint(object):
def __init__(self, x, y):
self.x = x
self.y = y
class EnvelopeTest(unittest.TestCase):
def setUp(self):
self.e = Envelope(0, 0, 5, 5)
def test01_init(self):
"Testing Envelope initilization."
e1 = Envelope((0, 0, 5, 5))
e2 = Envelope(0, 0, 5, 5)
e3 = Envelope(0, '0', '5', 5) # Thanks to ww for this
e4 = Envelope(e1._envelope)
self.assertRaises(OGRException, Envelope, (5, 5, 0, 0))
self.assertRaises(OGRException, Envelope, 5, 5, 0, 0)
self.assertRaises(OGRException, Envelope, (0, 0, 5, 5, 3))
self.assertRaises(OGRException, Envelope, ())
self.assertRaises(ValueError, Envelope, 0, 'a', 5, 5)
self.assertRaises(TypeError, Envelope, u'foo')
self.assertRaises(OGRException, Envelope, (1, 1, 0, 0))
try:
Envelope(0, 0, 0, 0)
except OGRException:
self.fail("shouldn't raise an exception for min_x == max_x or min_y == max_y")
def test02_properties(self):
"Testing Envelope properties."
e = Envelope(0, 0, 2, 3)
self.assertEqual(0, e.min_x)
self.assertEqual(0, e.min_y)
self.assertEqual(2, e.max_x)
self.assertEqual(3, e.max_y)
self.assertEqual((0, 0), e.ll)
self.assertEqual((2, 3), e.ur)
self.assertEqual((0, 0, 2, 3), e.tuple)
self.assertEqual('POLYGON((0.0 0.0,0.0 3.0,2.0 3.0,2.0 0.0,0.0 0.0))', e.wkt)
self.assertEqual('(0.0, 0.0, 2.0, 3.0)', str(e))
def test03_equivalence(self):
"Testing Envelope equivalence."
e1 = Envelope(0.523, 0.217, 253.23, 523.69)
e2 = Envelope((0.523, 0.217, 253.23, 523.69))
self.assertEqual(e1, e2)
self.assertEqual((0.523, 0.217, 253.23, 523.69), e1)
def test04_expand_to_include_pt_2_params(self):
"Testing Envelope expand_to_include -- point as two parameters."
self.e.expand_to_include(2, 6)
self.assertEqual((0, 0, 5, 6), self.e)
self.e.expand_to_include(-1, -1)
self.assertEqual((-1, -1, 5, 6), self.e)
def test05_expand_to_include_pt_2_tuple(self):
"Testing Envelope expand_to_include -- point as a single 2-tuple parameter."
self.e.expand_to_include((10, 10))
self.assertEqual((0, 0, 10, 10), self.e)
self.e.expand_to_include((-10, -10))
self.assertEqual((-10, -10, 10, 10), self.e)
def test06_expand_to_include_extent_4_params(self):
"Testing Envelope expand_to_include -- extent as 4 parameters."
self.e.expand_to_include(-1, 1, 3, 7)
self.assertEqual((-1, 0, 5, 7), self.e)
def test06_expand_to_include_extent_4_tuple(self):
"Testing Envelope expand_to_include -- extent as a single 4-tuple parameter."
self.e.expand_to_include((-1, 1, 3, 7))
self.assertEqual((-1, 0, 5, 7), self.e)
def test07_expand_to_include_envelope(self):
"Testing Envelope expand_to_include with Envelope as parameter."
self.e.expand_to_include(Envelope(-1, 1, 3, 7))
self.assertEqual((-1, 0, 5, 7), self.e)
def test08_expand_to_include_point(self):
"Testing Envelope expand_to_include with Point as parameter."
self.e.expand_to_include(TestPoint(-1, 1))
self.assertEqual((-1, 0, 5, 5), self.e)
self.e.expand_to_include(TestPoint(10, 10))
self.assertEqual((-1, 0, 10, 10), self.e)
def suite():
s = unittest.TestSuite()
s.addTest(unittest.makeSuite(EnvelopeTest))
return s
def run(verbosity=2):
unittest.TextTestRunner(verbosity=verbosity).run(suite())

View File

@ -7,7 +7,7 @@ from django.contrib.gis.tests.geometries import *
class OGRGeomTest(unittest.TestCase): class OGRGeomTest(unittest.TestCase):
"This tests the OGR Geometry." "This tests the OGR Geometry."
def test00_geomtype(self): def test00a_geomtype(self):
"Testing OGRGeomType object." "Testing OGRGeomType object."
# OGRGeomType should initialize on all these inputs. # OGRGeomType should initialize on all these inputs.
@ -22,9 +22,9 @@ class OGRGeomTest(unittest.TestCase):
self.fail('Could not create an OGRGeomType object!') self.fail('Could not create an OGRGeomType object!')
# Should throw TypeError on this input # Should throw TypeError on this input
self.assertRaises(TypeError, OGRGeomType.__init__, 23) self.assertRaises(OGRException, OGRGeomType, 23)
self.assertRaises(TypeError, OGRGeomType.__init__, 'fooD') self.assertRaises(OGRException, OGRGeomType, 'fooD')
self.assertRaises(TypeError, OGRGeomType.__init__, 9) self.assertRaises(OGRException, OGRGeomType, 9)
# Equivalence can take strings, ints, and other OGRGeomTypes # Equivalence can take strings, ints, and other OGRGeomTypes
self.assertEqual(True, OGRGeomType(1) == OGRGeomType(1)) self.assertEqual(True, OGRGeomType(1) == OGRGeomType(1))
@ -38,9 +38,14 @@ class OGRGeomTest(unittest.TestCase):
# Testing the Django field name equivalent property. # Testing the Django field name equivalent property.
self.assertEqual('PointField', OGRGeomType('Point').django) self.assertEqual('PointField', OGRGeomType('Point').django)
self.assertEqual(None, OGRGeomType('Unknown').django) self.assertEqual('GeometryField', OGRGeomType('Unknown').django)
self.assertEqual(None, OGRGeomType('none').django) self.assertEqual(None, OGRGeomType('none').django)
# 'Geometry' initialization implies an unknown geometry type.
gt = OGRGeomType('Geometry')
self.assertEqual(0, gt.num)
self.assertEqual('Unknown', gt.name)
def test01a_wkt(self): def test01a_wkt(self):
"Testing WKT output." "Testing WKT output."
for g in wkt_out: for g in wkt_out:
@ -165,6 +170,12 @@ class OGRGeomTest(unittest.TestCase):
def test07a_polygons(self): def test07a_polygons(self):
"Testing Polygon objects." "Testing Polygon objects."
# Testing `from_bbox` class method
bbox = (-180,-90,180,90)
p = OGRGeometry.from_bbox( bbox )
self.assertEqual(bbox, p.extent)
prev = OGRGeometry('POINT(0 0)') prev = OGRGeometry('POINT(0 0)')
for p in polygons: for p in polygons:
poly = OGRGeometry(p.wkt) poly = OGRGeometry(p.wkt)

View File

@ -9,42 +9,45 @@ def geo_suite():
""" """
from django.conf import settings from django.conf import settings
from django.contrib.gis.tests.utils import mysql, oracle, postgis from django.contrib.gis.tests.utils import mysql, oracle, postgis
from django.contrib.gis.gdal import HAS_GDAL from django.contrib.gis import gdal, utils
from django.contrib.gis.utils import HAS_GEOIP
# Tests that require use of a spatial database (e.g., creation of models) # The test suite.
test_models = ['geoapp',] s = unittest.TestSuite()
# Tests that do not require setting up and tearing down a spatial database. # Adding the GEOS tests. (__future__)
#from django.contrib.gis.geos import tests as geos_tests
#s.addTest(geos_tests.suite())
# Test apps that require use of a spatial database (e.g., creation of models)
test_apps = ['geoapp', 'relatedapp']
if oracle or postgis:
test_apps.append('distapp')
# Tests that do not require setting up and tearing down a spatial database
# and are modules in `django.contrib.gis.tests`.
test_suite_names = [ test_suite_names = [
'test_geos', 'test_geos',
'test_measure', 'test_measure',
] ]
if HAS_GDAL:
if oracle or postgis:
test_models += ['distapp', 'layermap', 'relatedapp']
elif mysql:
test_models += ['relatedapp', 'layermap']
test_suite_names += [ if gdal.HAS_GDAL:
'test_gdal_driver', # These tests require GDAL.
'test_gdal_ds', test_suite_names.append('test_spatialrefsys')
'test_gdal_envelope', test_apps.append('layermap')
'test_gdal_geom',
'test_gdal_srs', # Adding the GDAL tests.
'test_spatialrefsys', from django.contrib.gis.gdal import tests as gdal_tests
] s.addTest(gdal_tests.suite())
else: else:
print >>sys.stderr, "GDAL not available - no GDAL tests will be run." print >>sys.stderr, "GDAL not available - no GDAL tests will be run."
if HAS_GEOIP and hasattr(settings, 'GEOIP_PATH'): if utils.HAS_GEOIP and hasattr(settings, 'GEOIP_PATH'):
test_suite_names.append('test_geoip') test_suite_names.append('test_geoip')
s = unittest.TestSuite() for suite_name in test_suite_names:
for test_suite in test_suite_names: tsuite = getattr(__import__('django.contrib.gis.tests', globals(), locals(), [suite_name]), suite_name)
tsuite = getattr(__import__('django.contrib.gis.tests', globals(), locals(), [test_suite]),test_suite)
s.addTest(tsuite.suite()) s.addTest(tsuite.suite())
return s, test_models return s, test_apps
def run_gis_tests(test_labels, **kwargs): def run_gis_tests(test_labels, **kwargs):
""" """
@ -80,9 +83,9 @@ def run_gis_tests(test_labels, **kwargs):
# Creating the test suite, adding the test models to INSTALLED_APPS, and # Creating the test suite, adding the test models to INSTALLED_APPS, and
# adding the model test suites to our suite package. # adding the model test suites to our suite package.
gis_suite, test_models = geo_suite() gis_suite, test_apps = geo_suite()
for test_model in test_models: for test_app in test_apps:
module_name = 'django.contrib.gis.tests.%s' % test_model module_name = 'django.contrib.gis.tests.%s' % test_app
if mysql: if mysql:
test_module_name = 'tests_mysql' test_module_name = 'tests_mysql'
else: else:
@ -90,13 +93,13 @@ def run_gis_tests(test_labels, **kwargs):
new_installed.append(module_name) new_installed.append(module_name)
# Getting the model test suite # Getting the model test suite
tsuite = getattr(__import__('django.contrib.gis.tests.%s' % test_model, globals(), locals(), [test_module_name]), tsuite = getattr(__import__('django.contrib.gis.tests.%s' % test_app, globals(), locals(), [test_module_name]),
test_module_name) test_module_name)
gis_suite.addTest(tsuite.suite()) gis_suite.addTest(tsuite.suite())
# Resetting the loaded flag to take into account what we appended to # Resetting the loaded flag to take into account what we appended to
# the INSTALLED_APPS (since this routine is invoked through # the INSTALLED_APPS (since this routine is invoked through
# django/core/management, it caches the apps; this ensures that syncdb # django/core/management, it caches the apps; this ensures that syncdb
# will see our appended models) # will see our appended models)
settings.INSTALLED_APPS = new_installed settings.INSTALLED_APPS = new_installed
loading.cache.loaded = False loading.cache.loaded = False
@ -198,3 +201,25 @@ def run_tests(test_labels, verbosity=1, interactive=True, extra_tests=[], suite=
# Returning the total failures and errors # Returning the total failures and errors
return len(result.failures) + len(result.errors) return len(result.failures) + len(result.errors)
# Class for creating a fake module with a run method. This is for the
# GEOS and GDAL tests that were moved to their respective modules.
class _DeprecatedTestModule(object):
def __init__(self, tests, mod):
self.tests = tests
self.mod = mod
def run(self):
from warnings import warn
warn('This test module is deprecated because it has moved to ' \
'`django.contrib.gis.%s.tests` and will disappear in 1.2.' %
self.mod, DeprecationWarning)
self.tests.run()
#from django.contrib.gis.geos import tests as _tests
#test_geos = _DeprecatedTestModule(_tests, 'geos')
from django.contrib.gis.gdal import HAS_GDAL
if HAS_GDAL:
from django.contrib.gis.gdal import tests as _tests
test_gdal = _DeprecatedTestModule(_tests, 'gdal')

View File

@ -1,45 +0,0 @@
import unittest
from django.contrib.gis.gdal import Envelope, OGRException
class EnvelopeTest(unittest.TestCase):
def test01_init(self):
"Testing Envelope initilization."
e1 = Envelope((0, 0, 5, 5))
e2 = Envelope(0, 0, 5, 5)
e3 = Envelope(0, '0', '5', 5) # Thanks to ww for this
e4 = Envelope(e1._envelope)
self.assertRaises(OGRException, Envelope, (5, 5, 0, 0))
self.assertRaises(OGRException, Envelope, 5, 5, 0, 0)
self.assertRaises(OGRException, Envelope, (0, 0, 5, 5, 3))
self.assertRaises(OGRException, Envelope, ())
self.assertRaises(ValueError, Envelope, 0, 'a', 5, 5)
self.assertRaises(TypeError, Envelope, u'foo')
def test02_properties(self):
"Testing Envelope properties."
e = Envelope(0, 0, 2, 3)
self.assertEqual(0, e.min_x)
self.assertEqual(0, e.min_y)
self.assertEqual(2, e.max_x)
self.assertEqual(3, e.max_y)
self.assertEqual((0, 0), e.ll)
self.assertEqual((2, 3), e.ur)
self.assertEqual((0, 0, 2, 3), e.tuple)
self.assertEqual('POLYGON((0.0 0.0,0.0 3.0,2.0 3.0,2.0 0.0,0.0 0.0))', e.wkt)
self.assertEqual('(0.0, 0.0, 2.0, 3.0)', str(e))
def test03_equivalence(self):
"Testing Envelope equivalence."
e1 = Envelope(0.523, 0.217, 253.23, 523.69)
e2 = Envelope((0.523, 0.217, 253.23, 523.69))
self.assertEqual(e1, e2)
self.assertEqual((0.523, 0.217, 253.23, 523.69), e1)
def suite():
s = unittest.TestSuite()
s.addTest(unittest.makeSuite(EnvelopeTest))
return s
def run(verbosity=2):
unittest.TextTestRunner(verbosity=verbosity).run(suite())