mirror of
https://github.com/django/django.git
synced 2025-07-04 09:49:12 +00:00
gis: spatial-database compatibility and usability changes:
(1) The SpatialRefSys and GeometryColumns models and GeometryProxy have been moved to the PostGIS backend because their functionality depends on the spatial databse. (2) The GeoMixin is no longer required, as all the functionality contributed by the extra instance methods has been moved to the GeometryProxy. (3) The `_post_create_sql` field now returns a tuple of SQL statements, instead of a string. git-svn-id: http://code.djangoproject.com/svn/django/branches/gis@6467 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
5d7fd63423
commit
b6c8bba5b6
@ -1,15 +1,21 @@
|
|||||||
"""
|
"""
|
||||||
This module provides the backend for spatial SQL construction with Django.
|
This module provides the backend for spatial SQL construction with Django.
|
||||||
|
|
||||||
Specifically, this module will import the correct routines and modules
|
Specifically, this module will import the correct routines and modules
|
||||||
needed for GeoDjango
|
needed for GeoDjango.
|
||||||
|
|
||||||
|
(1) GeoBackEndField, a base class needed for GeometryField.
|
||||||
|
(2) GeometryProxy, for lazy-instantiated geometries from the
|
||||||
|
database output.
|
||||||
|
(3) GIS_TERMS, a list of acceptable geographic lookup types for
|
||||||
|
the backend.
|
||||||
|
(4) The `parse_lookup` function, used for spatial SQL construction by
|
||||||
|
the GeoQuerySet.
|
||||||
|
(5) The `create_spatial_db`, `geo_quotename`, and `get_geo_where_clause`
|
||||||
|
routines (needed by `parse_lookup`.
|
||||||
|
|
||||||
(1) GeoBackEndField, a base class needed for GeometryField.
|
Currently only PostGIS is supported, but someday backends will be added for
|
||||||
(2) The parse_lookup() function, used for spatial SQL construction by
|
additional spatial databases (e.g., Oracle, DB2).
|
||||||
the GeoQuerySet.
|
|
||||||
|
|
||||||
Currently only PostGIS is supported, but someday backends will be added for
|
|
||||||
additional spatial databases (e.g., Oracle, DB2).
|
|
||||||
"""
|
"""
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db import connection
|
from django.db import connection
|
||||||
@ -21,9 +27,11 @@ from django.utils.datastructures import SortedDict
|
|||||||
ASGML, ASKML, UNION = (False, False, False)
|
ASGML, ASKML, UNION = (False, False, False)
|
||||||
|
|
||||||
if settings.DATABASE_ENGINE == 'postgresql_psycopg2':
|
if settings.DATABASE_ENGINE == 'postgresql_psycopg2':
|
||||||
# PostGIS is the spatial database, getting the rquired modules, renaming as necessary.
|
# PostGIS is the spatial database, getting the rquired modules,
|
||||||
|
# renaming as necessary.
|
||||||
from django.contrib.gis.db.backend.postgis import \
|
from django.contrib.gis.db.backend.postgis import \
|
||||||
PostGISField as GeoBackendField, POSTGIS_TERMS as GIS_TERMS, \
|
PostGISField as GeoBackendField, POSTGIS_TERMS as GIS_TERMS, \
|
||||||
|
PostGISProxy as GeometryProxy, \
|
||||||
create_spatial_db, geo_quotename, get_geo_where_clause, \
|
create_spatial_db, geo_quotename, get_geo_where_clause, \
|
||||||
ASGML, ASKML, UNION
|
ASGML, ASKML, UNION
|
||||||
else:
|
else:
|
||||||
|
@ -7,6 +7,7 @@ from django.contrib.gis.db.backend.postgis.query import \
|
|||||||
MAJOR_VERSION, MINOR_VERSION1, MINOR_VERSION2
|
MAJOR_VERSION, MINOR_VERSION1, MINOR_VERSION2
|
||||||
from django.contrib.gis.db.backend.postgis.creation import create_spatial_db
|
from django.contrib.gis.db.backend.postgis.creation import create_spatial_db
|
||||||
from django.contrib.gis.db.backend.postgis.field import PostGISField
|
from django.contrib.gis.db.backend.postgis.field import PostGISField
|
||||||
|
from django.contrib.gis.db.backend.postgis.proxy import PostGISProxy
|
||||||
|
|
||||||
# Functions used by GeoManager methods, and not via lookup types.
|
# Functions used by GeoManager methods, and not via lookup types.
|
||||||
if MAJOR_VERSION == 1:
|
if MAJOR_VERSION == 1:
|
||||||
|
@ -44,9 +44,11 @@ class PostGISField(Field):
|
|||||||
return sql
|
return sql
|
||||||
|
|
||||||
def _post_create_sql(self, style, db_table):
|
def _post_create_sql(self, style, db_table):
|
||||||
"""Returns SQL that will be executed after the model has been
|
"""
|
||||||
|
Returns SQL that will be executed after the model has been
|
||||||
created. Geometry columns must be added after creation with the
|
created. Geometry columns must be added after creation with the
|
||||||
PostGIS AddGeometryColumn() function."""
|
PostGIS AddGeometryColumn() function.
|
||||||
|
"""
|
||||||
|
|
||||||
# Getting the AddGeometryColumn() SQL necessary to create a PostGIS
|
# Getting the AddGeometryColumn() SQL necessary to create a PostGIS
|
||||||
# geometry field.
|
# geometry field.
|
||||||
@ -54,9 +56,9 @@ class PostGISField(Field):
|
|||||||
|
|
||||||
# If the user wants to index this data, then get the indexing SQL as well.
|
# If the user wants to index this data, then get the indexing SQL as well.
|
||||||
if self._index:
|
if self._index:
|
||||||
return '%s\n%s' % (post_sql, self._geom_index(style, db_table))
|
return (post_sql, self._geom_index(style, db_table))
|
||||||
else:
|
else:
|
||||||
return post_sql
|
return (post_sql,)
|
||||||
|
|
||||||
def _post_delete_sql(self, style, db_table):
|
def _post_delete_sql(self, style, db_table):
|
||||||
"Drops the geometry column."
|
"Drops the geometry column."
|
||||||
@ -66,9 +68,18 @@ class PostGISField(Field):
|
|||||||
style.SQL_FIELD(quotename(self.column)) + ');'
|
style.SQL_FIELD(quotename(self.column)) + ');'
|
||||||
return sql
|
return sql
|
||||||
|
|
||||||
|
def db_type(self):
|
||||||
|
"""
|
||||||
|
PostGIS geometry columns are added by stored procedures, should be
|
||||||
|
None.
|
||||||
|
"""
|
||||||
|
return None
|
||||||
|
|
||||||
def get_db_prep_lookup(self, lookup_type, value):
|
def get_db_prep_lookup(self, lookup_type, value):
|
||||||
"""Returns field's value prepared for database lookup, accepts WKT and
|
"""
|
||||||
GEOS Geometries for the value."""
|
Returns field's value prepared for database lookup, accepts WKT and
|
||||||
|
GEOS Geometries for the value.
|
||||||
|
"""
|
||||||
if lookup_type in POSTGIS_TERMS:
|
if lookup_type in POSTGIS_TERMS:
|
||||||
if lookup_type == 'isnull': return [value] # special case for NULL geometries.
|
if lookup_type == 'isnull': return [value] # special case for NULL geometries.
|
||||||
if not bool(value): return [None] # If invalid value passed in.
|
if not bool(value): return [None] # If invalid value passed in.
|
||||||
@ -101,6 +112,12 @@ class PostGISField(Field):
|
|||||||
else:
|
else:
|
||||||
raise TypeError('Geometry Proxy should only return GEOSGeometry objects.')
|
raise TypeError('Geometry Proxy should only return GEOSGeometry objects.')
|
||||||
|
|
||||||
|
def get_internal_type(self):
|
||||||
|
"""
|
||||||
|
Returns NoField because a stored procedure is used by PostGIS to create the
|
||||||
|
"""
|
||||||
|
return 'NoField'
|
||||||
|
|
||||||
def get_placeholder(self, value):
|
def get_placeholder(self, value):
|
||||||
"""
|
"""
|
||||||
Provides a proper substitution value for Geometries that are not in the
|
Provides a proper substitution value for Geometries that are not in the
|
||||||
|
54
django/contrib/gis/db/backend/postgis/models.py
Normal file
54
django/contrib/gis/db/backend/postgis/models.py
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
"""
|
||||||
|
The GeometryColumns and SpatialRefSys models for the PostGIS backend.
|
||||||
|
"""
|
||||||
|
from django.db import models
|
||||||
|
from django.contrib.gis.models import SpatialRefSysMixin
|
||||||
|
|
||||||
|
# Checking for the presence of GDAL (needed for the SpatialReference object)
|
||||||
|
from django.contrib.gis.gdal import HAS_GDAL
|
||||||
|
if HAS_GDAL:
|
||||||
|
from django.contrib.gis.gdal import SpatialReference
|
||||||
|
|
||||||
|
class GeometryColumns(models.Model):
|
||||||
|
"""
|
||||||
|
The 'geometry_columns' table from the PostGIS. See the PostGIS
|
||||||
|
documentation at Ch. 4.2.2.
|
||||||
|
"""
|
||||||
|
f_table_catalog = models.CharField(maxlength=256)
|
||||||
|
f_table_schema = models.CharField(maxlength=256)
|
||||||
|
f_table_name = models.CharField(maxlength=256, primary_key=True)
|
||||||
|
f_geometry_column = models.CharField(maxlength=256)
|
||||||
|
coord_dimension = models.IntegerField()
|
||||||
|
srid = models.IntegerField()
|
||||||
|
type = models.CharField(maxlength=30)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = 'geometry_columns'
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def table_name(self):
|
||||||
|
"Class method for returning the table name field for this model."
|
||||||
|
return 'f_table_name'
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return "%s.%s - %dD %s field (SRID: %d)" % \
|
||||||
|
(self.f_table_name, self.f_geometry_column,
|
||||||
|
self.coord_dimension, self.type, self.srid)
|
||||||
|
|
||||||
|
class SpatialRefSys(models.Model, SpatialRefSysMixin):
|
||||||
|
"""
|
||||||
|
The 'spatial_ref_sys' table from PostGIS. See the PostGIS
|
||||||
|
documentaiton at Ch. 4.2.1.
|
||||||
|
"""
|
||||||
|
srid = models.IntegerField(primary_key=True)
|
||||||
|
auth_name = models.CharField(maxlength=256)
|
||||||
|
auth_srid = models.IntegerField()
|
||||||
|
srtext = models.CharField(maxlength=2048)
|
||||||
|
proj4text = models.CharField(maxlength=2048)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = 'spatial_ref_sys'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def wkt(self):
|
||||||
|
return self.srtext
|
62
django/contrib/gis/db/backend/postgis/proxy.py
Normal file
62
django/contrib/gis/db/backend/postgis/proxy.py
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
"""
|
||||||
|
The GeometryProxy object, allows for lazy-geometries. The proxy uses
|
||||||
|
Python descriptors for instantiating and setting GEOS Geometry objects
|
||||||
|
corresponding to geographic model fields.
|
||||||
|
|
||||||
|
Thanks to Robert Coup for providing this functionality (see #4322).
|
||||||
|
"""
|
||||||
|
|
||||||
|
from types import NoneType, StringType, UnicodeType
|
||||||
|
|
||||||
|
class PostGISProxy(object):
|
||||||
|
def __init__(self, klass, field):
|
||||||
|
"""
|
||||||
|
Proxy initializes on the given Geometry class (not an instance) and
|
||||||
|
the GeometryField.
|
||||||
|
"""
|
||||||
|
self._field = field
|
||||||
|
self._klass = klass
|
||||||
|
|
||||||
|
def __get__(self, obj, type=None):
|
||||||
|
"""
|
||||||
|
This accessor retrieves the geometry, initializing it using the geometry
|
||||||
|
class specified during initialization and the HEXEWKB value of the field.
|
||||||
|
Currently, only GEOS or OGR geometries are supported.
|
||||||
|
"""
|
||||||
|
# Getting the value of the field.
|
||||||
|
geom_value = obj.__dict__[self._field.attname]
|
||||||
|
|
||||||
|
if isinstance(geom_value, self._klass):
|
||||||
|
geom = geom_value
|
||||||
|
elif (geom_value is None) or (geom_value==''):
|
||||||
|
geom = None
|
||||||
|
else:
|
||||||
|
# Otherwise, a GEOSGeometry object is built using the field's contents,
|
||||||
|
# and the model's corresponding attribute is set.
|
||||||
|
geom = self._klass(geom_value)
|
||||||
|
setattr(obj, self._field.attname, geom)
|
||||||
|
return geom
|
||||||
|
|
||||||
|
def __set__(self, obj, value):
|
||||||
|
"""
|
||||||
|
This accessor sets the proxied geometry with the geometry class
|
||||||
|
specified during initialization. Values of None, HEXEWKB, or WKT may
|
||||||
|
be used to set the geometry as well.
|
||||||
|
"""
|
||||||
|
# The OGC Geometry type of the field.
|
||||||
|
gtype = self._field._geom
|
||||||
|
|
||||||
|
# The geometry type must match that of the field -- unless the
|
||||||
|
# general GeometryField is used.
|
||||||
|
if isinstance(value, self._klass) and (str(value.geom_type).upper() == gtype or gtype == 'GEOMETRY'):
|
||||||
|
# Assigning the SRID to the geometry.
|
||||||
|
if value.srid is None: value.srid = self._field._srid
|
||||||
|
elif isinstance(value, (NoneType, StringType, UnicodeType)):
|
||||||
|
# Set with None, WKT, or HEX
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
raise TypeError('cannot set %s GeometryProxy with value of type: %s' % (obj.__class__.__name__, type(value)))
|
||||||
|
|
||||||
|
# Setting the objects dictionary with the value, and returning.
|
||||||
|
obj.__dict__[self._field.attname] = value
|
||||||
|
return value
|
@ -1,9 +1,9 @@
|
|||||||
from django.contrib.gis.db.backend import GeoBackendField # depends on the spatial database backend.
|
from django.conf import settings
|
||||||
from django.contrib.gis.db.models.proxy import GeometryProxy
|
from django.contrib.gis.db.backend import GeoBackendField, GeometryProxy # these depend on the spatial database backend.
|
||||||
from django.contrib.gis.oldforms import WKTField
|
from django.contrib.gis.oldforms import WKTField
|
||||||
from django.utils.functional import curry
|
from django.contrib.gis.geos import GEOSGeometry
|
||||||
|
|
||||||
#TODO: Flesh out widgets.
|
#TODO: Flesh out widgets; consider adding support for OGR Geometry proxies.
|
||||||
class GeometryField(GeoBackendField):
|
class GeometryField(GeoBackendField):
|
||||||
"The base GIS field -- maps to the OpenGIS Specification Geometry type."
|
"The base GIS field -- maps to the OpenGIS Specification Geometry type."
|
||||||
|
|
||||||
@ -31,26 +31,9 @@ class GeometryField(GeoBackendField):
|
|||||||
def contribute_to_class(self, cls, name):
|
def contribute_to_class(self, cls, name):
|
||||||
super(GeometryField, self).contribute_to_class(cls, name)
|
super(GeometryField, self).contribute_to_class(cls, name)
|
||||||
|
|
||||||
# setup for lazy-instantiated GEOSGeometry objects
|
# Setup for lazy-instantiated GEOSGeometry object.
|
||||||
setattr(cls, self.attname, GeometryProxy(self))
|
setattr(cls, self.attname, GeometryProxy(GEOSGeometry, self))
|
||||||
|
|
||||||
# Adding needed accessor functions
|
|
||||||
setattr(cls, 'get_%s_geos' % self.name, curry(cls._get_GEOM_geos, field=self))
|
|
||||||
setattr(cls, 'get_%s_ogr' % self.name, curry(cls._get_GEOM_ogr, field=self, srid=self._srid))
|
|
||||||
setattr(cls, 'get_%s_srid' % self.name, curry(cls._get_GEOM_srid, srid=self._srid))
|
|
||||||
setattr(cls, 'get_%s_srs' % self.name, curry(cls._get_GEOM_srs, srid=self._srid))
|
|
||||||
setattr(cls, 'get_%s_wkt' % self.name, curry(cls._get_GEOM_wkt, field=self))
|
|
||||||
setattr(cls, 'get_%s_centroid' % self.name, curry(cls._get_GEOM_centroid, field=self))
|
|
||||||
setattr(cls, 'get_%s_area' % self.name, curry(cls._get_GEOM_area, field=self))
|
|
||||||
|
|
||||||
def get_internal_type(self):
|
|
||||||
return "NoField"
|
|
||||||
|
|
||||||
def db_type(self):
|
|
||||||
# Geometry columns are added by stored procedures, and thus should
|
|
||||||
# be None.
|
|
||||||
return None
|
|
||||||
|
|
||||||
def get_manipulator_field_objs(self):
|
def get_manipulator_field_objs(self):
|
||||||
"Using the WKTField (defined above) to be our manipulator."
|
"Using the WKTField (defined above) to be our manipulator."
|
||||||
return [WKTField]
|
return [WKTField]
|
||||||
|
@ -1,54 +1,11 @@
|
|||||||
from warnings import warn
|
|
||||||
|
|
||||||
# GEOS is a requirement, GDAL is not
|
|
||||||
from django.contrib.gis.geos import GEOSGeometry
|
|
||||||
from django.contrib.gis.gdal import HAS_GDAL
|
|
||||||
if HAS_GDAL:
|
|
||||||
from django.contrib.gis.gdal import OGRGeometry, SpatialReference
|
|
||||||
|
|
||||||
# Until model subclassing is a possibility, a mixin class is used to add
|
# Until model subclassing is a possibility, a mixin class is used to add
|
||||||
# the necessary functions that may be contributed for geographic objects.
|
# the necessary functions that may be contributed for geographic objects.
|
||||||
class GeoMixin:
|
class GeoMixin:
|
||||||
"The Geographic Mixin class provides routines for geographic objects."
|
"""
|
||||||
|
The Geographic Mixin class provides routines for geographic objects,
|
||||||
# A subclass of Model is specifically needed so that these geographic
|
however, it is no longer necessary, since all of its previous functions
|
||||||
# routines are present for instantiations of the models.
|
may now be accessed via the GeometryProxy. This mixin is only provided
|
||||||
def _get_GEOM_geos(self, field):
|
for backwards-compatibility purposes, and will be eventually removed
|
||||||
"Returns a GEOS Python object for the geometry."
|
(unless the need arises again).
|
||||||
warn("use model.%s" % field.attname, DeprecationWarning)
|
"""
|
||||||
return getattr(self, field.attname)
|
pass
|
||||||
|
|
||||||
def _get_GEOM_ogr(self, field, srid):
|
|
||||||
"Returns an OGR Python object for the geometry."
|
|
||||||
if HAS_GDAL:
|
|
||||||
return OGRGeometry(getattr(self, field.attname).wkt,
|
|
||||||
SpatialReference('EPSG:%d' % srid))
|
|
||||||
else:
|
|
||||||
raise Exception, "GDAL is not installed!"
|
|
||||||
|
|
||||||
def _get_GEOM_srid(self, srid):
|
|
||||||
"Returns the spatial reference identifier (SRID) of the geometry."
|
|
||||||
warn("use model.geometry_field.srid", DeprecationWarning)
|
|
||||||
return srid
|
|
||||||
|
|
||||||
def _get_GEOM_srs(self, srid):
|
|
||||||
"Returns ane OGR Spatial Reference object of the geometry."
|
|
||||||
if HAS_GDAL:
|
|
||||||
return SpatialReference('EPSG:%d' % srid)
|
|
||||||
else:
|
|
||||||
raise Exception, "GDAL is not installed!"
|
|
||||||
|
|
||||||
def _get_GEOM_wkt(self, field):
|
|
||||||
"Returns the WKT of the geometry."
|
|
||||||
warn("use model.%s.centroid.wkt" % field.attname, DeprecationWarning)
|
|
||||||
return getattr(self, field.attname).wkt
|
|
||||||
|
|
||||||
def _get_GEOM_centroid(self, field):
|
|
||||||
"Returns the centroid of the geometry, in WKT."
|
|
||||||
warn("use model.%s.centroid.wkt" % field.attname, DeprecationWarning)
|
|
||||||
return getattr(self, field.attname).centroid.wkt
|
|
||||||
|
|
||||||
def _get_GEOM_area(self, field):
|
|
||||||
"Returns the area of the geometry, in projected units."
|
|
||||||
warn("use model.%s.area" % field.attname, DeprecationWarning)
|
|
||||||
return getattr(self, field.attname).area
|
|
||||||
|
@ -1,47 +0,0 @@
|
|||||||
"""
|
|
||||||
The GeometryProxy object, allows for lazy-geometries. The proxy uses
|
|
||||||
Python descriptors for instantiating and setting GEOS Geometry objects
|
|
||||||
corresponding to geographic model fields.
|
|
||||||
|
|
||||||
Thanks to Robert Coup for providing this functionality (see #4322).
|
|
||||||
"""
|
|
||||||
|
|
||||||
from types import NoneType, StringType, UnicodeType
|
|
||||||
from django.contrib.gis.geos import GEOSGeometry, GEOSException
|
|
||||||
|
|
||||||
# TODO: docstrings
|
|
||||||
class GeometryProxy(object):
|
|
||||||
def __init__(self, field):
|
|
||||||
"Proxy initializes on the given GeometryField."
|
|
||||||
self._field = field
|
|
||||||
|
|
||||||
def __get__(self, obj, type=None):
|
|
||||||
# Getting the value of the field.
|
|
||||||
geom_value = obj.__dict__[self._field.attname]
|
|
||||||
|
|
||||||
if isinstance(geom_value, GEOSGeometry):
|
|
||||||
# If the value of the field is None, or is already a GEOS Geometry
|
|
||||||
# no more work is needed.
|
|
||||||
geom = geom_value
|
|
||||||
elif (geom_value is None) or (geom_value==''):
|
|
||||||
geom = None
|
|
||||||
else:
|
|
||||||
# Otherwise, a GEOSGeometry object is built using the field's contents,
|
|
||||||
# and the model's corresponding attribute is set.
|
|
||||||
geom = GEOSGeometry(geom_value)
|
|
||||||
setattr(obj, self._field.attname, geom)
|
|
||||||
return geom
|
|
||||||
|
|
||||||
def __set__(self, obj, value):
|
|
||||||
if isinstance(value, GEOSGeometry) and (value.geom_type.upper() == self._field._geom):
|
|
||||||
# Getting set with GEOS Geometry; geom_type must match that of the field.
|
|
||||||
|
|
||||||
# If value's SRID is not set, setting it to the field's SRID.
|
|
||||||
if value.srid is None: value.srid = self._field._srid
|
|
||||||
elif isinstance(value, (NoneType, StringType, UnicodeType)):
|
|
||||||
# Getting set with None, WKT, or HEX
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
raise TypeError, 'cannot set %s GeometryProxy with value of type: %s' % (self._field._geom, type(value))
|
|
||||||
obj.__dict__[self._field.attname] = value
|
|
||||||
return value
|
|
@ -1,8 +1,9 @@
|
|||||||
"""
|
"""
|
||||||
Models for the PostGIS/OGC database tables.
|
Imports the SpatialRefSys and GeometryColumns models dependent on the
|
||||||
|
spatial database backend.
|
||||||
"""
|
"""
|
||||||
import re
|
import re
|
||||||
from django.db import models
|
from django.conf import settings
|
||||||
|
|
||||||
# Checking for the presence of GDAL (needed for the SpatialReference object)
|
# Checking for the presence of GDAL (needed for the SpatialReference object)
|
||||||
from django.contrib.gis.gdal import HAS_GDAL
|
from django.contrib.gis.gdal import HAS_GDAL
|
||||||
@ -15,37 +16,11 @@ if HAS_GDAL:
|
|||||||
# parameter.
|
# parameter.
|
||||||
spheroid_regex = re.compile(r'.+SPHEROID\[\"(?P<name>.+)\",(?P<major>\d+(\.\d+)?),(?P<flattening>\d{3}\.\d+),')
|
spheroid_regex = re.compile(r'.+SPHEROID\[\"(?P<name>.+)\",(?P<major>\d+(\.\d+)?),(?P<flattening>\d{3}\.\d+),')
|
||||||
|
|
||||||
# This is the global 'geometry_columns' from PostGIS.
|
class SpatialRefSysMixin(object):
|
||||||
# See PostGIS Documentation at Ch. 4.2.2
|
"""
|
||||||
class GeometryColumns(models.Model):
|
The SpatialRefSysMixin is a class used by the database-dependent
|
||||||
f_table_catalog = models.CharField(maxlength=256)
|
SpatialRefSys objects to reduce redundnant code.
|
||||||
f_table_schema = models.CharField(maxlength=256)
|
"""
|
||||||
f_table_name = models.CharField(maxlength=256, primary_key=True)
|
|
||||||
f_geometry_column = models.CharField(maxlength=256)
|
|
||||||
coord_dimension = models.IntegerField()
|
|
||||||
srid = models.IntegerField()
|
|
||||||
type = models.CharField(maxlength=30)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
db_table = 'geometry_columns'
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return "%s.%s - %dD %s field (SRID: %d)" % \
|
|
||||||
(self.f_table_name, self.f_geometry_column,
|
|
||||||
self.coord_dimension, self.type, self.srid)
|
|
||||||
|
|
||||||
# This is the global 'spatial_ref_sys' table from PostGIS.
|
|
||||||
# See PostGIS Documentation at Ch. 4.2.1
|
|
||||||
class SpatialRefSys(models.Model):
|
|
||||||
srid = models.IntegerField(primary_key=True)
|
|
||||||
auth_name = models.CharField(maxlength=256)
|
|
||||||
auth_srid = models.IntegerField()
|
|
||||||
srtext = models.CharField(maxlength=2048)
|
|
||||||
proj4text = models.CharField(maxlength=2048)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
db_table = 'spatial_ref_sys'
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def srs(self):
|
def srs(self):
|
||||||
"""
|
"""
|
||||||
@ -58,24 +33,17 @@ class SpatialRefSys(models.Model):
|
|||||||
else:
|
else:
|
||||||
# Attempting to cache a SpatialReference object.
|
# Attempting to cache a SpatialReference object.
|
||||||
|
|
||||||
# Trying to get from WKT first
|
# Trying to get from WKT first.
|
||||||
try:
|
try:
|
||||||
self._srs = SpatialReference(self.srtext, 'wkt')
|
self._srs = SpatialReference(self.wkt)
|
||||||
return self._srs.clone()
|
return self.srs
|
||||||
except Exception, msg1:
|
except Exception, msg:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Trying the proj4 text next
|
raise Exception('Could not get OSR SpatialReference from WKT: %s\nError:\n%s' % (self.wkt, msg))
|
||||||
try:
|
|
||||||
self._srs = SpatialReference(self.proj4text, 'proj4')
|
|
||||||
return self._srs.clone()
|
|
||||||
except Exception, msg2:
|
|
||||||
pass
|
|
||||||
|
|
||||||
raise Exception, 'Could not get an OSR Spatial Reference:\n\tWKT error: %s\n\tPROJ.4 error: %s' % (msg1, msg2)
|
|
||||||
else:
|
else:
|
||||||
raise Exception, 'GDAL is not installed!'
|
raise Exception('GDAL is not installed.')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def ellipsoid(self):
|
def ellipsoid(self):
|
||||||
"""
|
"""
|
||||||
@ -85,7 +53,7 @@ class SpatialRefSys(models.Model):
|
|||||||
if HAS_GDAL:
|
if HAS_GDAL:
|
||||||
return self.srs.ellipsoid
|
return self.srs.ellipsoid
|
||||||
else:
|
else:
|
||||||
m = spheroid_regex.match(self.srtext)
|
m = spheroid_regex.match(self.wkt)
|
||||||
if m: return (float(m.group('major')), float(m.group('flattening')))
|
if m: return (float(m.group('major')), float(m.group('flattening')))
|
||||||
else: return None
|
else: return None
|
||||||
|
|
||||||
@ -139,10 +107,18 @@ class SpatialRefSys(models.Model):
|
|||||||
"Returns the name of the angular units."
|
"Returns the name of the angular units."
|
||||||
return self.srs.angular_name
|
return self.srs.angular_name
|
||||||
|
|
||||||
def __str__(self):
|
def __unicode__(self):
|
||||||
"""
|
"""
|
||||||
Returns the string representation. If GDAL is installed,
|
Returns the string representation. If GDAL is installed,
|
||||||
it will be 'pretty' OGC WKT.
|
it will be 'pretty' OGC WKT.
|
||||||
"""
|
"""
|
||||||
if HAS_GDAL: return str(self.srs)
|
try:
|
||||||
else: return "%d:%s " % (self.srid, self.auth_name)
|
return unicode(self.srs)
|
||||||
|
except:
|
||||||
|
return unicode(self.srtext)
|
||||||
|
|
||||||
|
# The SpatialRefSys and GeometryColumns models
|
||||||
|
if settings.DATABASE_ENGINE == 'postgresql_psycopg2':
|
||||||
|
from django.contrib.gis.db.backend.postgis.models import GeometryColumns, SpatialRefSys
|
||||||
|
else:
|
||||||
|
raise NotImplementedError('No SpatialRefSys or GeometryColumns models for backend: %s' % settings.DATABASE_ENGINE)
|
||||||
|
@ -1,16 +1,21 @@
|
|||||||
from django.contrib.gis.db import models
|
from django.contrib.gis.db import models
|
||||||
|
|
||||||
class Country(models.Model, models.GeoMixin):
|
class Country(models.Model):
|
||||||
name = models.CharField(maxlength=30)
|
name = models.CharField(max_length=30)
|
||||||
mpoly = models.MultiPolygonField() # SRID, by default, is 4326
|
mpoly = models.MultiPolygonField() # SRID, by default, is 4326
|
||||||
objects = models.GeoManager()
|
objects = models.GeoManager()
|
||||||
|
|
||||||
class City(models.Model, models.GeoMixin):
|
class City(models.Model):
|
||||||
name = models.CharField(maxlength=30)
|
name = models.CharField(max_length=30)
|
||||||
point = models.PointField()
|
point = models.PointField()
|
||||||
objects = models.GeoManager()
|
objects = models.GeoManager()
|
||||||
|
|
||||||
class State(models.Model, models.GeoMixin):
|
class State(models.Model):
|
||||||
name = models.CharField(maxlength=30)
|
name = models.CharField(max_length=30)
|
||||||
poly = models.PolygonField(null=True) # Allowing NULL geometries here.
|
poly = models.PolygonField(null=True) # Allowing NULL geometries here.
|
||||||
objects = models.GeoManager()
|
objects = models.GeoManager()
|
||||||
|
|
||||||
|
class Feature(models.Model):
|
||||||
|
name = models.CharField(max_length=20)
|
||||||
|
geom = models.GeometryField()
|
||||||
|
objects = models.GeoManager()
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import unittest
|
import unittest
|
||||||
from models import Country, City, State
|
from models import Country, City, State, Feature
|
||||||
from django.contrib.gis.geos import fromstr, Point, LineString, LinearRing, Polygon
|
from django.contrib.gis.geos import *
|
||||||
|
from django.contrib.gis import gdal
|
||||||
|
|
||||||
class GeoModelTest(unittest.TestCase):
|
class GeoModelTest(unittest.TestCase):
|
||||||
|
|
||||||
@ -59,8 +60,17 @@ class GeoModelTest(unittest.TestCase):
|
|||||||
nullstate = State(name='NullState', poly=ply)
|
nullstate = State(name='NullState', poly=ply)
|
||||||
self.assertEqual(4326, nullstate.poly.srid) # SRID auto-set from None
|
self.assertEqual(4326, nullstate.poly.srid) # SRID auto-set from None
|
||||||
nullstate.save()
|
nullstate.save()
|
||||||
self.assertEqual(ply, State.objects.get(name='NullState').poly)
|
|
||||||
|
ns = State.objects.get(name='NullState')
|
||||||
|
self.assertEqual(ply, ns.poly)
|
||||||
|
|
||||||
|
# Testing the `ogr` and `srs` lazy-geometry properties.
|
||||||
|
if gdal.HAS_GDAL:
|
||||||
|
self.assertEqual(True, isinstance(ns.poly.ogr, gdal.OGRGeometry))
|
||||||
|
self.assertEqual(ns.poly.wkb, ns.poly.ogr.wkb)
|
||||||
|
self.assertEqual(True, isinstance(ns.poly.srs, gdal.SpatialReference))
|
||||||
|
self.assertEqual('WGS 84', ns.poly.srs.name)
|
||||||
|
|
||||||
# Changing the interior ring on the poly attribute.
|
# Changing the interior ring on the poly attribute.
|
||||||
new_inner = LinearRing((30, 30), (30, 70), (70, 70), (70, 30), (30, 30))
|
new_inner = LinearRing((30, 30), (30, 70), (70, 70), (70, 30), (30, 30))
|
||||||
nullstate.poly[1] = new_inner.clone()
|
nullstate.poly[1] = new_inner.clone()
|
||||||
@ -277,6 +287,32 @@ class GeoModelTest(unittest.TestCase):
|
|||||||
self.assertEqual(True, union.equals_exact(u, 10)) # Going up to 10 digits of precision.
|
self.assertEqual(True, union.equals_exact(u, 10)) # Going up to 10 digits of precision.
|
||||||
qs = City.objects.filter(name='NotACity')
|
qs = City.objects.filter(name='NotACity')
|
||||||
self.assertEqual(None, qs.union('point'))
|
self.assertEqual(None, qs.union('point'))
|
||||||
|
|
||||||
|
def test18_geometryfield(self):
|
||||||
|
"Testing GeometryField."
|
||||||
|
f1 = Feature(name='Point', geom=Point(1, 1))
|
||||||
|
f2 = Feature(name='LineString', geom=LineString((0, 0), (1, 1), (5, 5)))
|
||||||
|
f3 = Feature(name='Polygon', geom=Polygon(LinearRing((0, 0), (0, 5), (5, 5), (5, 0), (0, 0))))
|
||||||
|
f4 = Feature(name='GeometryCollection',
|
||||||
|
geom=GeometryCollection(Point(2, 2), LineString((0, 0), (2, 2)),
|
||||||
|
Polygon(LinearRing((0, 0), (0, 5), (5, 5), (5, 0), (0, 0)))))
|
||||||
|
f1.save()
|
||||||
|
f2.save()
|
||||||
|
f3.save()
|
||||||
|
f4.save()
|
||||||
|
|
||||||
|
f_1 = Feature.objects.get(name='Point')
|
||||||
|
self.assertEqual(True, isinstance(f_1.geom, Point))
|
||||||
|
self.assertEqual((1.0, 1.0), f_1.geom.tuple)
|
||||||
|
f_2 = Feature.objects.get(name='LineString')
|
||||||
|
self.assertEqual(True, isinstance(f_2.geom, LineString))
|
||||||
|
self.assertEqual(((0.0, 0.0), (1.0, 1.0), (5.0, 5.0)), f_2.geom.tuple)
|
||||||
|
|
||||||
|
f_3 = Feature.objects.get(name='Polygon')
|
||||||
|
self.assertEqual(True, isinstance(f_3.geom, Polygon))
|
||||||
|
f_4 = Feature.objects.get(name='GeometryCollection')
|
||||||
|
self.assertEqual(True, isinstance(f_4.geom, GeometryCollection))
|
||||||
|
self.assertEqual(f_3.geom, f_4.geom[2])
|
||||||
|
|
||||||
def suite():
|
def suite():
|
||||||
s = unittest.TestSuite()
|
s = unittest.TestSuite()
|
||||||
|
@ -404,7 +404,7 @@ def custom_sql_for_model(model, style):
|
|||||||
# Post-creation SQL should come before any initial SQL data is loaded.
|
# Post-creation SQL should come before any initial SQL data is loaded.
|
||||||
for f in opts.fields:
|
for f in opts.fields:
|
||||||
if hasattr(f, '_post_create_sql'):
|
if hasattr(f, '_post_create_sql'):
|
||||||
output.append(f._post_create_sql(style, model._meta.db_table))
|
output.extend(f._post_create_sql(style, model._meta.db_table))
|
||||||
|
|
||||||
# Some backends can't execute more than one SQL statement at a time,
|
# Some backends can't execute more than one SQL statement at a time,
|
||||||
# so split into separate statements.
|
# so split into separate statements.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user