Code


Version 42 (modified by jbronn, 7 years ago) (diff)

added model api

The gis branch intends to add a contrib app implementing geographic support.

What's GIS?

  • Series of blog posts giving intro to GIS; choice quote from an early post: "If you feel like ending a conversation with a developer then simply bring up the topic of character encodings ... [o]r ... coordinate systems. ... So in the spirit of Tim Bray's and Joel Spolsky's wonderful writeups of character encodings, I thought I'd put together a basic survival guide to coordinate systems over my next few posts and then tie it back to Google Maps."
  • More on map projections, including why people can't agree on just one (utf-8).
  • geodesy the field of science for this stuff.

Useful code

  • PostGIS, the OpenGIS SQL Types (pdf) implementation for Postgresql
  • GEOS, low-level C++ port of Jave Topology Suite, used by PostGIS
  • GeoTypes is a type (and conversion) library for PostGIS via psycopg.
  • Geopy
    • Calculates distances using (very accurate) Vincenty, and uses the WGS 84 datum by default.
    • Has utility functions for unit of measurement (UOM) conversions (e.g. meters -> kilometers, kilometers -> miles, etc.)
    • Excellent GeoCoding capabilites. Has interfaces for Google, Yahoo, Microsoft Live, MediaWiki, and Geocoder.us.
  • GDAL/OGR, a library for fiddling with raster geo images.
    • Has a Python interface. A SWIG interface is in development, but not yet stable (no access to full API).
  • Geo::Coder::US An excellent Perl library for GeoCoding that powers Geocoder.us. Users can create their own Geographic databases using the Census Bureau's TIGER/Line data (see below).
  • GeoRosetta, CC-BY-SA licensed, quality-controlled, collection of geocoding data. Not yet released to public(?).
  • MapServer: University of Minnesota (UMN) "open source development environment for building spatially-enabled internet applications."
  • MapNik: C++ and Python toolkit for developing mapping applications. Claimed benefits over MapServer: "It uses the AGG library and offers world class anti-aliasing rendering with subpixel accuracy for geographic data. It is written from scratch in modern C++ and doesn't suffer from design decisions made a decade ago." See MapNik FAQ.
  • Ruby on Rails
    • IvyGIS: Google-maps type displays with RoR and UMN's MapServer
    • Spatial Adapter for Rails: A plugin for Rails which manages the MySql Spatial and PostGIS geometric columns in a transparent way (that is like the other base data type columns). This might have some useful techniques for when we try to support other spatial extensions other than PostGIS.
    • Cartographer GMaps plugin

Useful Data

  • TIGER/Line: "The TIGER/Line files are extracts of selected geographic and cartographic information from the Census Bureau's TIGER® (Topologically Integrated Geographic Encoding and Referencing) database." This data is useful in creating your own geocoding database service. Currently 2006 First Edition is the latest, but second edition should be coming soon. Note: The Census Bureau will be providing SHP files in Fall, 2007.

Questions

  • 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.

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

  • Add as much from the PostGIS API as possible.
  • Finish PostGIS indexing capability.
  • Admin fields and forms.
  • Add geometry-enabled routines to the fields that call directly on GEOS routines -- like area(), centroid(), etc.
  • Support for mapping frameworks.
  • Utilities for importing raster data (SHP files first) directly into Django models.

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
    • Utilities
    • Database representation ideas
    • WMS/WMF Framework
    • 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.

Example

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):
    name = models.CharField(maxlength=35)
    num  = models.IntegerField()
    poly = models.PolygonField()

    objects = models.GeoManager()

class School(models.Model):
    name  = models.CharField(maxlength=35)
    point = models.PointField()

    objects = models.GeoManager()

Use the manage.py just 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);
SELECT AddGeometryColumn('geo_app_district', 'poly', 4326, 'MULTIPOLYGON', 2);
COMMIT;
$ python manage.py syncdb geo_app

PostGIS additions to the API may now be used. Geographic queries are done by calling geo_filter() and geo_exclude on geometry-enabled models. 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.geo_filter(poly__bbcontains='POINT(-95.362293 29.756539)') 
>>> qs2 = District.objects.geo_filter(poly__contains='POINT(-95.362293 29.756539)') 

Both geo_filter() and 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').geo_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 (this is not 'fast' in the current implementation, since geographic indices are not automatically created):

>>> qs = District.objects.geo_filter(poly__bbcontains='POINT(-95.362293 29.756539)').geo_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.

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
    $ ./configure --enable-python
    $ make
    # make install
    

PROJ

  • Latest PROJ version is 4.5.0
    $ ./configure
    $ make
    # make install 
    

PostGIS

  • Latest PostGIS version is 1.2.1
  • First build & install PostGIS. We are currently using v8.1 of PostgreSQL.
    $ ./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.
  • Latest GDAL version is 1.4.0. Configure with GEOS and Python support, then make and install:
    $ ./configure --with-geos --with-python
    $ make
    # make install
    
  • Note: This is done without the 'next generation' SWIG Python bindings. I've had trouble getting them to work, and the rumor is this only works on Windows. The compilation flag to enable these is --with-ngpython, but our packages currently only use the old bindings.

Model API

Field Keywords

  • Field keywords are used during model creation, for example:
    class Zip(models.Model):
      code = models.IntegerField()
      poly = models.PolygonField(srid=-1, index=True)
    
  • srid
    • Sets the SRID of geometry to the value. Defaults to 4326 (WGS84)
  • index
    • If set to True, will create an index for the given geometry.
    • Disabled. Implemented, but there's a bug and won't allow syncdb to execute.

Fields

The following geometry-enabled fields are available:

  • PointField
  • LineStringField (bug in current version has this as LineString, will be fixed)
  • PolygonField
  • MultiPointField
  • MultiLineStringField
  • MultiPolygonField
  • GeometryCollectionField

Creating a Geometry Object and Saving

Here is an example of how to create a geometry object (assuming the Zip model example above). Geometries can be represented in either the WKT (Well Known Text) format, or in HEXEWKB (PostGIS specific, essentially a WKB geometry in hexadecimal). 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); see also PostGIS EWKB, EWKT and Canonical Forms, PostGIS documentation at Ch. 4.1.2].

>>> from zipcode.models import Zip
>>> z = Zip(code=77096, poly='POLYGON(( 10 10, 10 20, 20 20, 20 15, 10 10))')
>>> z.save()