root/framework/server/trunk/docs/framework/spatial-databases.txt

Revision 3609, 31.6 kB (checked in by elemoine, 21 months ago)

remove the filter abstraction, p=aabt,me, r=aabt,me #590

Line 
1Using spatial databases with MapFish
2************************************
3
4Since release 2.0 MapFish uses the `SQLAlchemy <http://www.sqlalchemy.org/>`_ extension
5`GeoAlchemy <http://www.geoalchemy.org/>`_ that provides support for geospatial databases.
6By using GeoAlchemy, MapFish can also be used with all database systems supported by GeoAlchemy.
7
8The following document is going to describe how to set up the databases and how to
9use them.               
10
11Using PostgreSQL/PostGIS
12========================
13
14Installation
15------------
16
17If you followed the `installation guide <../installation.html>`_, then you already
18have installed PostgreSQL/PostGIS and you can skip this section. If not, run the
19following command from a terminal to install `PostgreSQL <http://www.postgresql.org/>`_,
20`PostGIS <http://www.postgis.org/>`_ and the required library `GEOS <http://trac.osgeo.org/geos/>`_::
21
22        $ sudo apt-get install libgeos-3.0.0 postgresql postgis postgresql-8.3-postgis
23
24Further information about installing PostGIS can be found in the `PostGIS documentation
25<http://postgis.refractions.net/docs/ch02.html>`_.
26
27Setting up a spatially-enabled database
28----------------------------------------
29
30Creating a spatially-enabled database is slightly different from creating an ordinary database. Run
31the following commands to create the database ``gis``::
32
33        sudo su postgres
34        createdb -E UNICODE gis
35        createlang plpgsql gis
36        psql -d gis -f /usr/share/postgresql-8.3-postgis/lwpostgis.sql
37        psql -d gis -f /usr/share/postgresql-8.3-postgis/spatial_ref_sys.sql
38        psql -d gis -c "SELECT postgis_full_version()"
39        exit
40
41The last query will inform you about the version of your PostGIS installation.
42
43In most cases you do not want to access your database from a web application as ``root``. The following
44commands create a user ``www-data`` and grant access rights to this user for the database ``gis``::
45
46        sudo su postgres
47        createuser -P www-data
48        psql gis
49        grant all on database gis to "www-data";
50        grant select on spatial_ref_sys to "www-data";
51        grant all on geometry_columns to "www-data";
52        \q
53        exit
54
55.. hint::
56
57        Depending on if your user should be allowed to create tables, to insert/update rows or just
58        to read, you may want to adjust the privileges (see the `PostgreSQL documentation
59        <http://www.postgresql.org/docs/8.3/interactive/sql-grant.html>`_ for further
60        information).
61
62Now it is time to create tables in your database. You can either let SQLAlchemy/GeoAlchemy do
63that for you (see :ref:`setup-app`) or you can manually create tables (see `Creating a Spatial Table
64<http://postgis.refractions.net/docs/ch04.html#Create_Spatial_Table>`_ in the PostGIS documentation).
65
66You can also use the tool `shp2pgsql <http://postgis.refractions.net/docs/ch04.html#shp2pgsql_usage>`_
67to create a table from a Shapefile. For example to create the table for this countries Shapefile
68(:download:`countries.zip <_static/countries.zip>`), you would have to do this::
69
70        unzip countries.zip
71        sudo su postgres
72        shp2pgsql -W utf8 -s 4326 countries.shp  countries | psql -d gis
73        exit
74
75And to check that everything is ok, we can query the row count. Additionally we need to grant
76the access to this table to user ``www-data`` in order to allow access from MapFish server in a web environment::
77
78        sudo su postgres
79        psql gis
80        \d
81        select count(1) from countries;
82        GRANT ALL ON countries TO "www-data";
83        \q
84        exit
85       
86Configuration
87--------------
88
89Once the database is set up, you only have to change the database connection string in the
90configuration file of your MapFish application. Open your configuration file, for example
91``development.ini``, and replace the line::
92
93    sqlalchemy.url = sqlite:///%(here)s/development.db
94
95by this one::
96
97    sqlalchemy.url = postgresql://www-data:www-data@localhost/gis
98
99Using MySQL
100============
101
102Installation
103------------
104
105On Debian-based systems, MySQL can be installed with::
106       
107        $ sudo apt-get install mysql-server mysql-client
108       
109.. hint::
110        More information about installing MySQL can be found in the `MySQL manual
111        <http://dev.mysql.com/doc/refman/5.5/en/installing.html>`_.
112       
113You will also have to install a Python driver for MySQL. Run the following command
114inside the virtual environment to install the library `mysql-python
115<http://pypi.python.org/pypi/MySQL-python/>`_::
116
117        (venv) $ easy_install mysql-python
118       
119
120Setting up a spatially-enabled database
121----------------------------------------
122
123For MySQL setting up a spatial database is the same as setting up an ordinary database::
124
125        mysql -u root -p
126        create database gis;
127        quit
128       
129.. note::
130        When creating a database, or directly when creating a table, you can specify the
131        `storage engine <http://dev.mysql.com/doc/refman/5.5/en/storage-engines.html>`_
132        (MyISAM/InnoDB/..) that should be used for the tables. Note that currently only
133        MyISAM uses a `R-tree <http://en.wikipedia.org/wiki/R-tree>`_ for spatial indexes that
134        optimize spatial queries (see `Optimizing Spatial Analysis
135        <http://dev.mysql.com/doc/refman/5.5/en/optimizing-spatial-analysis.html>`_ in the MySQL
136        manual).
137       
138To create a database user that can be used for accessing the database from a web
139application, execute the following statements::
140
141        mysql -u root -p
142        create user 'www-data' identified by 'www-data';
143        grant all on gis.* to 'www-data';
144        quit
145       
146Tables with geometry columns can be created like any other table using the `geometry type
147<http://dev.mysql.com/doc/refman/5.5/en/mysql-spatial-datatypes.html>`_ as column data type
148(see `Creating Spatial Columns <http://dev.mysql.com/doc/refman/5.5/en/creating-spatial-columns.html>`_
149in the MySQL manual), for example::
150
151        mysql -u root -p
152        use gis;
153        CREATE TABLE points (id INTEGER AUTO_INCREMENT,
154                name VARCHAR(40),
155                geom POINT NOT NULL,
156                SPATIAL INDEX(geom),
157                PRIMARY KEY(id));
158        quit
159
160.. note::
161        If you want to use a `spatial index <http://dev.mysql.com/doc/refman/5.5/en/creating-spatial-indexes.html>`_
162        for your geometry column, the column must be declared as ``NOT NULL``.
163
164Configuration
165--------------
166
167Set the database connection string in the configuration file of your MapFish application
168(for example ``development.ini``) by replacing the line::
169
170    sqlalchemy.url = sqlite:///%(here)s/development.db
171
172by this one::
173
174    sqlalchemy.url = mysql://www-data:www-data@localhost/gis
175
176Limitations
177------------
178
179*       MySQL does not support coordinate system transformations for geometries. All
180        your spatial data must be in the same spatial reference system.
181*       Not all methods of the *OpenGIS® Simple Features Specifications For SQL* are supported by MySQL,
182        for example ``distance()`` or ``buffer()`` are not part of the stable release. A list of these functions can be found
183        `here <http://dev.mysql.com/doc/refman/5.5/en/functions-that-create-new-geometries-from-existing-ones.html#spatial-operators>`_.
184        Other functions only operate on the minimum bounding rectangle (MBR) of the geometries, a list of
185        these functions can be found in `the MySQL manual <http://dev.mysql.com/doc/refman/5.5/en/functions-that-test-spatial-relationships-between-geometries.html>`_.
186        Because of that, features queried through the `MapFish Protocol <../protocol.html>`_
187        are also selected using the MBR.
188
189Using SQLite/Spatialite
190=======================
191
192Installation
193------------
194
195Installation of Spatialite
196"""""""""""""""""""""""""""""""""
197
198Spatialite requires the libraries `GEOS <http://trac.osgeo.org/geos/>`_ and `PROJ4
199<http://proj.osgeo.org/>`_, which in most cases you will have already installed
200together with PostGIS::
201       
202        $ sudo apt-get install libgeos-c1 proj
203       
204.. note::
205       
206        Spatialite expects libgeos 3.1.1, but it can also be used with any 3.0.x release. You just have to
207        create a symbolic link::
208       
209                sudo ln /usr/lib/libgeos-3.0.0.so /usr/lib/libgeos-3.1.1.so
210       
211Now download the precompiled Spatialite library ``libspatialite`` from the `Spatialite download page
212<http://www.gaia-gis.it/spatialite/binaries.html>`_ and unzip the archive to ``/usr/local/lib/libspatialite`` or
213into a folder of your convenience::
214
215        wget http://www.gaia-gis.it/spatialite/libspatialite-linux-x86-2.3.1.tar.gz
216        sudo tar -xvf libspatialite-linux-x86-2.3.1.tar.gz -C /usr/local/lib/
217        sudo mv /usr/local/lib/libspatialite-* /usr/local/lib/libspatialite
218
219.. hint::
220
221        On Ubuntu 9.10+ you can install the Spatialite library as package ``libspatialite2`` directly
222        from the repositories.
223
224.. note::
225       
226        The precompiled libraries for Spatialite only work on 32-bit systems, if you are using a 64-bit system
227        you will have to compile by yourself. To do so, download the source code for ``libspatialite-amalgamation``
228        from the `Spatialite website <http://www.gaia-gis.it/spatialite/sources.html>`_. Make sure that you
229        also have installed the package ``libgeos-dev``. Unzip the source archive and compile by using the following commands
230        (you may want to change the ``prefix`` path, the compiled library will be copied there)::
231       
232                ./configure --prefix=/home/c2c/libspatialite --with-geos-lib=/usr/lib --with-proj-lib=/usr/lib
233                make install
234               
235        If you are receiving the error message ``cannot find -lstdc++``, you may have to create a
236        symbolic link for this library::
237               
238                sudo ln /usr/lib/libstdc++.so.6 /usr/lib/libstdc++.so
239               
240        Further information about compiling ``libspatialite`` can be found here:
241        `How to build libspatialite <http://www.gaia-gis.it/spatialite/how_to_build_libspatialite.html>`_.
242               
243
244Installation of pysqlite2
245""""""""""""""""""""""""""
246
247Even though Python 2.5+ contains the SQLite driver `pysqlite2 <http://trac.edgewall.org/wiki/PySqlite>`_,
248you have to compile it by yourself. The Spatialite library is used in SQLite as extension, and by default
249loading external extensions is disabled in *pysqlite2*.
250
251To compile *pysqlite2* you will have to install the SQLite header files::
252
253        sudo apt-get install libsqlite3-dev
254
255Download the *pysqlite2* source code from `pysqlite2 - Downloads
256<http://code.google.com/p/pysqlite/downloads/list?can=3&q=*.tar.gz>`_ and unzip it into your
257MapFish virtual environment.
258
259Then open the file ``setup.cfg`` and comment out the line ``define=SQLITE_OMIT_LOAD_EXTENSION``::
260
261        [build_ext]
262        #define=
263        #include_dirs=/usr/local/include
264        #library_dirs=/usr/local/lib
265        libraries=sqlite3
266        #define=SQLITE_OMIT_LOAD_EXTENSION
267       
268Now you can compile and setup *pysqlite2* with::
269
270        (venv) $ python setup.py install
271       
272.. note::
273
274    If you are running into ``Segmentation Fault`` errors using this build, you can try to do
275    a ``static build``. This will download the latest SQLite3 amalgation file and link it
276    internally::
277
278        (venv) $ python setup.py build_static install
279
280Setting up a spatially-enabled database
281----------------------------------------
282
283Creating a database can be done using ``spatialite-gui`` or by using the CLI client ``spatialite``. In
284the following we will use ``spatialite``, but you can also use ``spatialite-gui`` to execute the commands.
285
286.. hint::
287
288        You can get ``spatialite-gui`` from `the Spatialite website <http://www.gaia-gis.it/spatialite/binaries.html>`_,
289        also take a look at the `Quickguide for spatialite-gui
290        <http://www.gaia-gis.it/spatialite/spatialite-gui-notes.pdf>`_ (PDF).
291
292First you have to download the package ``spatialite-tools`` from `Spatialite Downloads
293<http://www.gaia-gis.it/spatialite/binaries.html>`_ and
294the script ``init_spatialite-2.3.sql`` from `Spatialite Ressources
295<http://www.gaia-gis.it/spatialite/resources.html>`_. The scripts creates the ``geometry_columns`` and
296``spatial_ref_sys`` metadata tables and also inserts a collection of spatial reference systems.
297
298Start the Spatialite client by calling::
299
300        spatialite gis.sqlite
301
302This will create the file ``gis.sqlite``, if it does not exist already. Then execute the script
303``init_spatialite-2.3.sql``::
304
305        .read init_spatialite-2.3.sql ASCII
306       
307Now you can create a table with a geometry column.  This is done in two steps: First we create a plain SQLite
308table without the geometry column, and then we add the geometry column using the function
309`AddGeometryColumn() <http://www.gaia-gis.it/spatialite-2.3.0/spatialite-sql-2.3.0.html#p16>`_::
310
311        CREATE TABLE points (id INTEGER PRIMARY KEY AUTOINCREMENT, name VARCHAR(40));
312        SELECT AddGeometryColumn('points', 'geom', 4326, 'POINT', 2);
313
314.. hint::
315
316        You can also create a table from a Shapefile with ``.loadshp`` (see also
317        `Creating a new SpatiaLite db and populating it
318        <http://www.gaia-gis.it/spatialite/spatialite-tutorial-2.3.1.html#t3.2>`_)::
319               
320                .loadshp countries Countries utf-8
321               
322        And you can even execute queries directly on Shapefiles without copying the data into a table
323        (see also `Performing SQL queries directly on shapefiles
324        <http://www.gaia-gis.it/spatialite/spatialite-tutorial-2.3.1.html#t6>`_)::
325       
326                CREATE VIRTUAL TABLE virtual_countries USING VirtualShape('/home/c2c/data/countries', utf-8, 4326);
327                select count(*) from virtual_countries where
328                        MBRWithin(Geometry, GeomFromText('POLYGON((0 0, 40 0, 40 40, 0 40, 0 0))', 4326));
329                       
330        Currently only read operations are supported, but still ``virtual tables`` are a good option
331        to publish a Shapefile with MapFish.
332                       
333Configuration
334--------------
335
336When using ``spatialite-gui`` and ``spatialite`` the Spatialite library is automatically
337loaded as extension. But when connecting to a Spatialite database using a ordinary SQLite driver,
338you have to load the Spatialite library manually.
339
340In MapFish, database connections are managed by SQLAlchemy. Every time SQLAlchemy opens a new
341connection to a Spatalite database, the Spatialite library must be loaded. This can be done by
342setting up a `PoolListener <http://www.sqlalchemy.org/docs/reference/sqlalchemy/interfaces.html#sqlalchemy.interfaces.PoolListener>`_.
343
344Open the file ``model/__init__.py`` and modify the method ``init_model(engine)``, so that
345it looks like this::
346
347        # ...
348       
349        from sqlalchemy.dialects.sqlite.base import SQLiteDialect
350        from sqlalchemy.interfaces import PoolListener
351       
352        # ...
353
354        def init_model(engine):
355            sm = orm.sessionmaker(autoflush=True, autocommit=False, bind=engine)
356       
357            meta.engine = engine
358            meta.Session = orm.scoped_session(sm)
359           
360            if isinstance(engine.dialect, SQLiteDialect):
361                class SpatialiteConnectionListener(PoolListener):
362                    def connect(self, dbapi_con, con_record):
363                        dbapi_con.enable_load_extension(True)
364                        dbapi_con.execute("select load_extension('/usr/local/lib/libspatialite/lib/libspatialite.so')")
365                        dbapi_con.enable_load_extension(False)
366                       
367                engine.pool.add_listener(SpatialiteConnectionListener())
368               
369                # ...
370
371Now you just have to set the database connection string in the configuration file of your MapFish application
372(for example ``development.ini``) by replacing the line::
373
374    sqlalchemy.url = sqlite:///%(here)s/development.db
375
376by this one::
377
378    sqlalchemy.url = sqlite:////home/c2c/data/gis.sqlite
379
380.. hint::
381
382    The number of slashs to the right of ``sqlite:`` depends on if you are using a relative or
383    absolute path, see also `SQLite: Connect Strings
384    <http://www.sqlalchemy.org/docs/reference/dialects/sqlite.html#connect-strings>`_.
385
386Using Oracle
387=======================
388
389Installation
390-------------
391
392The Python driver `cx_Oracle <http://cx-oracle.sourceforge.net/>`_ requires an Oracle client or
393server installation. If your MapFish application is running on the same system as your
394Oracle database, you can skip the section :ref:`instant-client`.
395
396.. note::
397   
398    The installation of Oracle database server software is not covered in this tutorial,
399    please refer to the `Oracle Database Documentation <http://www.oracle.com/pls/db112/portal.portal_db?selected=11&frame=>`_.
400   
401    For guidance on installing Oracle on Debian based systems, take a look at these two tutorials:
402   
403    - `Installing Oracle 11gR2 on Ubuntu 9.10 <http://mikesmithers.wordpress.com/2010/03/14/installing-oracle-11gr2-on-ubuntu-9-10/>`_
404    - `Installing Oracle 11gR1 on Ubuntu 8.10 Intrepid Ibex <http://www.pythian.com/news/1355/installing-oracle-11gr1-on-ubuntu-810-intrepid-ibex/>`_
405   
406.. _instant-client:
407
408Installation of Oracle Instant Client
409""""""""""""""""""""""""""""""""""""""
410
411The easiest way to get an Oracle Client is installing *Oracle Instant Client*. Download the following
412two packages for your operating system from `Instant Client Downloads <http://www.oracle.com/technology/software/tech/oci/instantclient/index.html>`_:
413
414- Instant Client Package - Basic
415- Instant Client Package - SDK
416
417In the following we are using RPM files for an installation on a Debian based system.
418
419First install the required packages ``alien`` and ``libaio1``::
420
421    sudo apt-get install alien libaio1
422
423Install the two RPM packages::
424
425    sudo alien -i oracle-instantclient11.2-basic-11.2.0.1.0-1.i386.rpm
426    sudo alien -i oracle-instantclient11.2-devel-11.2.0.1.0-1.i386.rpm
427   
428To add the installed libraries to the system search path, create the file
429``/etc/ld.so.conf.d/oracle.conf`` and insert the path to your installation, for example::
430
431    /usr/lib/oracle/11.2/client/lib/   
432
433Then run ``ldconfig`` to update the library cache::
434
435    sudo ldconfig
436    ldconfig -p | grep oracle
437   
438The last command should print out the Oracle library files.
439
440Installation of cx_Oracle
441"""""""""""""""""""""""""
442
443Installer files for various operating systems and Oracle versions can be found on the `cx_Oracle website
444<http://cx-oracle.sourceforge.net/>`_. In the following we are building cx_Oracle from source. To do so, download
445the source code archive from `cx_Oracle - Files <http://sourceforge.net/projects/cx-oracle/files/>`_.
446
447Before building cx_Oracle three environment variables have to be set::
448
449    export ORACLE_HOME=/usr/lib/oracle/11.2/client/
450    export LD_LIBRARY_PATH=$ORACLE_HOME/lib
451    export PATH=$ORACLE_HOME/bin:$PATH
452   
453Then start the setup inside your virtual environment::
454
455    (venv) $ python setup.py install   
456   
457To test if the installation was succesfull, start a Python interpreter and try to
458import the cx_Oracle module::
459
460    (venv) $ python -i
461    >>> import cx_Oracle
462    >>>
463   
464.. hint::
465
466    If you are using `Buildout <http://www.buildout.org/>`_ you may want to take a look at
467    the recipe `gocept.cxoracle <http://pypi.python.org/pypi/gocept.cxoracle>`_, which automatically
468    installs cx_Oracle and Oracle Instant Client.
469
470Configuration
471-------------
472
473When using cx_Oracle inside MapFish, the environment variables ``ORACLE_HOME`` and ``LD_LIBRARY`` have to be set
474before the cx_Oracle module is used from SQLAlchemy. You can do this in the file ``config/environment.py`` before
475the SQLAlchemy ``engine`` is created from the configuration::
476
477     def load_environment(global_conf, app_conf):
478     
479        # ...
480       
481        # Set the evironment variables required for cx_Oracle
482        os.environ['ORACLE_HOME'] = '/usr/lib/oracle/11.2/client/'
483        os.environ['LD_LIBRARY'] = '/usr/lib/oracle/11.2/client/lib'
484       
485        # Setup the SQLAlchemy database engine
486        engine = engine_from_config(config, 'sqlalchemy.')
487        init_model(engine)
488     
489Then set the database connection string in your Pylons configuration file ``development.ini``::
490
491    sqlalchemy.url = oracle://www-data:www-data@localhost:1521/gis   
492
493Notes about the Oracle dimension information array
494--------------------------------------------------
495
496Oracle requires a *Dimension Information Array (DIMINFO)* in its geometry metadata table
497for every spatial column (see `Oracle® Spatial User's Guide and Reference: Geometry Metadata Views
498<http://download.oracle.com/docs/cd/B12037_01/appdev.101/b10826/sdo_objrelschema.htm#i1001937>`_).
499
500If you are creating your tables with SQLAlchemy/GeoAlchemy (see :ref:`setup-app`), you will have to
501specify a DIMINFO in your model files. GeoAlchemy then will make an entry in the view
502``USER_SDO_GEOM_METADATA`` (see also `Oracle® Spatial User's Guide and Reference: DIMINFO
503<http://download.oracle.com/docs/cd/B12037_01/appdev.101/b10826/sdo_objrelschema.htm#i1010905>`_).
504
505Example definition (``model/places.py``)::
506
507    from sqlalchemy import Column, types
508    from sqlalchemy.schema import Sequence
509   
510    from geoalchemy import GeometryColumn, Point, GeometryDDL
511   
512    from mapfish.sqlalchemygeom import GeometryTableMixIn
513    from mapfishsample.model.meta import engine, Base
514   
515    diminfo = "MDSYS.SDO_DIM_ARRAY("\
516                "MDSYS.SDO_DIM_ELEMENT('LONGITUDE', -180, 180, 0.000000005),"\
517                "MDSYS.SDO_DIM_ELEMENT('LATITUDE', -90, 90, 0.000000005)"\
518                ")"
519   
520    class Place(Base, GeometryTableMixIn):
521        __tablename__ = 'places'
522        id = Column(types.Integer, Sequence('place_id_seq'), primary_key=True)
523        name = Column(types.String(40))
524       
525        the_geom = GeometryColumn(Point(dimension=2, srid=4326, diminfo=diminfo))
526   
527    GeometryDDL(Place.__table__)
528
529For Oracle MapFish in general uses the operator ``SDO_WITHIN_DISTANCE`` for spatial filter queries.
530If the filter geometry (Lat/Lon, BBox or arbitrary geometry) uses a different SRID, the geometry column
531has to be reprojected to this SRID. In this case the Oracle function ``SDO_GEOM.WITHIN_DISTANCE`` has to be
532used which either requires dimension information arrays or a tolerance value. These parameters have to be
533set on custom filters for the method ``index()`` inside the controller classes.
534
535Example (``controllers/places.py``)::
536
537    class PlacesController(BaseController):
538        # ..
539   
540        def index(self, format='json'):
541            """GET /: return all features."""
542            filter = create_default_filter(request, Place, additional_params={'tol': '0.005'})
543            return self.protocol.read(request, filter=filter)
544        # ..
545
546The tolerance value will be passed to ``SDO_GEOM.WITHIN_DISTANCE``. Alternatively you can set
547a DIMINFO for the reprojected geometry column and the filter query using the keywords ``dim1`` and
548``dim2``.
549
550Note that this tolerance is not the one used in the `MapFish Protocol
551<../protocol.html>`_ to specify a distance in which features should be queried.
552
553.. hint::
554
555    More information about using Oracle can be found in the `GeoAlchemy documentation
556    <http://geoalchemy.org/usagenotes.html#notes-for-oracle>`_.
557
558Notes about using a tolerance value for MapFish Web Services in Oracle
559----------------------------------------------------------------------
560
561Requests to a MapFish web service can contain a tolerance parameter, which specifies within which distance
562features should be queried. Usually the unit of this value is the unit associated with the coordinate system
563in use (for example degree for ``EPSG:4326``). But for geodetic coordinate systems (like ``EPSG:4326``)
564Oracle uses meter as unit. You have to keep this in mind when developing applications for Oracle.
565
566If you want to use a different unit, you can set it as parameter in your controller files. This
567parameter is passed to the database function call without further checks.
568
569Example (``controllers/places.py``)::
570
571        # ..
572   
573        def index(self, format='json'):
574            """GET /: return all features."""
575            filter = create_default_filter(request, Place, additional_params={'params': 'unit=KM'})
576            return self.protocol.read(request, filter=filter)
577
578        # ..
579
580Valid units are listed in the view ``SDO_DIST_UNITS``.
581
582Sperical Mercator (900913)
583===========================
584
585For many map tiles (including OpenStreetMap and Google Maps) `Sperical Mercator
586<http://docs.openlayers.org/library/spherical_mercator.html>`_ (EPSG: 900913) is used as projection. For Spatialite and PostGIS
587in releases prior to PostGIS 1.4, *Sperical Mercator* is not supported by default and you will receive
588an error message like the following, when you try to work with geometries using *Sperical Mercator* as
589spatial reference system (SRS)::
590
591        (InternalError) AddToPROJ4SRSCache: Cannot find SRID (900913) in spatial_ref_sys [..]
592               
593To enable support for *Sperical Mercator*, you first will have to update the library ``proj``, because on Ubuntu ``proj`` comes without datum shifting
594files which are required for transformations with the *Sperical Mercator* projection. Run the following commands
595to update your installation (see also `Notes on proj
596<http://docs.djangoproject.com/en/dev/ref/contrib/gis/install/#id43>`_)::
597
598        wget http://download.osgeo.org/proj/proj-datumgrid-1.4.tar.gz
599        mkdir nad
600        cd nad
601        tar xzf ../proj-datumgrid-1.4.tar.gz
602        nad2bin null < null.lla
603        sudo cp null /usr/share/proj
604
605Then for **PostGIS** run the following statements to insert the reference definition in PostGIS' ``spatial_ref_sys`` table::
606
607        sudo su postgres
608        psql gis
609        INSERT INTO spatial_ref_sys (srid, auth_name, auth_srid, srtext, proj4text) VALUES (900913, 'EPSG', 900913, 'PROJCS["unnamed",GEOGCS["unnamed ellipse",DATUM["unknown",SPHEROID["unnamed",6378137,0]], PRIMEM["Greenwich",0],UNIT["degree",0.0174532925199433]], PROJECTION["Mercator_2SP"],PARAMETER["standard_parallel_1",0],PARAMETER["central_meridian",0],PARAMETER["false_easting",0], PARAMETER["false_northing",0],UNIT["Meter",1],EXTENSION["PROJ4","+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +wktext  +no_defs"]]', '+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +wktext  +no_defs');
610        \q
611        exit
612
613The same can be done for **Spatialite**::
614
615        spatialite gis.sqlite
616        INSERT INTO spatial_ref_sys (srid, auth_name, auth_srid, ref_sys_name, proj4text) VALUES (900913, 'EPSG', 900913, 'Google Sperical Mercator', '+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +wktext  +no_defs');
617        .quit
618       
619Table mapping
620=============
621
622Declarative and non-declarative mapping
623---------------------------------------
624
625With SQLAlchemy table mappings can be configured either by defining a table and a class
626separetly (non-declarative) or by doing this at once (declarative). MapFish supports both ways.
627The following two examples show the mapping for a table ``spots``, one time as non-declarative and
628one time as declarative mapping.
629
630**Non-declarative mapping**:
631
632.. code-block:: python
633
634        from sqlalchemy import Column, Table, Integer, Numeric
635        from sqlalchemy.orm import mapper
636       
637        from geoalchemy import (Geometry, GeometryColumn,
638        GeometryDDL, GeometryExtensionColumn)
639       
640        from mapfish.sqlalchemygeom import GeometryTableMixIn 
641       
642        from mapfishsample.model.meta import metadata
643       
644        # table definition
645        spots_table = Table('spots', metadata,
646                Column('spot_id', Integer, primary_key=True),
647                Column('spot_height', Numeric(asdecimal=False)),
648                GeometryExtensionColumn('spot_location', Geometry(2)))
649
650        # class definition
651        class Spot(GeometryTableMixIn):
652            __table__ = spots_table
653           
654            def __init__(self, spot_id=None, spot_height=None, spot_location=None):
655                self.spot_id = spot_id
656                self.spot_height = spot_height
657                self.spot_location = spot_location
658       
659        # set up the mapping between table and class
660        mapper(Spot, spots_table, properties={
661                    'spot_location': GeometryColumn(spots_table.c.spot_location,
662                                                    comparator=PGComparator)})
663       
664        # register table for DDL extension, so that it can be created from SQLAlchemy
665        GeometryDDL(spots_table)
666       
667       
668**Declarative mapping**:
669
670.. code-block:: python
671
672        from sqlalchemy import Column, types
673
674        from geoalchemy import GeometryColumn, Geometry
675       
676        from mapfish.sqlalchemygeom import GeometryTableMixIn
677        from mapfishsample.model.meta import engine, Base
678       
679        class Spot(Base, GeometryTableMixIn):
680            __tablename__ = 'spots'
681       
682            spot_id = Column(Integer, primary_key=True)
683            spot_height = Column(Numeric(asdecimal=False))
684            spot_location = GeometryColumn(Point(2), comparator=PGComparator)
685       
686        GeometryDDL(Spot.__table__)
687
688.. hint::
689
690        See also `Creating Table, Class and Mapper All at Once Declaratively
691        <http://www.sqlalchemy.org/docs/ormtutorial.html#creating-table-class-and-mapper-all-at-once-declaratively>`_.
692
693Geometry column properties
694--------------------------
695
696When using ``paster mf-layer`` or ``paster mf-model``, MapFish creates a default configuration
697for the geometry column of your table. You may want to customize this configuration to your needs.
698
699**Example configuration for** ``model/spots.py``:
700
701.. code-block:: python
702
703        # [..]
704        spot_location = GeometryColumn(
705                Point(dimension=2, srid=4326, spatial_index=True),
706                comparator=PGComparator,
707                nullable=False)
708        # [..]
709
710``dimension=2``
711
712The dimension of the geometry (default: 2).
713
714``srid=4326``
715
716The spatial reference system (SRS) of the geometry column as EPSG code (default: 4326).
717
718``spatial_index=True``
719
720Indicates if a *spatial index* is created for the geometry column (default: True).
721
722.. hint::
723
724        Spatialite does not automatically make use of the spatial index when executing queries, you
725        explicitly have to access the spatial index in your queries, see  `Spatial Index: using SQLite's R*Tree
726        <http://www.gaia-gis.it/spatialite/spatialite-tutorial-2.3.1.html#t8>`_.
727
728``comparator=PGComparator``
729
730You only have to set this option, when you want to use a database specific function (like `AsKML` in PostGIS) on
731a geometry column in a SQLAlchemy query (for example ``session.query(Spot).filter(Spot.geom.kml == '..'``). Following
732comparators are available:
733
734* PostGIS: *geoalchemy.postgis.PGComparator*
735* MySQL: *geoalchemy.mysql.MySQLComparator*
736* Spatialite: *geoalchemy.spatialite.SQLiteComparator*
737* Oracle: *geoalchemy.oracle.OracleComparator*
738
739``nullable=False``
740
741Indicates if ``null`` values can be inserted into the geometry column (default: True).
742
743.. note::
744
745        When using MySQL with a spatial index, the parameter ``nullable`` is ignored, because MySQL
746        requires a ``NOT NULL`` constraint for spatial indexed columns.
747
748
749.. _setup-app:
750
751Using 'paster setup-app' to create your database tables
752==========================================================
753
754When setting up a Pylons application, you often have to create database tables to run the
755application. SQLAlchemy/GeoAlchemy can take over that task for you, so that all your tables
756are created just by calling ``paster setup-app config.ini``.
757
758The following steps describe how to configure your application.
759
760#.      **Table mapping**
761       
762        All columns that you want to be created for a table, must be enlisted in the table
763        definition.
764       
765        *Example:* ``model/Point.py``
766
767        .. code-block:: python
768               
769                from sqlalchemy import Column, types
770               
771                from geoalchemy import GeometryColumn, Point, GeometryDDL
772               
773                from mapfish.sqlalchemygeom import GeometryTableMixIn
774                from mapfishsample.model.meta import metadata, Base
775               
776               
777                class Point(Base, GeometryTableMixIn):
778                    __tablename__ = 'points'
779                   
780                    id = Column(types.Integer, primary_key=True)
781                    name = Column(types.String(30), default = 'foo')
782                    the_geom = GeometryColumn(Point(dimension=2, srid=4326))
783               
784                GeometryDDL(Point.__table__)
785               
786        Note the last line ``GeometryDDL(Point.__table__)``, this makes sure that
787        GeoAlchemy creates the geometry field of the table.
788
789#.      **websetup.py**
790       
791        When calling ``paster setup-app config.ini``, the method ``setup_app()`` inside the
792        file ``[your_app]/websetup.py`` is executed. By default the method ``setup_app()``
793        already contains the command ``metadata.create_all()`` that creates the tables. You
794        just have to import your model classes.
795       
796        *Example:*  ``websetup.py``
797       
798        .. code-block:: python
799       
800                """Setup the MapFishSample application"""
801                import logging
802               
803                from mapfishsample.config.environment import load_environment
804                from mapfishsample.model import meta
805               
806                # Import the model classes you want to create the tables for
807                from mapfishsample.model import points
808               
809                log = logging.getLogger(__name__)
810               
811                def setup_app(command, conf, vars):
812                    """Place any commands to setup mapfishsample here"""
813                    load_environment(conf.global_conf, conf.local_conf)
814               
815                    # Create the tables if they don't already exist
816                    meta.metadata.create_all(bind=meta.engine)
817
818#. **paster setup-app**
819       
820        Finally to setup your application, run the following command inside the virtual environment:
821       
822        .. code-block:: bash
823       
824                (venv) $ paster setup-app [your_config].ini
Note: See TracBrowser for help on using the browser.