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
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
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.
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
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
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
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
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
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
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
for database "pyarkansas". GeoDjango requires at least PostGIS version 1.3. Was the database created from a spatial database template? Saturday, October 27, 12
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
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
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
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
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
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
• 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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
\ .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
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
[-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
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.
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.
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
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
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