Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Getting Started with GeoDjango

Adam Fast
October 27, 2012

Getting Started with GeoDjango

The briefing I wish had existed before I started - tips and tricks to get you going.

Adam Fast

October 27, 2012
Tweet

More Decks by Adam Fast

Other Decks in Programming

Transcript

  1. Getting Started with GeoDjango Teaching web apps “where” Adam Fast

    pyArkansas 2012 Saturday, October 27, 12 This is the how attempting to share most everything I can remember wishing I’d known The pep talk comes at 1:30 in Room 103
  2. Disclaimers • Many of these techniques are not acceptable for

    instances where high levels of accuracy are required. • I am not formally trained in geospatial, mapmaking, or surveying. • I’m offering my opinion on techniques, use and apply at your own risk. Saturday, October 27, 12
  3. What is GeoDjango? • ctypes wrappers for GDAL, PROJ, GEOS

    • GDAL (geospatial data abstraction lib) • PROJ.4 (cartographic projections lib) • GEOS (geometry engine open source) • Geospatial database backend • Admin enhancements • OSMGeoAdmin • GeoModelAdmin Saturday, October 27, 12 GDAL - raster translator library PROJ - dealing with projections and transformation GEOS - port of Java Topology Suite, deals with vector geometry for spatial data
  4. “OGC standard” • Open Geospatial Consortium • Standardizing body of

    Open Source GIS • For our purposes, defines “standards” for databases • PostGIS adheres to those standards • Spatialite “mostly” adheres • Oracle and ESRI have compliant status Saturday, October 27, 12 PostGIS as an open source project can’t spend money to get certified - but it matches the specs. What does this mean for us? It means these are our best bets.
  5. Saturday, October 27, 12 Compatibility tables Note MySQL is bounding

    box only and only on one storage backend Though there has been a blog post and MySQL 5.6 has true spatial relationship support http://www.bostongis.com/blog/index.php?/archives/192-MySQL-inches-closer-to- PostGIS-with-support-of-true-spatial-relationship-functions.html
  6. Queryset vs Geom • Some features only work on one

    layer or another • c1 = Campground.objects.get(pk=12) • c2 = Campground.objects.get(pk=13) • Campground.objects.get(pk=12).distance(c2.point)) • Queryset - Field ‘distance’ is a D object • c1.point.distance(c2.point) • GEOS Geom - result is in degrees Saturday, October 27, 12
  7. What happens when / where? • Some things happen in

    the database, others in the libraries • Without a spatial database, you can’t get a distance calculation from GeoDjango • GeoPy after the fact, we will look at this later Saturday, October 27, 12
  8. What is GeoDjango NOT? • Automatic data repository • Automatic

    maps • Driving directions engine • pgrouting (but not integrated) Saturday, October 27, 12
  9. Prerequisites • Ubuntu 11.10 / 12.04 • libgeos-3.2.2 proj postgis

    gdal-bin postgresql-9.1-postgis postgresql-9.1 libgdal1-dev make python-dev • Other Linuces - see docs • Mac OS X • Homebrew • KyngChaos Binaries • Source Saturday, October 27, 12
  10. So what’s different? • Model imports • Managers • Admin

    classes • Settings • Database adapter • INSTALLED_APPS Saturday, October 27, 12
  11. from django.db import models class Location(models.Model): name = models.CharField(max_length=32) latitude

    = models.DecimalField(max_digits=12, decimal_places=9) longitude = models.DecimalField(max_digits=12, decimal_places=9) Saturday, October 27, 12
  12. from django.db import models from django.contrib.gis.db import models class Location(models.Model):

    name = models.CharField(max_length=32) latitude = models.DecimalField(max_digits=12, decimal_places=9) longitude = models.DecimalField(max_digits=12, decimal_places=9) point = models.PointField() Saturday, October 27, 12
  13. from django.db import models from django.contrib.gis.db import models class Location(models.Model):

    name = models.CharField(max_length=32) latitude = models.DecimalField(max_digits=12, decimal_places=9) longitude = models.DecimalField(max_digits=12, decimal_places=9) point = models.PointField() objects = models.Manager() # or a custom manager? Saturday, October 27, 12
  14. from django.db import models from django.contrib.gis.db import models class Location(models.Model):

    name = models.CharField(max_length=32) latitude = models.DecimalField(max_digits=12, decimal_places=9) longitude = models.DecimalField(max_digits=12, decimal_places=9) point = models.PointField() objects = models.Manager() # or a custom manager? objects = models.GeoManager() # or custom manager inherits Saturday, October 27, 12
  15. from django.contrib import admin from location.models import Location class LocationAdmin(admin.ModelAdmin):

    list_display = (‘name’,) admin.site.register(Location, LocationAdmin) Saturday, October 27, 12
  16. from django.contrib import admin from django.contrib.gis import admin from location.models

    import Location class LocationAdmin(admin.ModelAdmin): class LocationAdmin(admin.GeoModelAdmin): # OpenLayers list_display = (‘name’,) admin.site.register(Location, LocationAdmin) Saturday, October 27, 12
  17. from django.contrib import admin from django.contrib.gis import admin from location.models

    import Location class LocationAdmin(admin.ModelAdmin): class LocationAdmin(admin.OSMGeoAdmin): # OpenStreetMap list_display = (‘name’,) admin.site.register(Location, LocationAdmin) Saturday, October 27, 12
  18. DATABASES = { ‘default’: { ‘ENGINE’: ‘django.db.backends.postgresql_psycopg2’, ‘ENGINE’: ‘django.contrib.gis.db.backends.postgis’, ‘ENGINE’:

    ‘django.contrib.gis.db.backends.spatialite’, ‘ENGINE’: ‘django.contrib.gis.db.backends.oracle’, ‘ENGINE’: ‘django.contrib.gis.db.backends.mysql’, [...] } } ** multi-db warning Saturday, October 27, 12
  19. Common Mistake #1: Wrong backend settings.DATABASES[‘default’][‘ENGINE’] = \ ' django.db.backends.postgresql_psycopg2'

    AttributeError: 'DatabaseOperations' object has no attribute 'geo_db_type' Saturday, October 27, 12
  20. Common Mistake #2: Non-spatial DB django.core.exceptions.ImproperlyConfigured: Cannot determine PostGIS version

    for database "pyarkansas". GeoDjango requires at least PostGIS version 1.3. Was the database created from a spatial database template? Saturday, October 27, 12
  21. INSTALLED_APPS = ( [...], ‘django.contrib.gis’, ) Saturday, October 27, 12

    Geospatial admin only - so it can find its templates
  22. Gathering Geospatial Data • Census Bureau • TIGER/LINE • American

    Indian reservations / trust lands / tribal subdivisions • Counties • School districts • States • Legislative and voting districts • Urban areas • Zip codes Saturday, October 27, 12 TIGER/LINE - gathering this data is their job SimpleGeo shut down, you can find an API from factual.com or download the JSON uscampgrounds
  23. Gathering Geospatial Data • Census Bureau cont • TIGER/LINE •

    Highways • Railroads • Water • Census / demographics https://github.com/adamfast/geodjango-tigerline Saturday, October 27, 12 TIGER/LINE - gathering this data is their job SimpleGeo shut down, you can find an API from factual.com or download the JSON uscampgrounds
  24. Gathering Geospatial Data • Local governments (city/county/state) • data.gov •

    GeoNames • Timezones • geodjango-timezones • SimpleGeo Places JSON • http://s3.amazonaws.com/simplegeo-public/places_dump_20110628.zip • geodjango-places • Google for it Saturday, October 27, 12 TIGER/LINE - gathering this data is their job SimpleGeo shut down, you can find an API from factual.com or download the JSON uscampgrounds
  25. Gathering Geospatial Data • SRTM Raster • DEM (Digital Elevation

    Model) • Vector vs raster • Raster data vs visual • Flickr Shapefiles Public Dataset • http://code.flickr.com/blog/2011/01/08/flickr-shapefiles-public- dataset-2-0/ • User generated Saturday, October 27, 12 TIGER/LINE - gathering this data is their job SimpleGeo shut down, you can find an API from factual.com or download the JSON uscampgrounds
  26. User Generated? • Textual geocoding* • Address to latitude/longitude •

    Logging • Hardware / software • GeoIP • Manual entry • HTML5 geolocation • Provided latitude / longitude Saturday, October 27, 12 Be careful with usage of information you geocode Hardware loggers used by OSM, commonly available, or use a smartphone / tablet
  27. >>> from geopy import geocoders >>> g = geocoders.Google() >>>

    place, (lat, lng) = g.geocode(“Conway, AR”) >>> place u'Conway, AR, USA' >>> lat 35.0886963 >>> lng -92.4421011 Saturday, October 27, 12
  28. >>> from django.contrib.gis.utils.geoip import GeoIP >>> GeoIP().city("amazon.com") {'city': 'Seattle', 'region':

    'WA', 'area_code': 206, 'longitude': -122.32839965820312, 'country_code3': 'USA', 'latitude': 47.60260009765625, 'postal_code': '98104', 'dma_code': 819, 'country_code': 'US', 'country_name': 'United States'} Saturday, October 27, 12 Makes a useful Django context processor http://blog.adamfast.com/2011/11/where-is-my-user-part-1-geoip/
  29. <script src='http://media.adamfast.com/QueryData.compressed.js'></script> <script language='JavaScript'> function checkLocation() { var getData =

    new QueryData(); if ('lat' in getData) { } else { if (navigator.geolocation) { navigator.geolocation.getCurrentPosition( function (ppos) { window.location.href = window.location.href + '?lat=' + ppos.coords.latitude + '&lon=' + ppos.coords.longitude; }, function (err) { switch(err.code) { case err.TIMEOUT: alert('Attempts to retrieve location timed out.') break; case err.POSITION_UNAVAILABLE: alert("Your browser doesn't know where you are.") break; case err.PERMISSION_DENIED: alert('You have to give us permission!') break; case err.UNKNOWN_ERROR: alert('Unknown error returned.') break; default: alert(err + ' ' + err.code) } } ); } } } </script> Saturday, October 27, 12 HTML5 geolocation - bounces you back to the URL with parameters WATCH OUT - will be FAR more precise than your GPS is. Remember significant figures
  30. WARNING • JavaScript floating point - now I have .9999999999

    problems because my cache didn’t hit • This much precision (14 decimal places) is 1.1 nanometers, your computer CPU is likely a 32 nanometers process. Human hair 40,000 nanometers. • Remember significant figures - GPS will barely give 1m • 2 digits = 1111m / 3 digits = 111m (1/4 block) / 4 digits = 11.1m / 5 digits 1.1m (*at equator) http://localhost:8000/campgrounds/?lat=38.9591749&lon=-95.32675689999999 Saturday, October 27, 12 block ins’t a formal unit 2 digits ~0.69 miles, 3 digits ~360ft, 4 digits ~36ft, 5 digits ~3.6ft
  31. Importing Spatial Data • Shapefiles • TIGER/Line example • CSV

    • uscampgrounds.info example • Initializing GEOS geometries • Warning: longitude, latitude Saturday, October 27, 12 There isn’t much you can’t do with pre-save / overridden save() to build out data that wasn’t in the shapefile
  32. Project 1: Locator • You’ve used them before - now

    let’s build one • For simplicity, Zip Code only • TIGER/LINE • geodjango-tigerline https://github.com/adamfast/geodjango-tigerline • GeoPy • Faking spatial Saturday, October 27, 12
  33. Project 2: Disaster Recovery • Let’s assume communications infrastructure is

    intact and not overwhelmed • Browser-based app w/HTML5 geolocation • Responders check in with location and status • Results are displayed on a map dispatch console Saturday, October 27, 12
  34. Project 3: Radio Frequency Allocation • Repeater “pairs” are allocated

    by geographic region • The data is openly published • Most coordinators allocate by a fixed distance • It’s complicated to keep track of • Perfect problem for a computer • Weekend to build, could revolutionize for a subset • But due to community politics, unlikely to go anywhere Saturday, October 27, 12
  35. Faking it • Why? • Platform (Heroku, shared hosting, MySQL,

    etc) • Need “real” search i.e. solr • No longer. Solr and Elastic Search both do spatial • Avoiding library dependencies Saturday, October 27, 12
  36. Faking it • Ghetto bounding box • Filtering down via

    nonspatial database • GeoPy • VincentyDistance • GeoHash • What about Solr / ElasticSearch? • django-haystack has you covered Saturday, October 27, 12
  37. World is not flat • Proven by C. Columbus in

    1492 • Longitude: Degrees away from the Prime Meridian (-180 to 180) • Latitude: Degrees away from the Equator (-90 to 90) Saturday, October 27, 12
  38. Lines of Latitude (H / y) • Constant Distance •

    111.325 km / 69.172 mi per degree • Earth’s circumference, 24,900 mi / 360 deg http://www.nationalatlas.gov/articles/mapping/a_latlong.html Saturday, October 27, 12
  39. Lines of Longitude (V / x) • Lengths vary •

    cos(__latitude__) * 69.172 mi (or 111.325km) http://www.nationalatlas.gov/articles/mapping/a_latlong.html Saturday, October 27, 12
  40. Projections • We can only get “close enough” • Global

    is not going to be accurate • National is not going to be accurate • Cities and States COMMONLY have their own • spatialreference.org has 165 pages of 50 per page Saturday, October 27, 12 So now that we have a little theory, let’s see how not to do it
  41. Faking it • django-cms-storelocator example • Why is this approach

    bad? 50mi results in a 50 degree +/- • Degrees • The world is not flat • 1 degree longitude = constant • 1 degree latitude - variable from equator to pole Saturday, October 27, 12
  42. Faking it US: 24 deg N to 48 deg N

    67 deg W to 124 deg W Saturday, October 27, 12 This method includes 20 degrees (the height of the midwest) of results for a 20 mile query Then it uses Python to calculate a more accurate measurement and exclude as necessary
  43. Faking it • Always look and ask “does this make

    sense?” • Debug in Google Earth - slow, but good to visualize • Pre-filtering is the right idea • I would use GeoPy when calculating distances • It is BSD. Attribute / acknowledge and bundle. Saturday, October 27, 12
  44. import math def latitude_degrees(miles): return miles / 69.172 # very

    little variance here. a degree is approximately 69.172 miles def longitude_degrees(latitude, miles): return miles / (math.cos(math.radians(latitude)) * 69.172) # dist is cosine of the latitude mult by length of latitude def basic_bounding_box(center_lat, center_lon, radius_mi, wiggle_room=None): if wiggle_room is not None: radius_mi = radius_mi + wiggle_room off_center = radius_mi / 2 # since the center is the center, we only add 1/2 the range to each end start_lat = center_lat - latitude_degrees(off_center) end_lat = center_lat + latitude_degrees(off_center) start_lon = center_lon - longitude_degrees(center_lat, off_center) end_lon = center_lon + longitude_degrees(center_lat, off_center) return ( (start_lat, start_lon), (end_lat, end_lon), ) Saturday, October 27, 12 not going too deeply into this
  45. Faking it • Ghetto bounding box • Filtering down via

    nonspatial database • GeoPy • VincentyDistance • GeoHash • What about Solr / ElasticSearch? • django-haystack has you covered Saturday, October 27, 12
  46. Faking it • Ghetto bounding box • Filtering down via

    nonspatial database • GeoPy • VincentyDistance • GeoHash • What about Solr / ElasticSearch? • django-haystack has you covered Saturday, October 27, 12
  47. Faking It: GeoHash • Accuracy backs out as you have

    fewer characters • 9yum8 will match 9yum8yef3vds6 (Lawrence, KS) • Store in a CharField and use __startswith in a normal QuerySet • Items just on the line are missed. .expand() will grow the box to include neighbors • Pass a precision= kwarg into .encode() to specify how far the hash should go Saturday, October 27, 12
  48. Faking It: GeoHash • 1 char +/- 2500km • 2

    char +/- 630km • 3 char +/- 78km • 4 char +/- 20km • 5 char +/- 2.4km • 6 char +/- 0.61km (610m) • 7 char +/- 0.076km (76m) • 8 char +/- 0.019km (19m) Saturday, October 27, 12
  49. Faking It: GeoHash • Accuracy backs out as you have

    fewer characters • 9yum8 will match 9yum8yef3vds6 (Lawrence, KS) • Store in a CharField and use __startswith in a normal QuerySet • Items just on the line are missed. .expand() will grow the box to include neighbors • Pass a precision= kwarg into .encode() to specify how far the hash should go Saturday, October 27, 12
  50. >>> import geohash >>> geohash.encode(38.9716689, -95.2352501) '9yum8yef3vds' >>> geohash.expand('9yum8yef3vds') ['9yum8yef3vdk',

    '9yum8yef3vdu', '9yum8yef3vde', '9yum8yef3vd7', '9yum8yef3vdg', '9yum8yef3vdt', '9yum8yef3vdm', '9yum8yef3vdv', '9yum8yef3vds'] >>> from django.db.models import Q q_object = Q() >>> for hash in geohash.expand('9yum8yef3vds'): ... q_object.add(Q(geohash__startswith=hash), Q.OR) >>> print(q_object) (OR: ('geohash__startswith', '9yum8yef3vdk'), ('geohash__startswith', '9yum8yef3vdu'), ('geohash__startswith', '9yum8yef3vde'), ('geohash__startswith', '9yum8yef3vd7'), ('geohash__startswith', '9yum8yef3vdg'), ('geohash__startswith', '9yum8yef3vdt'), ('geohash__startswith', '9yum8yef3vdm'), ('geohash__startswith', '9yum8yef3vdv'), ('geohash__startswith', '9yum8yef3vds')) Saturday, October 27, 12
  51. Faking It: GeoHash • Accuracy backs out as you have

    fewer characters • 9yum8 will match 9yum8yef3vds6 (Lawrence, KS) • Store in a CharField and use __startswith in a normal QuerySet • Items just on the line are missed. .expand() will grow the box to include neighbors • Pass a precision= kwarg into .encode() to specify how far the hash should go Saturday, October 27, 12
  52. >>> geohash.encode(38.9716689, -95.2352501, precision=4) '9yum' >>> geohash.expand('9yum') ['9yuj', '9yut', '9yuk',

    '9yuh', '9yus', '9yuq', '9yun', '9yuw', '9yum'] >>> q_object = Q() >>> for hash in geohash.expand('9yum'): ... q_object.add(Q(geohash__startswith=hash), Q.OR) ... >>> print(q_object) (OR: ('geohash__startswith', '9yuj'), ('geohash__startswith', '9yut'), ('geohash__startswith', '9yuk'), ('geohash__startswith', '9yuh'), ('geohash__startswith', '9yus'), ('geohash__startswith', '9yuq'), ('geohash__startswith', '9yun'), ('geohash__startswith', '9yuw'), ('geohash__startswith', '9yum')) Saturday, October 27, 12
  53. Faking it • Ghetto bounding box • Filtering down via

    nonspatial database • GeoPy • VincentyDistance • GeoHash • What about Solr / ElasticSearch? • django-haystack has you covered Saturday, October 27, 12 haystack has a very similar to GeoDjango querysets API by design, uses GeoPy on the backend when necessary
  54. Displaying on a map • No real batteries here •

    There is a “hidden” Google Maps generator in GeoDjango, but it’s API v2 • You’ll spend a LOT more time reading source code than you would doing a Google Maps v3 template http://blog.adamfast.com/2011/11/on-the-map/ Saturday, October 27, 12
  55. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> {{ google.xhtml

    }} <head> <title>Google Maps via GeoDjango</title> {{ google.style }} {{ google.scripts }} </head> {{ google.body }} <div id="{{ google.dom_id }}" style="width:600px;height: 400px;"></div> </body> </html> Saturday, October 27, 12 base.html
  56. from django.contrib.gis.geos import Point from django.contrib.gis.maps.google import GoogleMap, GMarker, GPolygon

    from django.contrib.gis.measure import D from uscampgrounds.models import Campground def gmap2(request): ! campgrounds = \ Campground.objects.filter(point__distance_lte=(Point((-94, 37)), \ D(mi=100))) ! markers = [] ! for campground in campgrounds: ! ! markers.append(GMarker(campground.point, title=campground.name)) ! the_map = GoogleMap(markers=markers) ! return render_to_response('googlemap.html', { ! ! 'google': the_map, ! }, context_instance=RequestContext(request)) Saturday, October 27, 12 views.py This way won’t get us click events on markers or anything - to do that you actually have to pass in the queryset to the view too and then re-loop over it and define things and auto- generate what would have been generated - marker0 etc
  57. Displaying on a map • Google Maps API v3 •

    More ponies • No API keys • Still pretty easy http://blog.adamfast.com/2011/11/mapping-better-google-maps-api-v3/ http://blog.adamfast.com/2011/11/polygons-and-overlays-with-google-maps-v3-api/ Saturday, October 27, 12
  58. from uscampgrounds.models import Campground def gmap3(request): campgrounds = Campground.objects.filter(point__distance_lte=(Point((-94, 37)),

    \ D(mi=100))) return render_to_response('gmap3.html', { 'object_list': campgrounds, }, context_instance=RequestContext(request)) Saturday, October 27, 12 views.py
  59. <html> <head> <title>{% block page-title %}Simple Google Maps API v3{%

    endblock %}</title> <script type="text/javascript" src="http://maps.google.com/maps/ api/js?sensor=false"></script> {% block head_override %}{% endblock head_override %} </head> <body{% block body_override %}{% endblock body_override %}> {% block content %} {% endblock content %} </body> </html> Saturday, October 27, 12 gmap3base.html
  60. {% extends "gmap3base.html" %} {% block page-title %}Requested Items{% endblock

    %} {% block head_override %} <script type="text/javascript"> var bounds = new google.maps.LatLngBounds(); function buildMarker(map, latitude, longitude, name, color) { var latlng = new google.maps.LatLng(latitude, longitude); var marker = new google.maps.Marker({ position: latlng, map: map, title:name, }); marker.setIcon('http://maps.google.com/mapfiles/ms/icons/' + color + '-dot.png'); bounds.extend(latlng); return marker } function mapInitialize() { var myOptions = { zoom: 6, center: new google.maps.LatLng(0, 0), mapTypeId: google.maps.MapTypeId.SATELLITE }; var map = new google.maps.Map(document.getElementById("map_canvas"), myOptions); {% for object in object_list %}marker{{ object.pk }} = buildMarker(map, {{ object.point.y }}, {{ object.point.x }}, "{{ object }}", 'red'); {% endfor %} map.fitBounds(bounds); } </script> {% endblock head_override %} Saturday, October 27, 12 gmap3base.html (part 1)
  61. {# ...^^^... continued from above #} {% endblock head_override %}

    {% block body_override %} onload="mapInitialize()"{% endblock body_override %} {% block content %} <div id="map_canvas" style='width:600px;height:400px;'></div> {% endblock content %} Saturday, October 27, 12 gmap3base.html (part 2)
  62. Displaying on a map • OpenLayers • Abstraction layer for

    web slippy mapping • Leaflet • Mobile-focused abstraction layer web slippy mapping by CloudMade • Making no recommendations, but they have geo data APIs available as well • Mapstraction Saturday, October 27, 12 don’t tie to a specific basemap Google, OSM, your own custom
  63. Displaying on a map • Google Maps • OpenStreetMap •

    Community generated / edited, but with a TIGER/ LINE start in the US • TileMill • Combine all sorts of data sources to build whatever you want for a basemap • Export and host yourself or have it hosted Saturday, October 27, 12
  64. Performance • __distance_lte • Very useful - but slow •

    Buffer a point into a circle, then use __within • Transform to a meters-based coordinate system • Which one? What area are you in? • 1 mi = 1609km. Why 2172.344? Saturday, October 27, 12
  65. >>> Campground.objects.filter(point__distance_lte=(lawrence, \ D(mi=20))).distance(lawrence).order_by('distance') [<Campground: Clinton State Park>, <Campground: Bloomington

    Pubilc Use Area - Clinton Lake>, <Campground: Rockhaven - Clinton Lake>, <Campground: Lone Star Lake Park>, <Campground: Slough Creek - Perry Lake>, <Campground: Perry State Park>] 6.3ms Saturday, October 27, 12
  66. Performance • __distance_lte • Very useful - but slow •

    Buffer a point into a circle, then use __within • Transform to a meters-based coordinate system • Which one? What area are you in? • 1 mi = 1609km. Why 2172.344? Saturday, October 27, 12
  67. >>> lawrence.transform(900913) >>> lawrence_poly = lawrence.buffer(20 * 2172.344) >>> Campground.objects.filter(point__within=lawrence_poly)

    \ .distance(lawrence).order_by('distance') [<Campground: Clinton State Park>, <Campground: Bloomington Pubilc Use Area - Clinton Lake>, <Campground: Rockhaven - Clinton Lake>, <Campground: Lone Star Lake Park>, <Campground: Slough Creek - Perry Lake>, <Campground: Perry State Park>, <Campground: Old Town - Perry Lake>] 0.125ms Saturday, October 27, 12
  68. Performance • .defer() complex geometries • Add it to the

    end of your queryset to prevent that field from being loaded and transferred • Or override get_query_set() on the primary manager to ensure it’s never sent without being requested. Saturday, October 27, 12 This one’s silent - because the delay will happen on your DB end and over the wire Watch network traffic on database servers after deploying GeoDjango apps
  69. Cross-Platform Databases • postgis_restore.pl will be invaluable • pg_dump -Fc

    database > db.dump • copy db.dump to other machine • createdb -T template_postgis database • postgis_restore.pl db.dump | psql database Saturday, October 27, 12
  70. GeoDjango Errors • “DatabaseError: Coordinate values are out of range

    [-180 -90, 180 90] for GEOGRAPHY type” • Check the ordering of your arguments - you’re thinking what normal people do; latitude, longitude instead of the geospatial standard longitude, latitude Saturday, October 27, 12
  71. Tangent: QGIS Saturday, October 27, 12 Let’s chase a quick

    squirrel. All sorts of orgs love quick maps to show things. 100+ points on web maps gets unwieldy, and some may have copyright issues. There’s a better way.
  72. Saturday, October 27, 12 You want an app called QGIS.

    I run the KyngChaos version. First select the option to add a new raster layer (raster and vector are the two types, rasters have pixels)
  73. Saturday, October 27, 12 Grab a base layer. I commonly

    use NASA’s “blue marble” GeoTIFF. This one’s pretty low resolution though, and I can’t find a reference for it specifically. But as government data the NASA versions are public domain.
  74. Basemaps • NASA’s “blue marble” • http://earthobservatory.nasa.gov/Features/ BlueMarble/BlueMarble_2002.php • NASA’s

    “earth’s city lights” • http://visibleearth.nasa.gov/view.php?id=55167 • http://www.unearthedoutdoors.net/global_data/ true_marble/download Saturday, October 27, 12
  75. Saturday, October 27, 12 Here we are with the raster

    on screen. Satellite imagery is always discussed in “meters”. What does that mean? 1 pixel = x meters. The best quality you’ll find in a single image is 2km. (and that’s an 800MB image, 21600x10800) Now we’ll add a PostGIS database layer
  76. Saturday, October 27, 12 Choose the geometry you want as

    a layer (it will display all geometries from all tables on here)
  77. Saturday, October 27, 12 Now we have marks on the

    map, but it auto-chooses colors so you normally need to go tune.
  78. Saturday, October 27, 12 Now we have a map. With

    TIGER/Line we can drop cities with a population greater than _x_ or anything else we want.
  79. Saturday, October 27, 12 Click change on the page that

    comes up and fill style: no brush Border width depends on your use case.
  80. Saturday, October 27, 12 Right click the layer and choose

    query, this shows up Write your where, click test then ok.
  81. Saturday, October 27, 12 Now you have all > 100k

    population cities labeled. You’ll have to tune. On a 1024 wide screen the text’s not legible. 27” imac doesn’t have that problem.
  82. Deployment • Aside from a geographic database and some basic

    C lib dependencies, it deploys like any other Django project • PAAS • Heroku (expect $200/mo for PostGIS) • dotCloud (no extra for PostGIS) • RedHat OpenShift (no extra for PostGIS) • Shared • WebFaction has the libs and free PostGIS Saturday, October 27, 12
  83. In the real world • GeoDjango has tons of uses

    all around us • Beware the law of the instrument • Don’t just be __x__ with location. Be special. • “Mapping the News” • Analyzing fatal automobile accidents for proximity to bars • Comparing low income / standardized housing to large solutions of pollution Saturday, October 27, 12
  84. • Geographic Models • Importing Shapefiles • Importing lower-tech files

    • Basic poly/point queries • Geocoding • Distance Search • Radius Limited Searches • GeoIP User Location • Browser Geolocation • Faking spatial search • Slippy maps (Google v2/ v3 • Finding Data • Customizing the admin map (layers too) • Geo-front-end input All this and more... http://blog.adamfast.com Saturday, October 27, 12