Version 75 (modified by 18 years ago) ( diff ) | ,
---|
Contents
The GIS branch intends to be a world-class geographic web framework. Our goal is to make it as easy as possible to build GIS web applications and harness the power of spatially enabled data.
- FAQ
- Implementation
- Example
- Installation
- GeoDjango Branch from SVN (required)
- GEOS (required)
- PROJ.4 (required)
- PostGIS (required)
- GDAL
- Model API
- Database API
FAQ
- Place your questions here.
- Q: When dealing with points (say, degrees) from, do they need to be converted to be useful on the back-end data, assuming -that- data is in degrees? Is it enough to have the same datum and origin? (Reading the intro above is likely to answer the question.)
- My (JDunck) reading indicates yes. Given the same coordinate system (i.e. datum, origin, and axes), degrees are useful without conversion.
- Q: Can this implementation work with MySQL spatial-extensions. If not, it's planned?
- No. It is (now) planned, see Phase 3 below. From the last time I (jbronn) checked, MySQL's spatial capabilities have improved. However, we're going to focus our efforts on PostGIS until things are worked out a bit more. As a spatial database PostGIS it is more standards compliant (OpenGIS consortium), more widely used, and has more features (e.g. coordinate transformation,
geometry_columns
andspatial_ref_sys
tables). It is definitely something I would want to implement in the future since I do like MySQL.
- No. It is (now) planned, see Phase 3 below. From the last time I (jbronn) checked, MySQL's spatial capabilities have improved. However, we're going to focus our efforts on PostGIS until things are worked out a bit more. As a spatial database PostGIS it is more standards compliant (OpenGIS consortium), more widely used, and has more features (e.g. coordinate transformation,
- Q: Is this going to be a WMS Server/WMS Client/Both? OWSLib is just a WMS Client from what I have seen (from ruckc)
- WMS Server first, client capability a possibility in certain situations (i.e. you want to cache data from another WMS server). Yes, OWSLib is a client, but it contains code for validating the correct parameters to send to a WMS server, thus it can be adapted into a Django view that validates whether the proper WMS parameters were given. Mapnik has an
ogcserver
module that can parse the correct parameters for WMS 1.1.1 and 1.3.0, however since it is licensed under the LGPL it cannot be easily incorporated into this branch (unlike OWSLib).
- WMS Server first, client capability a possibility in certain situations (i.e. you want to cache data from another WMS server). Yes, OWSLib is a client, but it contains code for validating the correct parameters to send to a WMS server, thus it can be adapted into a Django view that validates whether the proper WMS parameters were given. Mapnik has an
- Q: Per this discussion in
django-developers
, "I can do spatial queries if they are directly between two models with polygon fields but I can't seem to get at the spatial queries through foreign keys."- Geographic queries require
GeoManager
, even if the model does not have a geographic field itself (in the case of a foreign key to a geo-field). The reply in the discussion gives detail into why.
- Geographic queries require
Implementation
Phase 1
- Create Geometry-enabled fields and manager. Status: complete as of r4788.
- Allow for Geometry-enabled queries. Status: complete as of r4788.
Phase 2
- Pending
- Add as much from the PostGIS API as possible.
- Support for a mapping framework (e.g. Google Maps/Earth, Yahoo Maps, MS Live, etc.)
- Admin fields and forms (WKT field currently as of r4884, but we want widgets to view and manipulate geographic objects).
- Utilities for importing vector and raster data (SHP files first) directly into Django models -- will be done with the forthcoming
LayerMapping
class. - Distance queries, calculations, and related utilities.
- Complete
- PostGIS indexing capability.
- As of r5008, added a GEOS wrapper object for geometry-enabled fields that call directly on GEOS routines (e.g.
z.get_poly_geos().area
). See Extra Instance Methods section below)
Phase 3
- Support MySQL databases.
- Geocoding framework.
Design Issues
- Client JS/Flash framework, i.e., do we want to support OpenLayers, the Google Maps API, the Yahoo API?
- So far, Google Maps looks the most promising for being supported first (people are familiar with it, and it's more stable than open layers).
- Yahoo! has a really slick flash interface, I'd like to support this eventually.
- OpenLayers supports WMS/WFS/tiles as well as Google, MSVE, and Yahoo layers. It is very flexible and open(!!). (Rob Coup)
- Mapping Framework (generating custom tiles, layers, labels, etc.)
- Mapnik is modern, but very early on in development and completely lacks documentation. However, the code is elegant and clean, and it was designed for integration with Python -- we're leaning towards this right now.
- Mapserver has been around for a while, strong backing in the community (e.g. native support in QGIS). Even with documentation, the code looks less inviting than Mapnik (all in C); also has archaic text-based configuration files (pre-dating markup languages).
- GEOS
- Update: As of r5008, GeoDjango has its own GEOS interface (via
ctypes
) - GEOS is no longer maintained by Sean Gillies. See Sean Gillies, Geometries for Python (blog post explaining rationale for abandoning GEOS support); see also Sean's message on the GEOS-Devel Mailing List (Mar. 5, 2007).
- Might consider either
using PCLor implement actypes
wrapper for the routines that we need -- can't really port PCL code here because it is GPL (Django is licensed under BSD).
- Update: As of r5008, GeoDjango has its own GEOS interface (via
- WMS Server
- I'm not satisfied with any of the current WMS/WFS implementations. One implemented in Django would be desirable, e.g.,
django.contrib.gis.wms
. Thoughts anyone? (OWSLib looks good, see below)
- I'm not satisfied with any of the current WMS/WFS implementations. One implemented in Django would be desirable, e.g.,
Collaboration
- PCL (Python Cartographic Library), now part of GIS Python, has done a lot of good work already. Let's apply the DRY principle. Strong opportunities for collaboration with regards to:
- Mapping framework
- WMS/WMF Framework -- OWSLib looks excellent for this (BSD licensed and has unit tests!)
- Utilities
- Database representation ideas
- GEOS support, Sean Gilles (lead developer of PCL) looking for help maintaining Python/SWIG interface to GEOS. If SWIG interface no longer maintained, might have to move to PCL for up-to-date GEOS library support.
- CoordinatesField.
- Jannis Leidel has already come up with a way to manipulate points in the admin interface, BSD licensed.
- geopy
- Brian Beck has written a good foundation for geocoding and distance calculations, BSD licensed.
Example
Geographic Models
Here is an example of how the model API currently works (assume this example is in geo_app/models.py):
from django.contrib.gis.db import models class District(models.Model, models.GeoMixin): name = models.CharField(maxlength=35) num = models.IntegerField() poly = models.PolygonField() objects = models.GeoManager() class School(models.Model, models.GeoMixin): name = models.CharField(maxlength=35) point = models.PointField() objects = models.GeoManager()
Notes: The GeoMixin
class allows for extra instance methods. By default, a GiST index will be created for the School PointField
s fields. This behavior can be turned off by using models.PointField(index=False)
.
Using syncdb
Use the manage.py
to invoke syncdb
like you normally would:
$ python manage.py sqlall geo_app BEGIN; CREATE TABLE "geo_app_school" ( "id" serial NOT NULL PRIMARY KEY, "name" varchar(35) NOT NULL ); CREATE TABLE "geo_app_district" ( "id" serial NOT NULL PRIMARY KEY, "name" varchar(35) NOT NULL, "num" integer NOT NULL ); SELECT AddGeometryColumn('geo_app_school', 'point', 4326, 'POINT', 2); CREATE INDEX "geo_app_school_point_id" ON "geo_app_school" USING GIST ( "point" GIST_GEOMETRY_OPS ); SELECT AddGeometryColumn('geo_app_district', 'poly', 4326, 'POLYGON', 2); CREATE INDEX "geo_app_district_poly_id" ON "geo_app_district" USING GIST ( "poly" GIST_GEOMETRY_OPS ); COMMIT; $ python manage.py syncdb geo_app
Note: The geometry columns are created outside of the CREATE TABLE
statements by the AddGeometryColumn
. This is done according to the OpenGIS specfication. See Open GIS Consortium, Inc., OpenGIS Simple Feature Specification For SQL, Document 99-049 (May 5, 1999), at Ch. 2.3.8 (Geometry Values and Spatial Reference Systems, pg. 39).
Spatial Queries
After a geographic model has been created, the PostGIS additions to the API may be used. Geographic queries are done normally by using filter()
and exclude()
on geometry-enabled models using geographic lookup types (see the Database API below for lookup types). In the following example, the bbcontains
lookup type is used which is the same as the PostGIS &&
operator. It looks to see if the bounding box of the polygon contains the specific point. The next example uses the PostGIS Contains()
function, which calls GEOS library to test if the polygon actually contains the specific point, not just the bounding box.
>>> from geo_app.models import District, School >>> qs1 = District.objects.filter(poly__bbcontains='POINT(-95.362293 29.756539)') >>> qs2 = District.objects.filter(poly__contains='POINT(-95.362293 29.756539)')
Both spatial queries and normal queries using filter()
may be used in the same query. For example, the following query set will only show school districts that have 'Houston' in their name and contain the given point within their polygon boundary:
>>> qs = District.objects.filter(name__contains='Houston').filter(poly__contains='POINT(-95.362293 29.756539)')
Or combine both the bounding box routines (less accurate, fast) with the GEOS routines (most accurate, slower) to get a query that is both fast and accurate:
>>> qs = District.objects.filter(poly__bbcontains='POINT(-95.362293 29.756539)').filter(poly__contains='POINT(-95.362293 29.756539)')
Installation
Installation of the GeoDjango module will also require the installation of existing open source geographic libraries and a spatial database (currently only PostGIS). This section will describe the installation process for these libraries. Initially, these instructions will pertain only to a Linux platform (particularly Debian or Ubuntu). Mac & Windows support will be considered later; however, these instructions will most likely work through the Mac shell. Don't hold your breath for Windows support. Community support for prerequisites is better than previously believed, Windows support will come much earlier than expected.
Python & PostgreSQL
- Python
- Required: Python 2.4 is required because of heavy use of 2.4 decorator syntax (e.g.
@property
). Thectypes
module needs to be installed as well. - Recommended: Python 2.5 is recommended because the
ctypes
module comes included. Python 2.5.1 is the current latest.
- Required: Python 2.4 is required because of heavy use of 2.4 decorator syntax (e.g.
- PostgreSQL
- Recommended: PostgreSQL 8.X
- We are currently using v8.1 of PostgreSQL, and know of no problems with 8.2
- On Ubuntu Feisty, you'll need the apt packages
postgresql-server-dev-8.x
(the development headers are needed for PostGIS compilation) andpostgresql-8.x
.
Django
- GeoDjango exists in the
gis
branch from SVN:$ svn co http://code.djangoproject.com/svn/django/branches/gis django_gis $ ln -s django_gis /path/to/site-packages/django
GEOS
- Latest GEOS version is 3.0.0RC4
- Update: As of r5008, you do not need to enable the Python bindings because GeoDjango has its own GEOS
ctypes
wrapper.ctypes
comes standard with Python 2.5. If you run Python 2.4,ctypes
may be downloaded here
- Configure, make, and install.
$ ./configure $ make # make install
PROJ.4
- Latest PROJ.4 version is 4.5.0
- First, download the PROJ datum shifting files. These will come in handy for coordinate transformations when other programs (like Mapserver or Mapnik) are not able to cope with EPSG transformations (I learned the hard way). Untar/unzip these in the
nad
subdirectory of the PROJ source. For example, if PROJ was unzipped in a directory namedproj
, then untar these files inproj/nad
. Do this before you do the configure/make/install dance.- See PROJ FAQ; see also Frank Warmerdam's reply to a Mapserver question.
- Next, configure, make and install.
$ ./configure $ make # make install
PostGIS
- Latest PostGIS version is 1.2.1
- First build & install PostGIS.
$ ./configure --with-geos --with-proj $ make # make install
- Next, create a role and database for your application, and allow it to access PostGIS functionality:
# su - postgres $ psql postgres=# CREATE ROLE <user> LOGIN; postgres=# \q $ createdb -O <user> <db_name> $ createlang plpgsql <db_name> $ psql -d <db_name> -f /usr/local/share/lwpostgis.sql $ psql -d <db_name> -f /usr/local/share/spatial_ref_sys.sql $ psql <db_name> <db_name>=# GRANT SELECT, UPDATE, INSERT, DELETE ON geometry_columns TO <user>; <db_name>=# GRANT SELECT ON spatial_ref_sys TO <user>;
- Finally, update your
settings.py
to reflect the name and user for the spatially enabled database. So far, we only plan to support the psycopg2 backend, thus:DATABASE_ENGINE='postgresql_psycopg2'
.
GDAL
- Optional, but highly useful for coordinate transformations and reading/writing both vector (e.g. SHP) and raster (e.g. TIFF) geographic data.
- For example, the following command will convert your SHP file into WGS84 (standard lat/lon). Then you can import directly into your database using
shp2pgsql
(utility from PostGIS):ogr2ogr -t_srs WGS84 output.shp input.shp
- For example, the following command will convert your SHP file into WGS84 (standard lat/lon). Then you can import directly into your database using
- Latest GDAL version is 1.4.1. Configure with GEOS and Python support, then make and install:
$ ./configure --with-geos --with-python $ make # make install
- This is done without the 'next generation' SWIG Python bindings. The compilation flag to enable the new bindings is
--with-ngpython
. - Note: As of 1.4.1,
ngpython
bindings don't work with Python 2.5. While it's listed as fixed on trunk per their ticket 1379, I've still had issues with using the trunk. Actypes
interface, for needed GeoDjango functionality, is forthcoming.
Model API
Fields
The following geometry-enabled fields are available:
PointField
LineStringField
PolygonField
MultiPointField
MultiLineStringField
MultiPolygonField
GeometryCollectionField
Field Keywords
- Field keywords are used during model creation, for example:
from django.contrib.gis.db import models class Zip(models.Model, models.GeoMixin): code = models.IntegerField() poly = models.PolygonField(srid=-1) object = models.GeoManager()
srid
- Sets the SRID (Spatial Reference System Identity) of geometry to the given value. Defaults to 4326 (WGS84). See Open GIS Consortium, Inc., OpenGIS Simple Feature Specification For SQL, Document 99-049 (May 5, 1999), at Ch. 2.3.8 (Geometry Values and Spatial Reference Systems, pg. 39).
index
- Defaults to True. Creates a GiST index for the given geometry. Update the index with the PostgreSQL command
VACUUM ANALYZE
(may take a while to execute depending on how large your geographic-enabled tables are).
- Defaults to True. Creates a GiST index for the given geometry. Update the index with the PostgreSQL command
Creating and Saving Models with Geometry Fields
Here is an example of how to create a geometry object (assuming the Zip
model example above):
>>> from zipcode.models import Zip >>> z = Zip(code=77096, poly='POLYGON(( 10 10, 10 20, 20 20, 20 15, 10 10))') >>> z.save()
Geometries are represented as strings in either of the formats WKT (Well Known Text) or HEXEWKB (PostGIS specific, essentially a WKB geometry in hexadecimal). For example:
- WKT Polygon:
'POLYGON(( 10 10, 10 20, 20 20, 20 15, 10 10))'
- See Open GIS Consortium, Inc., OpenGIS Simple Feature Specification For SQL, Document 99-049 (May 5, 1999), at Ch. 3.2.5 (SQL Textual Representation of Geometry, pg. 53).
- HEXEWKB Polygon: '
0103000000010000000 ... 00000000000002440'
- See PostGIS EWKB, EWKT and Canonical Forms, PostGIS documentation at Ch. 4.1.2.
Database API
Note: The following database lookup types can only be used with on geographic fields with filter()
. Filters on 'normal' fields (e.g. CharField
) may be chained with those on geographic fields. Thus, geographic queries take the following form (assuming the Zip
model used in the Model API section above):
>>> qs = Zip.objects.filter(<geo field A>__<geo lookup type>=<geo string B>) >>> qs = Zip.objects.exclude(...)
PostGIS Operator Field Lookup Types
- See generally, "Operators", PostGIS Documentation at Ch. 6.2.2
- Note: This API is subject to some change -- we're open to suggestions.
overlaps_left
- Returns true if A's bounding box overlaps or is to the left of B's bounding box.
- PostGIS equivalent "
&<
"
overlaps_right
- Returns true if A's bounding box overlaps or is to the right of B's bounding box.
- PostGIS equivalent "
&>
"
left
- Returns true if A's bounding box is strictly to the left of B's bounding box.
- PostGIS equivalent "
<<
"
right
- Returns true if A's bounding box is strictly to the right of B's bounding box.
- PostGIS equivalent "
>>
"
overlaps_below
- Returns true if A's bounding box overlaps or is below B's bounding box.
- PostGIS equivalent "
&<|
"
overlaps_above
- Returns true if A's bounding box overlaps or is above B's bounding box.
- PostGIS equivalent "
|&>
"
strictly_below
- Returns true if A's bounding box is strictly below B's bounding box.
- PostGIS equivalent "
<<|
"
strictly_above
- Returns true if A's bounding box is strictly above B's bounding box.
- PostGIS equivalent "
|>>
"
same_as
orexact
- The "same as" operator. It tests actual geometric equality of two features. So if A and B are the same feature, vertex-by-vertex, the operator returns true.
- PostGIS equivalent "
~=
"
contained
- Returns true if A's bounding box is completely contained by B's bounding box.
- PostGIS equivalent "
@
"
bbcontains
- Returns true if A's bounding box completely contains B's bounding box.
- PostGIS equivalent "
~
"
bboverlaps
- Returns true if A's bounding box overlaps B's bounding box.
- PostGIS equivalent "
&&
"
PostGIS GEOS Function Field Lookup Types
- See generally "Geometry Relationship Functions", PostGIS Documentation at Ch. 6.1.2.
- This documentation will be updated completely with the content from the aforementioned PostGIS docs.
distance
- Warning: This function lookup type does not work, and will be moved to a routine as part of
GeoManager
. - Return the cartesian distance between two geometries in projected units.
- PostGIS equivalent
Distance(geometry, geometry)
- Warning: This function lookup type does not work, and will be moved to a routine as part of
equals
- Requires GEOS
- Returns 1 (TRUE) if the given Geometries are "spatially equal".
- Use this for a 'better' answer than '='. equals('LINESTRING(0 0, 10 10)','LINESTRING(0 0, 5 5, 10 10)') is true.
- PostGIS equivalent
Equals(geometry, geometry)
, OGC SPEC s2.1.1.2
disjoint
- Requires GEOS
- Returns 1 (TRUE) if the Geometries are "spatially disjoint".
- PostGIS equivalent
Disjoint(geometry, geometry)
intersects
- PostGIS equivalent
Intersects(geometry, geometry)
- PostGIS equivalent
touches
- PostGIS equivalent
Touches(geometry, geometry)
- PostGIS equivalent
crosses
- PostGIS equivalent
Crosses(geometry, geometry)
- PostGIS equivalent
overlaps
- PostGIS equivalent
Overlaps(geometry, geometry)
- PostGIS equivalent
contains
- PostGIS equivalent
Contains(geometry, geometry)
- PostGIS equivalent
intersects
- PostGIS equivalent
Intersects(geometry, geometry)
- PostGIS equivalent
relate
- PostGIS equivelent
Relate(geometry, geometry)
- PostGIS equivelent
Extra Instance Methods
A model with geometry fields will get the following methods, substitute GEOM
with the name of the geometry field:
get_GEOM_geos
Returns a GEOSGeometry
instance for the geometry. For example (using the District
model from above):
>>> from django.contrib.gis.geos import GEOSGeometry >>> dist = District.objects.get(name='Houston ISD') >>> geom = dist.get_poly_geos() >>> print geom.centroid.wkt POINT(-95.231713 29.723235) >>> print geom.area 0.08332 >>> print geom.geom_type Polygon >>> print geom.centroid.geom_type Point >>> print geom.intersects(GEOSGeometry('POINT(-95.395223 29.798088)')) False
get_GEOM_wkt
Returns the OGC WKT (Well Known Text) for the geometry. For example (using the School
model from above):
>>> skool = School.objects.get(name='PSAS') >>> print skool.get_point_wkt() POINT(-95.460822 29.745463)
get_GEOM_centroid
This routine will return the centroid of the geometry. For example (using the District
model from above):
>>> dist = District.objects.get(name='Houston ISD') >>> print dist.get_poly_centroid() POINT(-95.231713 29.723235)
get_GEOM_area
This routine will return the area of the geometry field.
>>> dist = District.objects.get(name='Houston ISD') >>> print dist.get_poly_area() 0.08332
Note: Units are in the projected units of the coordinate system. In the example above, the units are in degrees since we're using WGS84. The units system needs to be figured out here, since I don't know what these units represent.