813 lines
28 KiB
Plaintext
813 lines
28 KiB
Plaintext
==================
|
|
GeoDjango Tutorial
|
|
==================
|
|
|
|
Introduction
|
|
============
|
|
|
|
GeoDjango is an included contrib module for Django that turns it into a
|
|
world-class geographic Web framework. GeoDjango strives to make it as simple
|
|
as possible to create geographic Web applications, like location-based services.
|
|
Its features include:
|
|
|
|
* Django model fields for `OGC`_ geometries.
|
|
* Extensions to Django's ORM for querying and manipulating spatial data.
|
|
* Loosely-coupled, high-level Python interfaces for GIS geometry operations and
|
|
data formats.
|
|
* Editing geometry fields from the admin.
|
|
|
|
This tutorial assumes familiarity with Django; thus, if you're brand new to
|
|
Django, please read through the :doc:`regular tutorial </intro/tutorial01>` to
|
|
familiarize yourself with Django first.
|
|
|
|
.. note::
|
|
|
|
GeoDjango has additional requirements beyond what Django requires --
|
|
please consult the :doc:`installation documentation <install/index>`
|
|
for more details.
|
|
|
|
This tutorial will guide you through the creation of a geographic web
|
|
application for viewing the `world borders`_. [#]_ Some of the code
|
|
used in this tutorial is taken from and/or inspired by the `GeoDjango
|
|
basic apps`_ project. [#]_
|
|
|
|
.. note::
|
|
|
|
Proceed through the tutorial sections sequentially for step-by-step
|
|
instructions.
|
|
|
|
.. _OGC: http://www.opengeospatial.org/
|
|
.. _world borders: http://thematicmapping.org/downloads/world_borders.php
|
|
.. _GeoDjango basic apps: http://code.google.com/p/geodjango-basic-apps/
|
|
|
|
Setting Up
|
|
==========
|
|
|
|
Create a Spatial Database
|
|
-------------------------
|
|
|
|
.. note::
|
|
|
|
MySQL and Oracle users can skip this section because spatial types
|
|
are already built into the database.
|
|
|
|
First, create a spatial database for your project.
|
|
|
|
If you are using PostGIS, create the database from the :ref:`spatial database
|
|
template <spatialdb_template>`:
|
|
|
|
.. code-block:: bash
|
|
|
|
$ createdb -T template_postgis geodjango
|
|
|
|
.. note::
|
|
|
|
This command must be issued by a database user with enough privileges to
|
|
create a database. To create a user with ``CREATE DATABASE`` privileges in
|
|
PostgreSQL, use the following commands:
|
|
|
|
.. code-block:: bash
|
|
|
|
$ sudo su - postgres
|
|
$ createuser --createdb geo
|
|
$ exit
|
|
|
|
Replace ``geo`` with your Postgres database user's username.
|
|
(In PostgreSQL, this user will also be an OS-level user.)
|
|
|
|
If you are using SQLite and SpatiaLite, consult the instructions on how
|
|
to create a :ref:`SpatiaLite database <create_spatialite_db>`.
|
|
|
|
Create a New Project
|
|
------------------------
|
|
|
|
Use the standard ``django-admin`` script to create a project called
|
|
``geodjango``:
|
|
|
|
.. code-block:: bash
|
|
|
|
$ django-admin startproject geodjango
|
|
|
|
This will initialize a new project. Now, create a ``world`` Django application
|
|
within the ``geodjango`` project:
|
|
|
|
.. code-block:: bash
|
|
|
|
$ cd geodjango
|
|
$ python manage.py startapp world
|
|
|
|
Configure ``settings.py``
|
|
-------------------------
|
|
|
|
The ``geodjango`` project settings are stored in the ``geodjango/settings.py``
|
|
file. Edit the database connection settings to match your setup::
|
|
|
|
DATABASES = {
|
|
'default': {
|
|
'ENGINE': 'django.contrib.gis.db.backends.postgis',
|
|
'NAME': 'geodjango',
|
|
'USER': 'geo',
|
|
}
|
|
}
|
|
|
|
In addition, modify the :setting:`INSTALLED_APPS` setting to include
|
|
:mod:`django.contrib.admin`, :mod:`django.contrib.gis`,
|
|
and ``world`` (your newly created application)::
|
|
|
|
INSTALLED_APPS = (
|
|
'django.contrib.admin',
|
|
'django.contrib.auth',
|
|
'django.contrib.contenttypes',
|
|
'django.contrib.sessions',
|
|
'django.contrib.messages',
|
|
'django.contrib.staticfiles',
|
|
'django.contrib.gis',
|
|
'world'
|
|
)
|
|
|
|
Geographic Data
|
|
===============
|
|
|
|
.. _worldborders:
|
|
|
|
World Borders
|
|
-------------
|
|
|
|
The world borders data is available in this `zip file`__. Create a ``data``
|
|
directory in the ``world`` application, download the world borders data, and
|
|
unzip. On GNU/Linux platforms, use the following commands:
|
|
|
|
.. code-block:: bash
|
|
|
|
$ mkdir world/data
|
|
$ cd world/data
|
|
$ wget http://thematicmapping.org/downloads/TM_WORLD_BORDERS-0.3.zip
|
|
$ unzip TM_WORLD_BORDERS-0.3.zip
|
|
$ cd ../..
|
|
|
|
The world borders ZIP file contains a set of data files collectively known as
|
|
an `ESRI Shapefile`__, one of the most popular geospatial data formats. When
|
|
unzipped, the world borders dataset includes files with the following
|
|
extensions:
|
|
|
|
* ``.shp``: Holds the vector data for the world borders geometries.
|
|
* ``.shx``: Spatial index file for geometries stored in the ``.shp``.
|
|
* ``.dbf``: Database file for holding non-geometric attribute data
|
|
(e.g., integer and character fields).
|
|
* ``.prj``: Contains the spatial reference information for the geographic
|
|
data stored in the shapefile.
|
|
|
|
__ http://thematicmapping.org/downloads/TM_WORLD_BORDERS-0.3.zip
|
|
__ http://en.wikipedia.org/wiki/Shapefile
|
|
|
|
Use ``ogrinfo`` to examine spatial data
|
|
---------------------------------------
|
|
|
|
The GDAL ``ogrinfo`` utility allows examining the metadata of shapefiles or
|
|
other vector data sources:
|
|
|
|
.. code-block:: bash
|
|
|
|
$ ogrinfo world/data/TM_WORLD_BORDERS-0.3.shp
|
|
INFO: Open of `world/data/TM_WORLD_BORDERS-0.3.shp'
|
|
using driver `ESRI Shapefile' successful.
|
|
1: TM_WORLD_BORDERS-0.3 (Polygon)
|
|
|
|
``ogrinfo`` tells us that the shapefile has one layer, and that this
|
|
layer contains polygon data. To find out more, we'll specify the layer name
|
|
and use the ``-so`` option to get only the important summary information:
|
|
|
|
.. code-block:: bash
|
|
|
|
$ ogrinfo -so world/data/TM_WORLD_BORDERS-0.3.shp TM_WORLD_BORDERS-0.3
|
|
INFO: Open of `world/data/TM_WORLD_BORDERS-0.3.shp'
|
|
using driver `ESRI Shapefile' successful.
|
|
|
|
Layer name: TM_WORLD_BORDERS-0.3
|
|
Geometry: Polygon
|
|
Feature Count: 246
|
|
Extent: (-180.000000, -90.000000) - (180.000000, 83.623596)
|
|
Layer SRS WKT:
|
|
GEOGCS["GCS_WGS_1984",
|
|
DATUM["WGS_1984",
|
|
SPHEROID["WGS_1984",6378137.0,298.257223563]],
|
|
PRIMEM["Greenwich",0.0],
|
|
UNIT["Degree",0.0174532925199433]]
|
|
FIPS: String (2.0)
|
|
ISO2: String (2.0)
|
|
ISO3: String (3.0)
|
|
UN: Integer (3.0)
|
|
NAME: String (50.0)
|
|
AREA: Integer (7.0)
|
|
POP2005: Integer (10.0)
|
|
REGION: Integer (3.0)
|
|
SUBREGION: Integer (3.0)
|
|
LON: Real (8.3)
|
|
LAT: Real (7.3)
|
|
|
|
This detailed summary information tells us the number of features in the layer
|
|
(246), the geographic bounds of the data, the spatial reference system
|
|
("SRS WKT"), as well as type information for each attribute field. For example,
|
|
``FIPS: String (2.0)`` indicates that the ``FIPS`` character field has
|
|
a maximum length of 2. Similarly, ``LON: Real (8.3)`` is a floating-point
|
|
field that holds a maximum of 8 digits up to three decimal places.
|
|
|
|
Geographic Models
|
|
=================
|
|
|
|
Defining a Geographic Model
|
|
---------------------------
|
|
|
|
Now that you've examined your dataset using ``ogrinfo``, create a GeoDjango
|
|
model to represent this data::
|
|
|
|
from django.contrib.gis.db import models
|
|
|
|
class WorldBorder(models.Model):
|
|
# Regular Django fields corresponding to the attributes in the
|
|
# world borders shapefile.
|
|
name = models.CharField(max_length=50)
|
|
area = models.IntegerField()
|
|
pop2005 = models.IntegerField('Population 2005')
|
|
fips = models.CharField('FIPS Code', max_length=2)
|
|
iso2 = models.CharField('2 Digit ISO', max_length=2)
|
|
iso3 = models.CharField('3 Digit ISO', max_length=3)
|
|
un = models.IntegerField('United Nations Code')
|
|
region = models.IntegerField('Region Code')
|
|
subregion = models.IntegerField('Sub-Region Code')
|
|
lon = models.FloatField()
|
|
lat = models.FloatField()
|
|
|
|
# GeoDjango-specific: a geometry field (MultiPolygonField), and
|
|
# overriding the default manager with a GeoManager instance.
|
|
mpoly = models.MultiPolygonField()
|
|
objects = models.GeoManager()
|
|
|
|
# Returns the string representation of the model.
|
|
def __str__(self): # __unicode__ on Python 2
|
|
return self.name
|
|
|
|
Please note two important things:
|
|
|
|
1. The ``models`` module is imported from ``django.contrib.gis.db``.
|
|
2. You must override the model's default manager with
|
|
:class:`~django.contrib.gis.db.models.GeoManager` to perform spatial queries.
|
|
|
|
The default spatial reference system for geometry fields is WGS84 (meaning
|
|
the `SRID`__ is 4326) -- in other words, the field coordinates are in
|
|
longitude, latitude pairs in units of degrees. To use a different
|
|
coordinate system, set the SRID of the geometry field with the ``srid``
|
|
argument. Use an integer representing the coordinate system's EPSG code.
|
|
|
|
__ http://en.wikipedia.org/wiki/SRID
|
|
|
|
Run ``migrate``
|
|
---------------
|
|
|
|
After defining your model, you need to sync it with the database. First,
|
|
create a database migration:
|
|
|
|
.. code-block:: bash
|
|
|
|
$ python manage.py makemigrations
|
|
Migrations for 'world':
|
|
0001_initial.py:
|
|
- Create model WorldBorder
|
|
|
|
Let's look at the SQL that will generate the table for the ``WorldBorder``
|
|
model:
|
|
|
|
.. code-block:: bash
|
|
|
|
$ python manage.py sqlmigrate world 0001
|
|
|
|
This command should produce the following output:
|
|
|
|
.. code-block:: sql
|
|
|
|
BEGIN;
|
|
CREATE TABLE "world_worldborder" (
|
|
"id" serial NOT NULL PRIMARY KEY,
|
|
"name" varchar(50) NOT NULL,
|
|
"area" integer NOT NULL,
|
|
"pop2005" integer NOT NULL,
|
|
"fips" varchar(2) NOT NULL,
|
|
"iso2" varchar(2) NOT NULL,
|
|
"iso3" varchar(3) NOT NULL,
|
|
"un" integer NOT NULL,
|
|
"region" integer NOT NULL,
|
|
"subregion" integer NOT NULL,
|
|
"lon" double precision NOT NULL,
|
|
"lat" double precision NOT NULL
|
|
"mpoly" geometry(MULTIPOLYGON,4326) NOT NULL
|
|
)
|
|
;
|
|
CREATE INDEX "world_worldborder_mpoly_id" ON "world_worldborder" USING GIST ( "mpoly" );
|
|
COMMIT;
|
|
|
|
.. note::
|
|
|
|
With PostGIS < 2.0, the output is slightly different. The ``mpoly`` geometry
|
|
column is added through a separate ``SELECT AddGeometryColumn(...)``
|
|
statement.
|
|
|
|
If this looks correct, run :djadmin:`migrate` to create this table in the
|
|
database:
|
|
|
|
.. code-block:: bash
|
|
|
|
$ python manage.py migrate
|
|
Operations to perform:
|
|
Apply all migrations: admin, world, contenttypes, auth, sessions
|
|
Running migrations:
|
|
...
|
|
Applying world.0001_initial... OK
|
|
|
|
Importing Spatial Data
|
|
======================
|
|
|
|
This section will show you how to import the world borders shapefile into the
|
|
database via GeoDjango models using the :doc:`layermapping`.
|
|
|
|
There are many different ways to import data into a spatial database --
|
|
besides the tools included within GeoDjango, you may also use the following:
|
|
|
|
* `ogr2ogr`_: A command-line utility included with GDAL that
|
|
can import many vector data formats into PostGIS, MySQL, and Oracle databases.
|
|
* `shp2pgsql`_: This utility included with PostGIS imports ESRI shapefiles into
|
|
PostGIS.
|
|
|
|
.. _ogr2ogr: http://www.gdal.org/ogr2ogr.html
|
|
.. _shp2pgsql: http://postgis.refractions.net/documentation/manual-1.5/ch04.html#shp2pgsql_usage
|
|
|
|
.. _gdalinterface:
|
|
|
|
GDAL Interface
|
|
--------------
|
|
|
|
Earlier, you used ``ogrinfo`` to examine the contents of the world borders
|
|
shapefile. GeoDjango also includes a Pythonic interface to GDAL's powerful OGR
|
|
library that can work with all the vector data sources that OGR supports.
|
|
|
|
First, invoke the Django shell:
|
|
|
|
.. code-block:: bash
|
|
|
|
$ python manage.py shell
|
|
|
|
If you downloaded the :ref:`worldborders` data earlier in the
|
|
tutorial, then you can determine its path using Python's built-in
|
|
``os`` module::
|
|
|
|
>>> import os
|
|
>>> import world
|
|
>>> world_shp = os.path.abspath(os.path.join(os.path.dirname(world.__file__),
|
|
... 'data/TM_WORLD_BORDERS-0.3.shp'))
|
|
|
|
Now, open the world borders shapefile using GeoDjango's
|
|
:class:`~django.contrib.gis.gdal.DataSource` interface::
|
|
|
|
>>> from django.contrib.gis.gdal import DataSource
|
|
>>> ds = DataSource(world_shp)
|
|
>>> print(ds)
|
|
/ ... /geodjango/world/data/TM_WORLD_BORDERS-0.3.shp (ESRI Shapefile)
|
|
|
|
Data source objects can have different layers of geospatial features; however,
|
|
shapefiles are only allowed to have one layer::
|
|
|
|
>>> print(len(ds))
|
|
1
|
|
>>> lyr = ds[0]
|
|
>>> print(lyr)
|
|
TM_WORLD_BORDERS-0.3
|
|
|
|
You can see the layer's geometry type and how many features it contains::
|
|
|
|
>>> print(lyr.geom_type)
|
|
Polygon
|
|
>>> print(len(lyr))
|
|
246
|
|
|
|
.. note::
|
|
|
|
Unfortunately, the shapefile data format does not allow for greater
|
|
specificity with regards to geometry types. This shapefile, like
|
|
many others, actually includes ``MultiPolygon`` geometries, not Polygons.
|
|
It's important to use a more general field type in models: a
|
|
GeoDjango ``MultiPolygonField`` will accept a ``Polygon`` geometry, but a
|
|
``PolygonField`` will not accept a ``MultiPolygon`` type geometry. This
|
|
is why the ``WorldBorder`` model defined above uses a ``MultiPolygonField``.
|
|
|
|
The :class:`~django.contrib.gis.gdal.Layer` may also have a spatial reference
|
|
system associated with it. If it does, the ``srs`` attribute will return a
|
|
:class:`~django.contrib.gis.gdal.SpatialReference` object::
|
|
|
|
>>> srs = lyr.srs
|
|
>>> print(srs)
|
|
GEOGCS["GCS_WGS_1984",
|
|
DATUM["WGS_1984",
|
|
SPHEROID["WGS_1984",6378137.0,298.257223563]],
|
|
PRIMEM["Greenwich",0.0],
|
|
UNIT["Degree",0.0174532925199433]]
|
|
>>> srs.proj4 # PROJ.4 representation
|
|
'+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs '
|
|
|
|
This shapefile is in the popular WGS84 spatial reference
|
|
system -- in other words, the data uses longitude, latitude pairs in
|
|
units of degrees.
|
|
|
|
In addition, shapefiles also support attribute fields that may contain
|
|
additional data. Here are the fields on the World Borders layer:
|
|
|
|
>>> print(lyr.fields)
|
|
['FIPS', 'ISO2', 'ISO3', 'UN', 'NAME', 'AREA', 'POP2005', 'REGION', 'SUBREGION', 'LON', 'LAT']
|
|
|
|
The following code will let you examine the OGR types (e.g. integer or
|
|
string) associated with each of the fields:
|
|
|
|
>>> [fld.__name__ for fld in lyr.field_types]
|
|
['OFTString', 'OFTString', 'OFTString', 'OFTInteger', 'OFTString', 'OFTInteger', 'OFTInteger', 'OFTInteger', 'OFTInteger', 'OFTReal', 'OFTReal']
|
|
|
|
You can iterate over each feature in the layer and extract information from both
|
|
the feature's geometry (accessed via the ``geom`` attribute) as well as the
|
|
feature's attribute fields (whose **values** are accessed via ``get()``
|
|
method)::
|
|
|
|
>>> for feat in lyr:
|
|
... print(feat.get('NAME'), feat.geom.num_points)
|
|
...
|
|
Guernsey 18
|
|
Jersey 26
|
|
South Georgia South Sandwich Islands 338
|
|
Taiwan 363
|
|
|
|
:class:`~django.contrib.gis.gdal.Layer` objects may be sliced::
|
|
|
|
>>> lyr[0:2]
|
|
[<django.contrib.gis.gdal.feature.Feature object at 0x2f47690>, <django.contrib.gis.gdal.feature.Feature object at 0x2f47650>]
|
|
|
|
And individual features may be retrieved by their feature ID::
|
|
|
|
>>> feat = lyr[234]
|
|
>>> print(feat.get('NAME'))
|
|
San Marino
|
|
|
|
Boundary geometries may be exported as WKT and GeoJSON::
|
|
|
|
>>> geom = feat.geom
|
|
>>> print(geom.wkt)
|
|
POLYGON ((12.415798 43.957954,12.450554 ...
|
|
>>> print(geom.json)
|
|
{ "type": "Polygon", "coordinates": [ [ [ 12.415798, 43.957954 ], [ 12.450554, 43.979721 ], ...
|
|
|
|
|
|
``LayerMapping``
|
|
----------------
|
|
|
|
To import the data, use a LayerMapping in a Python script.
|
|
Create a file called ``load.py`` inside the ``world`` application,
|
|
with the following code::
|
|
|
|
import os
|
|
from django.contrib.gis.utils import LayerMapping
|
|
from models import WorldBorder
|
|
|
|
world_mapping = {
|
|
'fips' : 'FIPS',
|
|
'iso2' : 'ISO2',
|
|
'iso3' : 'ISO3',
|
|
'un' : 'UN',
|
|
'name' : 'NAME',
|
|
'area' : 'AREA',
|
|
'pop2005' : 'POP2005',
|
|
'region' : 'REGION',
|
|
'subregion' : 'SUBREGION',
|
|
'lon' : 'LON',
|
|
'lat' : 'LAT',
|
|
'mpoly' : 'MULTIPOLYGON',
|
|
}
|
|
|
|
world_shp = os.path.abspath(os.path.join(os.path.dirname(__file__), 'data/TM_WORLD_BORDERS-0.3.shp'))
|
|
|
|
def run(verbose=True):
|
|
lm = LayerMapping(WorldBorder, world_shp, world_mapping,
|
|
transform=False, encoding='iso-8859-1')
|
|
|
|
lm.save(strict=True, verbose=verbose)
|
|
|
|
A few notes about what's going on:
|
|
|
|
* Each key in the ``world_mapping`` dictionary corresponds to a field in the
|
|
``WorldBorder`` model. The value is the name of the shapefile field
|
|
that data will be loaded from.
|
|
* The key ``mpoly`` for the geometry field is ``MULTIPOLYGON``, the
|
|
geometry type GeoDjango will import the field as. Even simple polygons in
|
|
the shapefile will automatically be converted into collections prior to
|
|
insertion into the database.
|
|
* The path to the shapefile is not absolute -- in other words, if you move the
|
|
``world`` application (with ``data`` subdirectory) to a different location,
|
|
the script will still work.
|
|
* The ``transform`` keyword is set to ``False`` because the data in the
|
|
shapefile does not need to be converted -- it's already in WGS84 (SRID=4326).
|
|
* The ``encoding`` keyword is set to the character encoding of the string
|
|
values in the shapefile. This ensures that string values are read and saved
|
|
correctly from their original encoding system.
|
|
|
|
Afterwards, invoke the Django shell from the ``geodjango`` project directory:
|
|
|
|
.. code-block:: bash
|
|
|
|
$ python manage.py shell
|
|
|
|
Next, import the ``load`` module, call the ``run`` routine, and watch
|
|
``LayerMapping`` do the work::
|
|
|
|
>>> from world import load
|
|
>>> load.run()
|
|
|
|
.. _ogrinspect-intro:
|
|
|
|
Try ``ogrinspect``
|
|
------------------
|
|
Now that you've seen how to define geographic models and import data with the
|
|
:doc:`layermapping`, it's possible to further automate this process with
|
|
use of the :djadmin:`ogrinspect` management command. The :djadmin:`ogrinspect`
|
|
command introspects a GDAL-supported vector data source (e.g., a shapefile)
|
|
and generates a model definition and ``LayerMapping`` dictionary automatically.
|
|
|
|
The general usage of the command goes as follows:
|
|
|
|
.. code-block:: bash
|
|
|
|
$ python manage.py ogrinspect [options] <data_source> <model_name> [options]
|
|
|
|
``data_source`` is the path to the GDAL-supported data source and
|
|
``model_name`` is the name to use for the model. Command-line options may
|
|
be used to further define how the model is generated.
|
|
|
|
For example, the following command nearly reproduces the ``WorldBorder`` model
|
|
and mapping dictionary created above, automatically:
|
|
|
|
.. code-block:: bash
|
|
|
|
$ python manage.py ogrinspect world/data/TM_WORLD_BORDERS-0.3.shp WorldBorder \
|
|
--srid=4326 --mapping --multi
|
|
|
|
A few notes about the command-line options given above:
|
|
|
|
* The ``--srid=4326`` option sets the SRID for the geographic field.
|
|
* The ``--mapping`` option tells ``ogrinspect`` to also generate a
|
|
mapping dictionary for use with
|
|
:class:`~django.contrib.gis.utils.LayerMapping`.
|
|
* The ``--multi`` option is specified so that the geographic field is a
|
|
:class:`~django.contrib.gis.db.models.MultiPolygonField` instead of just a
|
|
:class:`~django.contrib.gis.db.models.PolygonField`.
|
|
|
|
The command produces the following output, which may be copied
|
|
directly into the ``models.py`` of a GeoDjango application::
|
|
|
|
# This is an auto-generated Django model module created by ogrinspect.
|
|
from django.contrib.gis.db import models
|
|
|
|
class WorldBorder(models.Model):
|
|
fips = models.CharField(max_length=2)
|
|
iso2 = models.CharField(max_length=2)
|
|
iso3 = models.CharField(max_length=3)
|
|
un = models.IntegerField()
|
|
name = models.CharField(max_length=50)
|
|
area = models.IntegerField()
|
|
pop2005 = models.IntegerField()
|
|
region = models.IntegerField()
|
|
subregion = models.IntegerField()
|
|
lon = models.FloatField()
|
|
lat = models.FloatField()
|
|
geom = models.MultiPolygonField(srid=4326)
|
|
objects = models.GeoManager()
|
|
|
|
# Auto-generated `LayerMapping` dictionary for WorldBorder model
|
|
worldborders_mapping = {
|
|
'fips' : 'FIPS',
|
|
'iso2' : 'ISO2',
|
|
'iso3' : 'ISO3',
|
|
'un' : 'UN',
|
|
'name' : 'NAME',
|
|
'area' : 'AREA',
|
|
'pop2005' : 'POP2005',
|
|
'region' : 'REGION',
|
|
'subregion' : 'SUBREGION',
|
|
'lon' : 'LON',
|
|
'lat' : 'LAT',
|
|
'geom' : 'MULTIPOLYGON',
|
|
}
|
|
|
|
Spatial Queries
|
|
===============
|
|
|
|
Spatial Lookups
|
|
---------------
|
|
GeoDjango adds spatial lookups to the Django ORM. For example, you
|
|
can find the country in the ``WorldBorder`` table that contains
|
|
a particular point. First, fire up the management shell:
|
|
|
|
.. code-block:: bash
|
|
|
|
$ python manage.py shell
|
|
|
|
Now, define a point of interest [#]_::
|
|
|
|
>>> pnt_wkt = 'POINT(-95.3385 29.7245)'
|
|
|
|
The ``pnt_wkt`` string represents the point at -95.3385 degrees longitude,
|
|
29.7245 degrees latitude. The geometry is in a format known as
|
|
Well Known Text (WKT), a standard issued by the Open Geospatial
|
|
Consortium (OGC). [#]_ Import the ``WorldBorder`` model, and perform
|
|
a ``contains`` lookup using the ``pnt_wkt`` as the parameter::
|
|
|
|
>>> from world.models import WorldBorder
|
|
>>> qs = WorldBorder.objects.filter(mpoly__contains=pnt_wkt)
|
|
>>> qs
|
|
[<WorldBorder: United States>]
|
|
|
|
Here, you retrieved a ``GeoQuerySet`` with only one model: the border of
|
|
the United States (exactly what you would expect).
|
|
|
|
Similarly, you may also use a :doc:`GEOS geometry object <geos>`.
|
|
Here, you can combine the ``intersects`` spatial lookup with the ``get``
|
|
method to retrieve only the ``WorldBorder`` instance for San Marino instead
|
|
of a queryset::
|
|
|
|
>>> from django.contrib.gis.geos import Point
|
|
>>> pnt = Point(12.4604, 43.9420)
|
|
>>> sm = WorldBorder.objects.get(mpoly__intersects=pnt)
|
|
>>> sm
|
|
<WorldBorder: San Marino>
|
|
|
|
The ``contains`` and ``intersects`` lookups are just a subset of the
|
|
available queries -- the :doc:`db-api` documentation has more.
|
|
|
|
Automatic Spatial Transformations
|
|
---------------------------------
|
|
When doing spatial queries, GeoDjango automatically transforms
|
|
geometries if they're in a different coordinate system. In the following
|
|
example, coordinates will be expressed in `EPSG SRID 32140`__,
|
|
a coordinate system specific to south Texas **only** and in units of
|
|
**meters**, not degrees::
|
|
|
|
>>> from django.contrib.gis.geos import Point, GEOSGeometry
|
|
>>> pnt = Point(954158.1, 4215137.1, srid=32140)
|
|
|
|
Note that ``pnt`` may also be constructed with EWKT, an "extended" form of
|
|
WKT that includes the SRID::
|
|
|
|
>>> pnt = GEOSGeometry('SRID=32140;POINT(954158.1 4215137.1)')
|
|
|
|
GeoDjango's ORM will automatically wrap geometry values
|
|
in transformation SQL, allowing the developer to work at a higher level
|
|
of abstraction::
|
|
|
|
>>> qs = WorldBorder.objects.filter(mpoly__intersects=pnt)
|
|
>>> print(qs.query) # Generating the SQL
|
|
SELECT "world_worldborder"."id", "world_worldborder"."name", "world_worldborder"."area",
|
|
"world_worldborder"."pop2005", "world_worldborder"."fips", "world_worldborder"."iso2",
|
|
"world_worldborder"."iso3", "world_worldborder"."un", "world_worldborder"."region",
|
|
"world_worldborder"."subregion", "world_worldborder"."lon", "world_worldborder"."lat",
|
|
"world_worldborder"."mpoly" FROM "world_worldborder"
|
|
WHERE ST_Intersects("world_worldborder"."mpoly", ST_Transform(%s, 4326))
|
|
>>> qs # printing evaluates the queryset
|
|
[<WorldBorder: United States>]
|
|
|
|
__ http://spatialreference.org/ref/epsg/32140/
|
|
|
|
.. admonition:: Raw queries
|
|
|
|
When using :doc:`raw queries </topics/db/sql>`, you should generally wrap
|
|
your geometry fields with the ``asText()`` SQL function (or ``ST_AsText``
|
|
for PostGIS) so that the field value will be recognized by GEOS::
|
|
|
|
City.objects.raw('SELECT id, name, asText(point) from myapp_city')
|
|
|
|
This is not absolutely required by PostGIS, but generally you should only
|
|
use raw queries when you know exactly what you are doing.
|
|
|
|
Lazy Geometries
|
|
---------------
|
|
GeoDjango loads geometries in a standardized textual representation. When the
|
|
geometry field is first accessed, GeoDjango creates a `GEOS geometry object
|
|
<ref-geos>`, exposing powerful functionality, such as serialization properties
|
|
for popular geospatial formats::
|
|
|
|
>>> sm = WorldBorder.objects.get(name='San Marino')
|
|
>>> sm.mpoly
|
|
<MultiPolygon object at 0x24c6798>
|
|
>>> sm.mpoly.wkt # WKT
|
|
MULTIPOLYGON (((12.4157980000000006 43.9579540000000009, 12.4505540000000003 43.9797209999999978, ...
|
|
>>> sm.mpoly.wkb # WKB (as Python binary buffer)
|
|
<read-only buffer for 0x1fe2c70, size -1, offset 0 at 0x2564c40>
|
|
>>> sm.mpoly.geojson # GeoJSON (requires GDAL)
|
|
'{ "type": "MultiPolygon", "coordinates": [ [ [ [ 12.415798, 43.957954 ], [ 12.450554, 43.979721 ], ...
|
|
|
|
This includes access to all of the advanced geometric operations provided by
|
|
the GEOS library::
|
|
|
|
>>> pnt = Point(12.4604, 43.9420)
|
|
>>> sm.mpoly.contains(pnt)
|
|
True
|
|
>>> pnt.contains(sm.mpoly)
|
|
False
|
|
|
|
``GeoQuerySet`` Methods
|
|
-----------------------
|
|
|
|
|
|
Putting your data on the map
|
|
============================
|
|
|
|
Geographic Admin
|
|
----------------
|
|
|
|
GeoDjango extends :doc:`Django's admin application </ref/contrib/admin/index>`
|
|
with support for editing geometry fields.
|
|
|
|
Basics
|
|
^^^^^^
|
|
|
|
GeoDjango also supplements the Django admin by allowing users to create
|
|
and modify geometries on a JavaScript slippy map (powered by `OpenLayers`_).
|
|
|
|
Let's dive right in. Create a file called ``admin.py`` inside the
|
|
``world`` application with the following code::
|
|
|
|
from django.contrib.gis import admin
|
|
from models import WorldBorder
|
|
|
|
admin.site.register(WorldBorder, admin.GeoModelAdmin)
|
|
|
|
Next, edit your ``urls.py`` in the ``geodjango`` application folder as follows::
|
|
|
|
from django.conf.urls import url, include
|
|
from django.contrib.gis import admin
|
|
|
|
urlpatterns = [
|
|
url(r'^admin/', include(admin.site.urls)),
|
|
]
|
|
|
|
Create an admin user:
|
|
|
|
.. code-block:: bash
|
|
|
|
$ python manage.py createsuperuser
|
|
|
|
Next, start up the Django development server:
|
|
|
|
.. code-block:: bash
|
|
|
|
$ python manage.py runserver
|
|
|
|
Finally, browse to ``http://localhost:8000/admin/``, and log in with the user
|
|
you just created. Browse to any of the ``WorldBorder`` entries -- the borders
|
|
may be edited by clicking on a polygon and dragging the vertexes to the desired
|
|
position.
|
|
|
|
.. _OpenLayers: http://openlayers.org/
|
|
.. _Open Street Map: http://www.openstreetmap.org/
|
|
.. _Vector Map Level 0: http://earth-info.nga.mil/publications/vmap0.html
|
|
.. _OSGeo: http://www.osgeo.org
|
|
|
|
.. _osmgeoadmin-intro:
|
|
|
|
``OSMGeoAdmin``
|
|
^^^^^^^^^^^^^^^
|
|
|
|
With the :class:`~django.contrib.gis.admin.OSMGeoAdmin`, GeoDjango uses
|
|
a `Open Street Map`_ layer in the admin.
|
|
This provides more context (including street and thoroughfare details) than
|
|
available with the :class:`~django.contrib.gis.admin.GeoModelAdmin`
|
|
(which uses the `Vector Map Level 0`_ WMS dataset hosted at `OSGeo`_).
|
|
|
|
First, there are some important requirements:
|
|
|
|
* :class:`~django.contrib.gis.admin.OSMGeoAdmin` requires that
|
|
:doc:`GDAL <gdal>` is installed. If you are running GDAL 1.6 (uncommon)
|
|
you will also need to add the :ref:`spherical mercator projection
|
|
<addgoogleprojection>` to the ``spatial_ref_sys`` table.
|
|
|
|
* The PROJ.4 datum shifting files must be installed (see the
|
|
:ref:`PROJ.4 installation instructions <proj4>` for more details).
|
|
|
|
If you meet this requirement, then just substitute the ``OSMGeoAdmin``
|
|
option class in your ``admin.py`` file::
|
|
|
|
admin.site.register(WorldBorder, admin.OSMGeoAdmin)
|
|
|
|
.. rubric:: Footnotes
|
|
|
|
.. [#] Special thanks to Bjørn Sandvik of `thematicmapping.org
|
|
<http://thematicmapping.org>`_ for providing and maintaining this
|
|
dataset.
|
|
.. [#] GeoDjango basic apps was written by Dane Springmeyer, Josh Livni, and
|
|
Christopher Schmidt.
|
|
.. [#] This point is the `University of Houston Law Center
|
|
<http://www.law.uh.edu/>`_.
|
|
.. [#] Open Geospatial Consortium, Inc., `OpenGIS Simple Feature Specification
|
|
For SQL <http://www.opengeospatial.org/standards/sfs>`_.
|