Commit 8eca2df3 authored by Justin Bronn's avatar Justin Bronn
Browse files

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

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
parent 5ff1703f
Loading
Loading
Loading
Loading
+59 −0
Original line number Diff line number Diff line
__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,
                                    )
+8 −0
Original line number Diff line number Diff line
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)
+61 −0
Original line number Diff line number Diff line
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()
+81 −0
Original line number Diff line number Diff line
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)
+53 −0
Original line number Diff line number Diff line
"""
 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'
Loading