Changeset 6467
- Timestamp:
- 10/08/07 13:18:17 (9 months ago)
- Files:
-
- django/branches/gis/django/contrib/gis/db/backend/__init__.py (modified) (2 diffs)
- django/branches/gis/django/contrib/gis/db/backend/postgis/field.py (modified) (4 diffs)
- django/branches/gis/django/contrib/gis/db/backend/postgis/__init__.py (modified) (1 diff)
- django/branches/gis/django/contrib/gis/db/backend/postgis/models.py (added)
- django/branches/gis/django/contrib/gis/db/backend/postgis/proxy.py (moved) (moved from django/branches/gis/django/contrib/gis/db/models/proxy.py) (2 diffs)
- django/branches/gis/django/contrib/gis/db/models/fields/__init__.py (modified) (2 diffs)
- django/branches/gis/django/contrib/gis/db/models/mixin.py (modified) (1 diff)
- django/branches/gis/django/contrib/gis/models.py (modified) (5 diffs)
- django/branches/gis/django/contrib/gis/tests/geoapp/models.py (modified) (1 diff)
- django/branches/gis/django/contrib/gis/tests/geoapp/tests.py (modified) (3 diffs)
- django/branches/gis/django/core/management/sql.py (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
django/branches/gis/django/contrib/gis/db/backend/__init__.py
r6441 r6467 1 1 """ 2 This module provides the backend for spatial SQL construction with Django. 3 4 Specifically, this module will import the correct routines and modules 5 needed for GeoDjango 6 7 (1) GeoBackEndField, a base class needed for GeometryField. 8 (2) The parse_lookup() function, used for spatial SQL construction by 9 the GeoQuerySet. 10 11 Currently only PostGIS is supported, but someday backends will be added for 12 additional spatial databases (e.g., Oracle, DB2). 2 This module provides the backend for spatial SQL construction with Django. 3 4 Specifically, this module will import the correct routines and modules 5 needed for GeoDjango. 6 7 (1) GeoBackEndField, a base class needed for GeometryField. 8 (2) GeometryProxy, for lazy-instantiated geometries from the 9 database output. 10 (3) GIS_TERMS, a list of acceptable geographic lookup types for 11 the backend. 12 (4) The `parse_lookup` function, used for spatial SQL construction by 13 the GeoQuerySet. 14 (5) The `create_spatial_db`, `geo_quotename`, and `get_geo_where_clause` 15 routines (needed by `parse_lookup`. 16 17 Currently only PostGIS is supported, but someday backends will be added for 18 additional spatial databases (e.g., Oracle, DB2). 13 19 """ 14 20 from django.conf import settings … … 22 28 23 29 if settings.DATABASE_ENGINE == 'postgresql_psycopg2': 24 # PostGIS is the spatial database, getting the rquired modules, renaming as necessary. 30 # PostGIS is the spatial database, getting the rquired modules, 31 # renaming as necessary. 25 32 from django.contrib.gis.db.backend.postgis import \ 26 33 PostGISField as GeoBackendField, POSTGIS_TERMS as GIS_TERMS, \ 34 PostGISProxy as GeometryProxy, \ 27 35 create_spatial_db, geo_quotename, get_geo_where_clause, \ 28 36 ASGML, ASKML, UNION django/branches/gis/django/contrib/gis/db/backend/postgis/field.py
r6439 r6467 45 45 46 46 def _post_create_sql(self, style, db_table): 47 """Returns SQL that will be executed after the model has been 47 """ 48 Returns SQL that will be executed after the model has been 48 49 created. Geometry columns must be added after creation with the 49 PostGIS AddGeometryColumn() function.""" 50 PostGIS AddGeometryColumn() function. 51 """ 50 52 51 53 # Getting the AddGeometryColumn() SQL necessary to create a PostGIS … … 55 57 # If the user wants to index this data, then get the indexing SQL as well. 56 58 if self._index: 57 return '%s\n%s' %(post_sql, self._geom_index(style, db_table))59 return (post_sql, self._geom_index(style, db_table)) 58 60 else: 59 return post_sql61 return (post_sql,) 60 62 61 63 def _post_delete_sql(self, style, db_table): … … 67 69 return sql 68 70 71 def db_type(self): 72 """ 73 PostGIS geometry columns are added by stored procedures, should be 74 None. 75 """ 76 return None 77 69 78 def get_db_prep_lookup(self, lookup_type, value): 70 """Returns field's value prepared for database lookup, accepts WKT and 71 GEOS Geometries for the value.""" 79 """ 80 Returns field's value prepared for database lookup, accepts WKT and 81 GEOS Geometries for the value. 82 """ 72 83 if lookup_type in POSTGIS_TERMS: 73 84 if lookup_type == 'isnull': return [value] # special case for NULL geometries. … … 102 113 raise TypeError('Geometry Proxy should only return GEOSGeometry objects.') 103 114 115 def get_internal_type(self): 116 """ 117 Returns NoField because a stored procedure is used by PostGIS to create the 118 """ 119 return 'NoField' 120 104 121 def get_placeholder(self, value): 105 122 """ django/branches/gis/django/contrib/gis/db/backend/postgis/__init__.py
r6439 r6467 8 8 from django.contrib.gis.db.backend.postgis.creation import create_spatial_db 9 9 from django.contrib.gis.db.backend.postgis.field import PostGISField 10 from django.contrib.gis.db.backend.postgis.proxy import PostGISProxy 10 11 11 12 # Functions used by GeoManager methods, and not via lookup types. django/branches/gis/django/contrib/gis/db/backend/postgis/proxy.py
r6243 r6467 1 1 """ 2 The GeometryProxy object, allows for lazy-geometries. The proxy uses3 Python descriptors for instantiating and setting GEOS Geometry objects4 corresponding to geographic model fields.2 The GeometryProxy object, allows for lazy-geometries. The proxy uses 3 Python descriptors for instantiating and setting GEOS Geometry objects 4 corresponding to geographic model fields. 5 5 6 Thanks to Robert Coup for providing this functionality (see #4322).6 Thanks to Robert Coup for providing this functionality (see #4322). 7 7 """ 8 8 9 9 from types import NoneType, StringType, UnicodeType 10 from django.contrib.gis.geos import GEOSGeometry, GEOSException11 10 12 # TODO: docstrings 13 class GeometryProxy(object): 14 def __init__(self, field): 15 "Proxy initializes on the given GeometryField." 11 class PostGISProxy(object): 12 def __init__(self, klass, field): 13 """ 14 Proxy initializes on the given Geometry class (not an instance) and 15 the GeometryField. 16 """ 16 17 self._field = field 18 self._klass = klass 17 19 18 20 def __get__(self, obj, type=None): 21 """ 22 This accessor retrieves the geometry, initializing it using the geometry 23 class specified during initialization and the HEXEWKB value of the field. 24 Currently, only GEOS or OGR geometries are supported. 25 """ 19 26 # Getting the value of the field. 20 27 geom_value = obj.__dict__[self._field.attname] 21 22 if isinstance(geom_value, GEOSGeometry): 23 # If the value of the field is None, or is already a GEOS Geometry 24 # no more work is needed. 28 29 if isinstance(geom_value, self._klass): 25 30 geom = geom_value 26 31 elif (geom_value is None) or (geom_value==''): … … 29 34 # Otherwise, a GEOSGeometry object is built using the field's contents, 30 35 # and the model's corresponding attribute is set. 31 geom = GEOSGeometry(geom_value)36 geom = self._klass(geom_value) 32 37 setattr(obj, self._field.attname, geom) 33 38 return geom 34 39 35 def __set__(self, obj, value): 36 if isinstance(value, GEOSGeometry) and (value.geom_type.upper() == self._field._geom): 37 # Getting set with GEOS Geometry; geom_type must match that of the field. 38 39 # If value's SRID is not set, setting it to the field's SRID. 40 def __set__(self, obj, value): 41 """ 42 This accessor sets the proxied geometry with the geometry class 43 specified during initialization. Values of None, HEXEWKB, or WKT may 44 be used to set the geometry as well. 45 """ 46 # The OGC Geometry type of the field. 47 gtype = self._field._geom 48 49 # The geometry type must match that of the field -- unless the 50 # general GeometryField is used. 51 if isinstance(value, self._klass) and (str(value.geom_type).upper() == gtype or gtype == 'GEOMETRY'): 52 # Assigning the SRID to the geometry. 40 53 if value.srid is None: value.srid = self._field._srid 41 54 elif isinstance(value, (NoneType, StringType, UnicodeType)): 42 # Getting set with None, WKT, or HEX55 # Set with None, WKT, or HEX 43 56 pass 44 57 else: 45 raise TypeError, 'cannot set %s GeometryProxy with value of type: %s' % (self._field._geom, type(value)) 58 raise TypeError('cannot set %s GeometryProxy with value of type: %s' % (obj.__class__.__name__, type(value))) 59 60 # Setting the objects dictionary with the value, and returning. 46 61 obj.__dict__[self._field.attname] = value 47 62 return value django/branches/gis/django/contrib/gis/db/models/fields/__init__.py
r6018 r6467 1 from django.con trib.gis.db.backend import GeoBackendField # depends on the spatial database backend.2 from django.contrib.gis.db. models.proxy import GeometryProxy1 from django.conf import settings 2 from django.contrib.gis.db.backend import GeoBackendField, GeometryProxy # these depend on the spatial database backend. 3 3 from django.contrib.gis.oldforms import WKTField 4 from django. utils.functional import curry4 from django.contrib.gis.geos import GEOSGeometry 5 5 6 #TODO: Flesh out widgets .6 #TODO: Flesh out widgets; consider adding support for OGR Geometry proxies. 7 7 class GeometryField(GeoBackendField): 8 8 "The base GIS field -- maps to the OpenGIS Specification Geometry type." … … 32 32 super(GeometryField, self).contribute_to_class(cls, name) 33 33 34 # setup for lazy-instantiated GEOSGeometry objects 35 setattr(cls, self.attname, GeometryProxy(self)) 36 37 # Adding needed accessor functions 38 setattr(cls, 'get_%s_geos' % self.name, curry(cls._get_GEOM_geos, field=self)) 39 setattr(cls, 'get_%s_ogr' % self.name, curry(cls._get_GEOM_ogr, field=self, srid=self._srid)) 40 setattr(cls, 'get_%s_srid' % self.name, curry(cls._get_GEOM_srid, srid=self._srid)) 41 setattr(cls, 'get_%s_srs' % self.name, curry(cls._get_GEOM_srs, srid=self._srid)) 42 setattr(cls, 'get_%s_wkt' % self.name, curry(cls._get_GEOM_wkt, field=self)) 43 setattr(cls, 'get_%s_centroid' % self.name, curry(cls._get_GEOM_centroid, field=self)) 44 setattr(cls, 'get_%s_area' % self.name, curry(cls._get_GEOM_area, field=self)) 34 # Setup for lazy-instantiated GEOSGeometry object. 35 setattr(cls, self.attname, GeometryProxy(GEOSGeometry, self)) 45 36 46 def get_internal_type(self):47 return "NoField"48 49 def db_type(self):50 # Geometry columns are added by stored procedures, and thus should51 # be None.52 return None53 54 37 def get_manipulator_field_objs(self): 55 38 "Using the WKTField (defined above) to be our manipulator." django/branches/gis/django/contrib/gis/db/models/mixin.py
r6413 r6467 1 from warnings import warn2 3 # GEOS is a requirement, GDAL is not4 from django.contrib.gis.geos import GEOSGeometry5 from django.contrib.gis.gdal import HAS_GDAL6 if HAS_GDAL:7 from django.contrib.gis.gdal import OGRGeometry, SpatialReference8 9 1 # Until model subclassing is a possibility, a mixin class is used to add 10 2 # the necessary functions that may be contributed for geographic objects. 11 3 class GeoMixin: 12 "The Geographic Mixin class provides routines for geographic objects." 13 14 # A subclass of Model is specifically needed so that these geographic 15 # routines are present for instantiations of the models. 16 def _get_GEOM_geos(self, field): 17 "Returns a GEOS Python object for the geometry." 18 warn("use model.%s" % field.attname, DeprecationWarning) 19 return getattr(self, field.attname) 20 21 def _get_GEOM_ogr(self, field, srid): 22 "Returns an OGR Python object for the geometry." 23 if HAS_GDAL: 24 return OGRGeometry(getattr(self, field.attname).wkt, 25 SpatialReference('EPSG:%d' % srid)) 26 else: 27 raise Exception, "GDAL is not installed!" 28 29 def _get_GEOM_srid(self, srid): 30 "Returns the spatial reference identifier (SRID) of the geometry." 31 warn("use model.geometry_field.srid", DeprecationWarning) 32 return srid 33 34 def _get_GEOM_srs(self, srid): 35 "Returns ane OGR Spatial Reference object of the geometry." 36 if HAS_GDAL: 37 return SpatialReference('EPSG:%d' % srid) 38 else: 39 raise Exception, "GDAL is not installed!" 40 41 def _get_GEOM_wkt(self, field): 42 "Returns the WKT of the geometry." 43 warn("use model.%s.centroid.wkt" % field.attname, DeprecationWarning) 44 return getattr(self, field.attname).wkt 45 46 def _get_GEOM_centroid(self, field): 47 "Returns the centroid of the geometry, in WKT." 48 warn("use model.%s.centroid.wkt" % field.attname, DeprecationWarning) 49 return getattr(self, field.attname).centroid.wkt 50 51 def _get_GEOM_area(self, field): 52 "Returns the area of the geometry, in projected units." 53 warn("use model.%s.area" % field.attname, DeprecationWarning) 54 return getattr(self, field.attname).area 4 """ 5 The Geographic Mixin class provides routines for geographic objects, 6 however, it is no longer necessary, since all of its previous functions 7 may now be accessed via the GeometryProxy. This mixin is only provided 8 for backwards-compatibility purposes, and will be eventually removed 9 (unless the need arises again). 10 """ 11 pass django/branches/gis/django/contrib/gis/models.py
r6427 r6467 1 1 """ 2 Models for the PostGIS/OGC database tables. 2 Imports the SpatialRefSys and GeometryColumns models dependent on the 3 spatial database backend. 3 4 """ 4 5 import re 5 from django. db import models6 from django.conf import settings 6 7 7 8 # Checking for the presence of GDAL (needed for the SpatialReference object) … … 16 17 spheroid_regex = re.compile(r'.+SPHEROID\[\"(?P<name>.+)\",(?P<major>\d+(\.\d+)?),(?P<flattening>\d{3}\.\d+),') 17 18 18 # This is the global 'geometry_columns' from PostGIS. 19 # See PostGIS Documentation at Ch. 4.2.2 20 class GeometryColumns(models.Model): 21 f_table_catalog = models.CharField(maxlength=256) 22 f_table_schema = models.CharField(maxlength=256) 23 f_table_name = models.CharField(maxlength=256, primary_key=True) 24 f_geometry_column = models.CharField(maxlength=256) 25 coord_dimension = models.IntegerField() 26 srid = models.IntegerField() 27 type = models.CharField(maxlength=30) 28 29 class Meta: 30 db_table = 'geometry_columns' 31 32 def __str__(self): 33 return "%s.%s - %dD %s field (SRID: %d)" % \ 34 (self.f_table_name, self.f_geometry_column, 35 self.coord_dimension, self.type, self.srid) 36 37 # This is the global 'spatial_ref_sys' table from PostGIS. 38 # See PostGIS Documentation at Ch. 4.2.1 39 class SpatialRefSys(models.Model): 40 srid = models.IntegerField(primary_key=True) 41 auth_name = models.CharField(maxlength=256) 42 auth_srid = models.IntegerField() 43 srtext = models.CharField(maxlength=2048) 44 proj4text = models.CharField(maxlength=2048) 45 46 class Meta: 47 db_table = 'spatial_ref_sys' 48 19 class SpatialRefSysMixin(object): 20 """ 21 The SpatialRefSysMixin is a class used by the database-dependent 22 SpatialRefSys objects to reduce redundnant code. 23 """ 49 24 @property 50 25 def srs(self): … … 59 34 # Attempting to cache a SpatialReference object. 60 35 61 # Trying to get from WKT first 36 # Trying to get from WKT first. 62 37 try: 63 self._srs = SpatialReference(self. srtext, 'wkt')64 return self. _srs.clone()65 except Exception, msg 1:38 self._srs = SpatialReference(self.wkt) 39 return self.srs 40 except Exception, msg: 66 41 pass 42 43 raise Exception('Could not get OSR SpatialReference from WKT: %s\nError:\n%s' % (self.wkt, msg)) 44 else: 45 raise Exception('GDAL is not installed.') 67 46 68 # Trying the proj4 text next69 try:70 self._srs = SpatialReference(self.proj4text, 'proj4')71 return self._srs.clone()72 except Exception, msg2:73 pass74 75 raise Exception, 'Could not get an OSR Spatial Reference:\n\tWKT error: %s\n\tPROJ.4 error: %s' % (msg1, msg2)76 else:77 raise Exception, 'GDAL is not installed!'78 79 47 @property 80 48 def ellipsoid(self): … … 86 54 return self.srs.ellipsoid 87 55 else: 88 m = spheroid_regex.match(self. srtext)56 m = spheroid_regex.match(self.wkt) 89 57 if m: return (float(m.group('major')), float(m.group('flattening'))) 90 58 else: return None … … 140 108 return self.srs.angular_name 141 109 142 def __ str__(self):110 def __unicode__(self): 143 111 """ 144 112 Returns the string representation. If GDAL is installed, 145 113 it will be 'pretty' OGC WKT. 146 114 """ 147 if HAS_GDAL: return str(self.srs) 148 else: return "%d:%s " % (self.srid, self.auth_name) 115 try: 116 return unicode(self.srs) 117 except: 118 return unicode(self.srtext) 119 120 # The SpatialRefSys and GeometryColumns models 121 if settings.DATABASE_ENGINE == 'postgresql_psycopg2': 122 from django.contrib.gis.db.backend.postgis.models import GeometryColumns, SpatialRefSys 123 else: 124 raise NotImplementedError('No SpatialRefSys or GeometryColumns models for backend: %s' % settings.DATABASE_ENGINE) django/branches/gis/django/contrib/gis/tests/geoapp/models.py
r5773 r6467 1 1 from django.contrib.gis.db import models 2 2 3 class Country(models.Model , models.GeoMixin):4 name = models.CharField(max length=30)3 class Country(models.Model): 4 name = models.CharField(max_length=30) 5 5 mpoly = models.MultiPolygonField() # SRID, by default, is 4326 6 6 objects = models.GeoManager() 7 7 8 class City(models.Model , models.GeoMixin):9 name = models.CharField(max length=30)8 class City(models.Model): 9 name = models.CharField(max_length=30) 10 10 point = models.PointField() 11 11 objects = models.GeoManager() 12 12 13 class State(models.Model , models.GeoMixin):14 name = models.CharField(max length=30)13 class State(models.Model): 14 name = models.CharField(max_length=30) 15 15 poly = models.PolygonField(null=True) # Allowing NULL geometries here. 16 16 objects = models.GeoManager() 17 18 class Feature(models.Model): 19 name = models.CharField(max_length=20) 20 geom = models.GeometryField() 21 objects = models.GeoManager() django/branches/gis/django/contrib/gis/tests/geoapp/tests.py
r6441 r6467 1 1 import unittest 2 from models import Country, City, State 3 from django.contrib.gis.geos import fromstr, Point, LineString, LinearRing, Polygon 2 from models import Country, City, State, Feature 3 from django.contrib.gis.geos import * 4 from django.contrib.gis import gdal 4 5 5 6 class GeoModelTest(unittest.TestCase): … … 60 61 self.assertEqual(4326, nullstate.poly.srid) # SRID auto-set from None 61 62 nullstate.save() 62 self.assertEqual(ply, State.objects.get(name='NullState').poly) 63 63 64 ns = State.objects.get(name='NullState') 65 self.assertEqual(ply, ns.poly) 66 67 # Testing the `ogr` and `srs` lazy-geometry properties. 68 if gdal.HAS_GDAL: 69 self.assertEqual(True, isinstance(ns.poly.ogr, gdal.OGRGeometry)) 70 self.assertEqual(ns.poly.wkb, ns.poly.ogr.wkb) 71 self.assertEqual(True, isinstance(ns.poly.srs, gdal.SpatialReference)) 72 self.assertEqual('WGS 84', ns.poly.srs.name) 73 64 74 # Changing the interior ring on the poly attribute. 65 75 new_inner = LinearRing((30, 30), (30, 70), (70, 70), (70, 30), (30, 30)) … … 278 288 qs = City.objects.filter(name='NotACity') 279 289 self.assertEqual(None, qs.union('point')) 290 291 def test18_geometryfield(self): 292 "Testing GeometryField." 293 f1 = Feature(name='Point', geom=Point(1, 1)) 294 f2 = Feature(name='LineString', geom=LineString((0, 0), (1, 1), (5, 5))) 295 f3 = Feature(name='Polygon', geom=Polygon(LinearRing((0, 0), (0, 5), (5, 5), (5, 0), (0, 0)))) 296 f4 = Feature(name='GeometryCollection', 297 geom=GeometryCollection(Point(2, 2), LineString((0, 0), (2, 2)), 298 Polygon(LinearRing((0, 0), (0, 5), (5, 5), (5, 0), (0, 0))))) 299 f1.save() 300 f2.save() 301 f3.save() 302 f4.save() 303 304 f_1 = Feature.objects.get(name='Point') 305 self.assertEqual(True, isinstance(f_1.geom, Point)) 306 self.assertEqual((1.0, 1.0), f_1.geom.tuple) 307 f_2 = Feature.objects.get(name='LineString') 308 self.assertEqual(True, isinstance(f_2.geom, LineString)) 309 self.assertEqual(((0.0, 0.0), (1.0, 1.0), (5.0, 5.0)), f_2.geom.tuple) 310 311 f_3 = Feature.objects.get(name='Polygon') 312 self.assertEqual(True, isinstance(f_3.geom, Polygon)) 313 f_4 = Feature.objects.get(name='GeometryCollection') 314 self.assertEqual(True, isinstance(f_4.geom, GeometryCollection)) 315 self.assertEqual(f_3.geom, f_4.geom[2]) 280 316 281 317 def suite(): django/branches/gis/django/core/management/sql.py
r6395 r6467 405 405 for f in opts.fields: 406 406 if hasattr(f, '_post_create_sql'): 407 output. append(f._post_create_sql(style, model._meta.db_table))407 output.extend(f._post_create_sql(style, model._meta.db_table)) 408 408 409 409 # Some backends can't execute more than one SQL statement at a time,
