Permalink
Browse files

Fixed #9686 -- SpatiaLite is now a supported spatial database backend…

…. Thanks to Matthew Hancher for initial patch and hard work in implementing this feature.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@10222 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
1 parent 5ff1703 commit 8eca2df3a4db70ed7d721312d789a92274aee397 @jbronn jbronn committed Mar 30, 2009
@@ -0,0 +1,59 @@
+__all__ = ['create_test_spatial_db', 'get_geo_where_clause', 'SpatialBackend']
+
+from ctypes.util import find_library
+from django.conf import settings
+from django.db.backends.signals import connection_created
+
+from django.contrib.gis.db.backend.base import BaseSpatialBackend
+from django.contrib.gis.db.backend.spatialite.adaptor import SpatiaLiteAdaptor
+from django.contrib.gis.db.backend.spatialite.creation import create_test_spatial_db
+from django.contrib.gis.db.backend.spatialite.field import SpatiaLiteField
+from django.contrib.gis.db.backend.spatialite.models import GeometryColumns, SpatialRefSys
+from django.contrib.gis.db.backend.spatialite.query import *
+
+# Here we are figuring out the path to the SpatiLite library (`libspatialite`).
+# If it's not in the system PATH, it may be set manually in the settings via
+# the `SPATIALITE_LIBRARY_PATH` setting.
+spatialite_lib = getattr(settings, 'SPATIALITE_LIBRARY_PATH', find_library('spatialite'))
+if spatialite_lib:
+ def initialize_spatialite(sender=None, **kwargs):
+ """
+ This function initializes the pysqlite2 connection to enable the
+ loading of extensions, and to load up the SpatiaLite library
+ extension.
+ """
+ from django.db import connection
+ connection.connection.enable_load_extension(True)
+ connection.cursor().execute("SELECT load_extension(%s)", (spatialite_lib,))
+ connection_created.connect(initialize_spatialite)
+else:
+ # No SpatiaLite library found.
+ raise Exception('Unable to locate SpatiaLite, needed to use GeoDjango with sqlite3.')
+
+SpatialBackend = BaseSpatialBackend(name='spatialite', spatialite=True,
+ area=AREA,
+ centroid=CENTROID,
+ contained=CONTAINED,
+ difference=DIFFERENCE,
+ distance=DISTANCE,
+ distance_functions=DISTANCE_FUNCTIONS,
+ envelope=ENVELOPE,
+ from_text=GEOM_FROM_TEXT,
+ gis_terms=SPATIALITE_TERMS,
+ intersection=INTERSECTION,
+ length=LENGTH,
+ num_geom=NUM_GEOM,
+ num_points=NUM_POINTS,
+ point_on_surface=POINT_ON_SURFACE,
+ scale=SCALE,
+ select=GEOM_SELECT,
+ sym_difference=SYM_DIFFERENCE,
+ transform=TRANSFORM,
+ translate=TRANSLATE,
+ union=UNION,
+ unionagg=UNIONAGG,
+ Adaptor=SpatiaLiteAdaptor,
+ Field=SpatiaLiteField,
+ GeometryColumns=GeometryColumns,
+ SpatialRefSys=SpatialRefSys,
+ )
@@ -0,0 +1,8 @@
+from django.db.backends.sqlite3.base import Database
+from django.contrib.gis.db.backend.adaptor import WKTAdaptor
+
+class SpatiaLiteAdaptor(WKTAdaptor):
+ "SQLite adaptor for geometry objects."
+ def __conform__(self, protocol):
+ if protocol is Database.PrepareProtocol:
+ return str(self)
@@ -0,0 +1,61 @@
+import os
+from django.conf import settings
+from django.core.management import call_command
+from django.db import connection
+
+def spatialite_init_file():
+ # SPATIALITE_SQL may be placed in settings to tell
+ # GeoDjango to use a specific user-supplied file.
+ return getattr(settings, 'SPATIALITE_SQL', 'init_spatialite-2.2.sql')
+
+def create_test_spatial_db(verbosity=1, autoclobber=False, interactive=False):
+ "Creates a spatial database based on the settings."
+
+ # Making sure we're using PostgreSQL and psycopg2
+ if settings.DATABASE_ENGINE != 'sqlite3':
+ raise Exception('SpatiaLite database creation only supported on sqlite3 platform.')
+
+ # Getting the test database name using the the SQLite backend's
+ # `_create_test_db`. Unless `TEST_DATABASE_NAME` is defined,
+ # it returns ":memory:".
+ db_name = connection.creation._create_test_db(verbosity, autoclobber)
+
+ # Closing out the current connection to the database set in
+ # originally in the settings. This makes it so `initialize_spatialite`
+ # function will be run on the connection for the _test_ database instead.
+ connection.close()
+
+ # Point to the new database
+ settings.DATABASE_NAME = db_name
+ connection.settings_dict["DATABASE_NAME"] = db_name
+ can_rollback = connection.creation._rollback_works()
+ settings.DATABASE_SUPPORTS_TRANSACTIONS = can_rollback
+ connection.settings_dict["DATABASE_SUPPORTS_TRANSACTIONS"] = can_rollback
+
+ # Finally, loading up the SpatiaLite SQL file.
+ load_spatialite_sql(db_name, verbosity=verbosity)
+
+ if verbosity >= 1:
+ print 'Creation of spatial database %s successful.' % db_name
+
+ # Syncing the database
+ call_command('syncdb', verbosity=verbosity, interactive=interactive)
+
+def load_spatialite_sql(db_name, verbosity=1):
+ """
+ This routine loads up the SpatiaLite SQL file.
+ """
+ # Getting the location of the SpatiaLite SQL file, and confirming
+ # it exists.
+ spatialite_sql = spatialite_init_file()
+ if not os.path.isfile(spatialite_sql):
+ raise Exception('Could not find the SpatiaLite initialization SQL file: %s' % spatialite_sql)
+
+ # Opening up the SpatiaLite SQL initialization file and executing
+ # as a script.
+ sql_fh = open(spatialite_sql, 'r')
+ try:
+ cur = connection.cursor()
+ cur.executescript(sql_fh.read())
+ finally:
+ sql_fh.close()
@@ -0,0 +1,81 @@
+from django.db.models.fields import Field # Django base Field class
+
+# Quotename & geographic quotename, respectively
+from django.db import connection
+qn = connection.ops.quote_name
+from django.contrib.gis.db.backend.util import gqn
+from django.contrib.gis.db.backend.spatialite.query import GEOM_FROM_TEXT, TRANSFORM
+
+class SpatiaLiteField(Field):
+ """
+ The backend-specific geographic field for SpatiaLite.
+ """
+
+ def _add_geom(self, style, db_table):
+ """
+ Constructs the addition of the geometry to the table using the
+ AddGeometryColumn(...) OpenGIS stored procedure.
+
+ Takes the style object (provides syntax highlighting) and the
+ database table as parameters.
+ """
+ sql = (style.SQL_KEYWORD('SELECT ') +
+ style.SQL_TABLE('AddGeometryColumn') + '(' +
+ style.SQL_TABLE(gqn(db_table)) + ', ' +
+ style.SQL_FIELD(gqn(self.column)) + ', ' +
+ style.SQL_FIELD(str(self.srid)) + ', ' +
+ style.SQL_COLTYPE(gqn(self.geom_type)) + ', ' +
+ style.SQL_KEYWORD(str(self.dim)) + ');')
+
+ return sql
+
+ def _geom_index(self, style, db_table):
+ "Creates a spatial index for this geometry field."
+ sql = (style.SQL_KEYWORD('SELECT ') +
+ style.SQL_TABLE('CreateSpatialIndex') + '(' +
+ style.SQL_TABLE(gqn(db_table)) + ', ' +
+ style.SQL_FIELD(gqn(self.column)) + ');')
+ return sql
+
+ def post_create_sql(self, style, db_table):
+ """
+ Returns SQL that will be executed after the model has been
+ created. Geometry columns must be added after creation with the
+ OpenGIS AddGeometryColumn() function.
+ """
+ # Getting the AddGeometryColumn() SQL necessary to create a OpenGIS
+ # geometry field.
+ post_sql = self._add_geom(style, db_table)
+
+ # If the user wants to index this data, then get the indexing SQL as well.
+ if self.spatial_index:
+ return (post_sql, self._geom_index(style, db_table))
+ else:
+ return (post_sql,)
+
+ def _post_delete_sql(self, style, db_table):
+ "Drops the geometry column."
+ sql = (style.SQL_KEYWORD('SELECT ') +
+ style.SQL_KEYWORD('DropGeometryColumn') + '(' +
+ style.SQL_TABLE(gqn(db_table)) + ', ' +
+ style.SQL_FIELD(gqn(self.column)) + ');')
+ return sql
+
+ def db_type(self):
+ """
+ SpatiaLite geometry columns are added by stored procedures;
+ should be None.
+ """
+ return None
+
+ def get_placeholder(self, value):
+ """
+ Provides a proper substitution value for Geometries that are not in the
+ SRID of the field. Specifically, this routine will substitute in the
+ Transform() and GeomFromText() function call(s).
+ """
+ if value is None or value.srid == self.srid:
+ return '%s(%%s,%s)' % (GEOM_FROM_TEXT, self.srid)
+ else:
+ # Adding Transform() to the SQL placeholder.
+ return '%s(%s(%%s,%s), %s)' % (TRANSFORM, GEOM_FROM_TEXT, value.srid, self.srid)
@@ -0,0 +1,53 @@
+"""
+ The GeometryColumns and SpatialRefSys models for the SpatiaLite backend.
+"""
+from django.db import models
+
+class GeometryColumns(models.Model):
+ """
+ The 'geometry_columns' table from SpatiaLite.
+ """
+ f_table_name = models.CharField(max_length=256)
+ f_geometry_column = models.CharField(max_length=256)
+ type = models.CharField(max_length=30)
+ coord_dimension = models.IntegerField()
+ srid = models.IntegerField(primary_key=True)
+ spatial_index_enabled = models.IntegerField()
+
+ class Meta:
+ db_table = 'geometry_columns'
+
+ @classmethod
+ def table_name_col(cls):
+ """
+ Returns the name of the metadata column used to store the
+ the feature table name.
+ """
+ return 'f_table_name'
+
+ @classmethod
+ def geom_col_name(cls):
+ """
+ Returns the name of the metadata column used to store the
+ the feature geometry column.
+ """
+ return 'f_geometry_column'
+
+ 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):
+ """
+ The 'spatial_ref_sys' table from SpatiaLite.
+ """
+ srid = models.IntegerField(primary_key=True)
+ auth_name = models.CharField(max_length=256)
+ auth_srid = models.IntegerField()
+ ref_sys_name = models.CharField(max_length=256)
+ proj4text = models.CharField(max_length=2048)
+
+ class Meta:
+ abstract = True
+ db_table = 'spatial_ref_sys'
Oops, something went wrong. Retry.

0 comments on commit 8eca2df

Please sign in to comment.