@@ -7,47 +7,37 @@ GeoAlchemy 2's main target is PostGIS. But GeoAlchemy 2 also supports SpatiaLite
77extension to SQLite. This tutorial describes how to use GeoAlchemy 2 with SpatiaLite. It's based on
88the :ref: `orm_tutorial `, which you may want to read first.
99
10+ .. _spatialite_connect :
11+
1012Connect to the DB
1113-----------------
1214
1315Just like when using PostGIS connecting to a SpatiaLite database requires an ``Engine ``. This is how
1416you create one for SpatiaLite::
1517
18+ >>> from geoalchemy2 import load_spatialite
1619 >>> from sqlalchemy import create_engine
1720 >>> from sqlalchemy.event import listen
1821 >>>
19- >>> def load_spatialite(dbapi_conn, connection_record):
20- ... dbapi_conn.enable_load_extension(True)
21- ... dbapi_conn.load_extension('/usr/lib/x86_64-linux-gnu/mod_spatialite.so')
22- ...
23- >>>
24- >>> engine = create_engine('sqlite:///gis.db', echo=True)
25- >>> listen(engine, 'connect', load_spatialite)
22+ >>> engine = create_engine("sqlite:///gis.db", echo=True)
23+ >>> listen(engine, "connect", load_spatialite)
2624
2725The call to ``create_engine `` creates an engine bound to the database file ``gis.db ``. After that
2826a ``connect `` listener is registered on the engine. The listener is responsible for loading the
29- SpatiaLite extension, which is a necessary operation for using SpatiaLite through SQL.
27+ SpatiaLite extension, which is a necessary operation for using SpatiaLite through SQL. The path to
28+ the ``mod_spatialite `` file should be stored in the ``SPATIALITE_LIBRARY_PATH `` environment
29+ variable before using the ``load_spatialite `` function.
3030
3131At this point you can test that you are able to connect to the database::
3232
3333 >> conn = engine.connect()
34- 2018-05-30 17:12:02,675 INFO sqlalchemy.engine.base.Engine SELECT CAST('test plain returns' AS VARCHAR(60)) AS anon_1
35- 2018-05-30 17:12:02,676 INFO sqlalchemy.engine.base.Engine ()
36- 2018-05-30 17:12:02,676 INFO sqlalchemy.engine.base.Engine SELECT CAST('test unicode returns' AS VARCHAR(60)) AS anon_1
37- 2018-05-30 17:12:02,676 INFO sqlalchemy.engine.base.Engine ()
38-
39- You can also check that the ``gis.db `` SQLite database file was created on the file system.
40-
41- One additional step is required for using SpatiaLite: create the ``geometry_columns `` and
42- ``spatial_ref_sys `` metadata tables. This is done by calling SpatiaLite's ``InitSpatialMetaData ``
43- function::
4434
45- >>> from sqlalchemy.sql import select, func
46- >>>
47- >>> conn.execute(select([func.InitSpatialMetaData()]))
35+ Note that this call will internally call the ``load_spatialite `` function, which can take some time
36+ to execute on a new database because it actually calls the ``InitSpatialMetaData `` function from
37+ SpatiaLite.
38+ Then you can also check that the ``gis.db `` SQLite database file was created on the file system.
4839
49- Note that this operation may take some time the first time it is executed for a database. When
50- ``InitSpatialMetaData `` is executed again it will report an error::
40+ Note that when ``InitSpatialMetaData `` is executed again it will report an error::
5141
5242 InitSpatiaMetaData() error:"table spatial_ref_sys already exists"
5343
@@ -61,9 +51,7 @@ Declare a Mapping
6151-----------------
6252
6353Now that we have a working connection we can go ahead and create a mapping between
64- a Python class and a database table.
65-
66- ::
54+ a Python class and a database table::
6755
6856 >>> from sqlalchemy.ext.declarative import declarative_base
6957 >>> from sqlalchemy import Column, Integer, String
@@ -72,17 +60,14 @@ a Python class and a database table.
7260 >>> Base = declarative_base()
7361 >>>
7462 >>> class Lake(Base):
75- ... __tablename__ = ' lake'
63+ ... __tablename__ = " lake"
7664 ... id = Column(Integer, primary_key=True)
7765 ... name = Column(String)
78- ... geom = Column(Geometry(geometry_type=' POLYGON', management=True ))
66+ ... geom = Column(Geometry(geometry_type=" POLYGON" ))
7967
80- This basically works in the way as with PostGIS. The difference is the ``management ``
81- argument that must be set to ``True ``.
82-
83- Setting ``management `` to ``True `` indicates that the ``AddGeometryColumn `` and
84- ``DiscardGeometryColumn `` management functions will be used for the creation and removal of the
85- geometry column. This is required with SpatiaLite.
68+ From the user point of view this works in the same way as with PostGIS. The difference is that
69+ internally the ``RecoverGeometryColumn `` and ``DiscardGeometryColumn `` management functions will be
70+ used for the creation and removal of the geometry column.
8671
8772Create the Table in the Database
8873--------------------------------
@@ -117,25 +102,25 @@ do it using GeoAlchemy 2 with PostGIS.
117102
118103::
119104
120- >>> lake = Lake(name=' Majeur' , geom=' POLYGON((0 0,1 0,1 1,0 1,0 0))' )
105+ >>> lake = Lake(name=" Majeur" , geom=" POLYGON((0 0,1 0,1 1,0 1,0 0))" )
121106 >>> session.add(lake)
122107 >>> session.commit()
123108
124109We can now query the database for ``Majeur ``::
125110
126- >>> our_lake = session.query(Lake).filter_by(name=' Majeur' ).first()
111+ >>> our_lake = session.query(Lake).filter_by(name=" Majeur" ).first()
127112 >>> our_lake.name
128- u' Majeur'
113+ u" Majeur"
129114 >>> our_lake.geom
130- <WKBElement at 0x9af594c; ' 0103000000010000000500000000000000000000000000000000000000000000000000f03f0000000000000000000000000000f03f000000000000f03f0000000000000000000000000000f03f00000000000000000000000000000000' >
115+ <WKBElement at 0x9af594c; " 0103000000010000000500000000000000000000000000000000000000000000000000f03f0000000000000000000000000000f03f000000000000f03f0000000000000000000000000000f03f00000000000000000000000000000000" >
131116 >>> our_lake.id
132117 1
133118
134119Let's add more lakes::
135120
136121 >>> session.add_all([
137- ... Lake(name=' Garde' , geom=' POLYGON((1 0,3 0,3 2,1 2,1 0))' ),
138- ... Lake(name=' Orta' , geom=' POLYGON((3 0,6 0,6 3,3 3,3 0))' )
122+ ... Lake(name=" Garde" , geom=" POLYGON((1 0,3 0,3 2,1 2,1 0))" ),
123+ ... Lake(name=" Orta" , geom=" POLYGON((3 0,6 0,6 3,3 3,3 0))" )
139124 ... ])
140125 >>> session.commit()
141126
@@ -156,7 +141,7 @@ Now a spatial query::
156141
157142 >>> from geolachemy2 import WKTElement
158143 >>> query = session.query(Lake).filter(
159- ... func.ST_Contains(Lake.geom, WKTElement(' POINT(4 1)' )))
144+ ... func.ST_Contains(Lake.geom, WKTElement(" POINT(4 1)" )))
160145 ...
161146 >>> for lake in query:
162147 ... print(lake.name)
@@ -166,7 +151,7 @@ Now a spatial query::
166151Here's another spatial query, using ``ST_Intersects `` this time::
167152
168153 >>> query = session.query(Lake).filter(
169- ... Lake.geom.ST_Intersects(WKTElement(' LINESTRING(2 1,4 1)' )))
154+ ... Lake.geom.ST_Intersects(WKTElement(" LINESTRING(2 1,4 1)" )))
170155 ...
171156 >>> for lake in query:
172157 ... print(lake.name)
@@ -176,8 +161,8 @@ Here's another spatial query, using ``ST_Intersects`` this time::
176161
177162We can also apply relationship functions to :class: `geoalchemy2.elements.WKBElement `. For example::
178163
179- >>> lake = session.query(Lake).filter_by(name=' Garde' ).one()
180- >>> print(session.scalar(lake.geom.ST_Intersects(WKTElement(' LINESTRING(2 1,4 1)' ))))
164+ >>> lake = session.query(Lake).filter_by(name=" Garde" ).one()
165+ >>> print(session.scalar(lake.geom.ST_Intersects(WKTElement(" LINESTRING(2 1,4 1)" ))))
181166 1
182167
183168``session.scalar `` allows executing a clause and returning a scalar value (an integer value in this
@@ -191,22 +176,59 @@ Function mapping
191176
192177Several functions have different names in SpatiaLite than in PostGIS. The GeoAlchemy 2 package is
193178based on the PostGIS syntax but it is possible to automatically translate the queries into
194- SpatiaLite ones. For example, the function `ST_GeomFromEWKT ` is automatically translated into
195- `GeomFromEWKT `. Unfortunately, only a few functions are automatically mapped (the ones internally
196- used by GeoAlchemy 2). Nevertheless, it is possible to define new mappings in order to translate
197- the queries automatically. Here is an example to register a mapping for the `ST_Buffer ` function::
179+ SpatiaLite ones. For example, the function ``ST_GeomFromEWKT `` is automatically translated into
180+ ``GeomFromEWKT ``. Unfortunately, only a few functions are automatically mapped (mainly the ones
181+ internally used by GeoAlchemy 2). Nevertheless, it is possible to define new mappings in order to
182+ translate the queries automatically. Here is an example to register a mapping for the ``ST_Buffer ``
183+ function::
198184
199185 >>> geoalchemy2.functions.register_sqlite_mapping(
200- ... {' ST_Buffer': ' Buffer' }
186+ ... {" ST_Buffer": " Buffer" }
201187 ... )
202188
203- After this command, all `ST_Buffer ` calls in the queries will be translated to `Buffer ` calls when
204- the query is executed on a SQLite DB.
189+ After this command, all ``ST_Buffer `` calls in the queries will be translated to ``Buffer `` calls
190+ when the query is executed on a SQLite DB.
191+
192+ A more complex example is provided for when the PostGIS function should be mapped depending on
193+ the given parameters. For example, the ``ST_Buffer `` function can actually be translate into either
194+ the ``Buffer `` function or the ``SingleSidedBuffer `` function (only when ``side=right `` or ``side=left ``
195+ is passed). See the :ref: `sphx_glr_gallery_test_specific_compilation.py ` example in the gallery.
196+
197+ GeoPackage format
198+ -----------------
199+
200+ Starting from the version ``4.2 `` of Spatialite, it is possible to use GeoPackage files as DB
201+ containers. GeoAlchemy 2 is able to handle most of the GeoPackage features automatically if the
202+ GeoPackage dialect is used (i.e. the DB URL starts with ``gpkg:/// ``) and the SpatiaLite extension
203+ is loaded. Usually, this extension should be loaded using the ``load_spatialite_gpkg `` listener::
204+
205+ >>> from geoalchemy2 import load_spatialite_gpkg
206+ >>> from sqlalchemy import create_engine
207+ >>> from sqlalchemy.event import listen
208+ >>>
209+ >>> engine = create_engine("gpkg:///gis.gpkg", echo=True)
210+ >>> listen(engine, "connect", load_spatialite_gpkg)
211+
212+ When using the ``load_spatialite_gpkg `` listener on a DB recognized as a GeoPackage, specific
213+ processes are activated:
214+
215+ * the base tables are created if they are missing,
216+ * the ``Amphibious `` mode is enabled using the ``EnableGpkgAmphibiousMode `` function,
217+ * the ``VirtualGPKG `` wrapper is activated using the ``AutoGpkgStart `` function.
218+
219+ After that it should be possible to use a GeoPackage the same way as a standard SpatiaLite
220+ database. GeoAlchemy 2 should be able to handle the following features in a transparent way for the
221+ user:
222+
223+ * create/drop spatial tables,
224+ * automatically create/drop spatial indexes if required,
225+ * reflect spatial tables,
226+ * use spatial functions on inserted geometries.
227+
228+ .. Note ::
205229
206- A more complex example is provided for when the `PostGIS ` function should be mapped depending on
207- the given parameters. For example, the `ST_Buffer ` function can actually be translate into either
208- the `Buffer ` function or the `SingleSidedBuffer ` function (only when `side=right ` or `side=left ` is
209- passed). See the :ref: `sphx_glr_gallery_test_specific_compilation.py ` example in the gallery.
230+ If you want to use the ``ST_Transform `` function you should call the
231+ :func: `geoalchemy2.admin.dialects.geopackage.create_spatial_ref_sys_view ` first.
210232
211233Further Reference
212234-----------------
0 commit comments