The Web map stack on Django

Download The Web map stack on Django

Post on 05-Dec-2014

7.885 views

Category:

Technology

0 download

Embed Size (px)

DESCRIPTION

My EuroDjangoCon talk about how EveryBlock has used Mapnik and GeoDjango to create our own maps.

TRANSCRIPT

<ul><li> 1. The Web map stack on Django Paul Smith http://www.pauladamsmith.com/ @paulsmith EveryBlock EuroDjangoCon 09 </li> <li> 2. Data types </li> <li> 3. 11 metros Boston Philadelphia Charlotte San Francisco Chicago San Jose Los Angeles Seattle Miami Washington, DC New York and growing </li> <li> 4. Open source This summer </li> <li> 5. Why? </li> <li> 6. Control design </li> <li> 7. Prioritize visualizations </li> <li> 8. The Web map stack </li> <li> 9. The Web map stack </li> <li> 10. The Web map stack </li> <li> 11. The Web map stack </li> <li> 12. The Web map stack </li> <li> 13. GeoDjango + Mapnik example app Your Political Footprint </li> <li> 14. Mapnik overview </li> <li> 15. # models.py from django.contrib.gis.db import models class CongressionalDistrict(models.Model): state = models.ForeignKey(State) name = models.CharField(max_length=32) # ex. 1st, 25th, at-large number = models.IntegerField() # 0 if at-large district = models.MultiPolygonField(srid=4326) objects = models.GeoManager() def __unicode__(self): return '%s %s' % (self.state.name, self.name) class Footprint(models.Model): location = models.CharField(max_length=200) point = models.PointField(srid=4326) cong_dist = models.ForeignKey(CongressionalDistrict) objects = models.GeoManager() def __unicode__(self): return '%s in %s' % (self.location, self.cong_dist) </li> <li> 16. GET /tile/?bbox=-112.5,22.5,-90,45 </li> <li> 17. # urls.py from django.conf import settings from django.conf.urls.defaults import * from edc_demo.footprint import views urlpatterns = patterns('', (r'^footprint/', views.political_footprint), (r'^tile/', views.map_tile) ) </li> <li> 18. # views.py from mapnik import * from django.http import HttpResponse, Http404 from django.conf import settings from edc_demo.footprint.models import CongressionalDistrict TILE_WIDTH = TILE_HEIGHT = 256 TILE_MIMETYPE = 'image/png' LIGHT_GREY = '#C0CCC4' PGIS_DB_CONN = dict( host=settings.DATABASE_HOST, dbname=settings.DATABASE_NAME, user=settings.DATABASE_USER, password=settings.DATABASE_PASSWORD) def map_tile(request): if request.GET.has_key('bbox'): bbox = [float(x) for x in request.GET['bbox'].split(',')] tile = Map(TILE_WIDTH, TILE_HEIGHT) rule = Rule() rule.symbols.append(LineSymbolizer(Color(LIGHT_GREY), 1.0)) style = Style() style.rules.append(rule) tile.append_style('cong_dist', style) layer = Layer('cong_dists') db_table = CongressionalDistrict._meta.db_table layer.datasource = PostGIS(table=db_table, **PGIS_DB_CONN) layer.styles.append('cong_dist') tile.layers.append(layer) tile.zoom_to_box(Envelope(*bbox)) img = Image(tile.width, tile.height) render(tile, img) img_bytes = img.tostring(TILE_MIMETYPE.split('/')[1]) return HttpResponse(img_bytes, mimetype=TILE_MIMETYPE) else: raise Http404() </li> <li> 19. # views.py cont'd from django.shortcuts import render_to_response from edc_demo.footprint.geocoder import geocode def political_footprint(request): context = {} if request.GET.has_key('location'): point = geocode(request.GET['location']) cd = CongressionalDistrict.objects.get(district__contains=point) footprint = Footprint.objects.create( location = request.GET['location'], point = point, cong_dist = cd ) context['footprint'] = footprint context['cd_bbox'] = cong_dist.district.extent return render_to_response('footprint.html', context) </li> <li> 20. // footprint.html </li> <li> 21. # views.py from edc_demo.footprint.models import Footprint def map_tile(request): if request.GET.has_key('bbox'): bbox = [float(x) for x in request.GET['bbox'].split(',')] tile = Map(TILE_WIDTH, TILE_HEIGHT) rule = Rule() rule.symbols.append(LineSymbolizer(Color(LIGHT_GREY), 1.0)) style = Style() style.rules.append(rule) if request.GET.has_key('fp_id'): footprint = Footprint.objects.get(pk=request.GET['fp_id']) rule = Rule() rule.symbols.append(LineSymbolizer(Color(GREEN), 1.0)) rule.symbols.append(PolygonSymbolizer(Color(LIGHT_GREEN))) rule.filter = Filter('[id] = ' + str(footprint.cong_dist.id)) style.rules.append(rule) tile.append_style('cong_dist', style) layer = Layer('cong_dists') db_table = CongressionalDistrict._meta.db_table layer.datasource = PostGIS(table=db_table, **PGIS_DB_CONN) layer.styles.append('cong_dist') tile.layers.append(layer) if request.GET.has_key('fp_id'): add_footprint_layer(tile, footprint) tile.zoom_to_box(Envelope(*bbox)) img = Image(tile.width, tile.height) render(tile, img) img_bytes = img.tostring(TILE_MIMETYPE.split('/')[1]) return HttpResponse(img_bytes, mimetype=TILE_MIMETYPE) else: raise Http404() </li> <li> 22. # views.py cont'd def add_footprint_layer(tile, footprint): rule = Rule() rule.symbols.append( PointSymbolizer( os.path.join(settings.STATIC_MEDIA_DIR, 'img', 'footprint.png'), 'png', 46, 46) ) rule.filter = Filter('[id] = ' + str(footprint.id)) style = Style() style.rules.append(rule) tile.append_style('footprint', style) layer = Layer('footprint') layer.datasource = PostGIS(table=Footprint._meta.db_table, **PGIS_DB_CONN) layer.styles.append('footprint') tile.layers.append(layer) </li> <li> 23. Serving tiles </li> <li> 24. Zoom levels </li> <li> 25. Tile example z: 5, x: 2384, y: 1352 </li> <li> 26. TileCache pro con Cache population Python overhead integrated with (rendering, serving) request/response cycle Flexible storage </li> <li> 27. Pre-render + custom nginx mod pro con Fast responses Render everything in advance Parallelizable, offline rendering C module inflexibility (esp. storage backends) </li> <li> 28. Tile rendering for each zoom level z: for each column x: for each row y: render tile (x, y, z) </li> <li> 29. Tile rendering for each zoom level z: for each column x: for each row y: render tile (x, y, z) </li> <li> 30. Tile rendering for each zoom level z: for each column x: for each row y: render tile (x, y, z) </li> <li> 31. Tile rendering for each zoom level z: for each column x: for each row y: render tile (x, y, z) </li> <li> 32. # nginx.conf server { server_name tile.example.com root /var/www/maptiles; expires max; location ~* ^/[^/]+/w+/d+/d+,d+.(jpg|gif|png)$ { tilecache; } } </li> <li> 33. // ngx_tilecache_mod.c /* * This struct holds the attributes that uniquely identify a map tile. */ typedef struct { u_char *version; u_char *name; int x; int y; int z; u_char *ext; } tilecache_tile_t; /* * The following regex pattern matches the request URI for a tile and * creates capture groups for the tile attributes. Example request URI: * * /1.0/main/8/654,23.png * * would map to the following attributes: * * version: 1.0 * name: main * z: 8 * x: 654 * y: 23 * extension: png */ static ngx_str_t tile_request_pat = ngx_string(quot;^/([^/]+)/([^/]+)/([0-9]+)/([0-9]+),([0-9]+).([a-z]+)$quot;); </li> <li> 34. // ngx_tilecache_mod.c u_char * get_disk_key(u_char *s, u_char *name, int x, int y, int z, u_char *ext) { u_int a, b, c, d, e, f; a = x / 100000; b = (x / 1000) % 1000; c = x % 1000; d = y / 100000; e = (y / 1000) % 1000; f = y % 1000; return ngx_sprintf(s, quot;/%s/%02d/%03d/%03d/%03d/%03d/%03d/%03d.%squot;, name, z, a, b, c, d, e, f, ext); } static ngx_int_t ngx_tilecache_handler(ngx_http_request_t *r) { // ... snip ... sub_uri.data = ngx_pcalloc(r-&gt;pool, len + 1); if (sub_uri.data == NULL) { return NGX_ERROR; } get_disk_key(sub_uri.data, tile-&gt;name, tile-&gt;x, tile-&gt;y, tile-&gt;z, tile-&gt;ext); sub_uri.len = ngx_strlen(sub_uri.data); return ngx_http_internal_redirect(r, &amp;sub_uri, &amp;r-&gt;args); } </li> <li> 35. Custom tile cache technique responsibility Far-future expiry Tile versions for cache header expires max; invalidation </li> <li> 36. // everyblock.js eb.TileLayer = OpenLayers.Class(OpenLayers.Layer.TMS, { version: null, // see eb.TILE_VERSION layername: null, // lower-cased: quot;mainquot;, quot;locatorquot; type: null, // i.e., mime-type extension: quot;pngquot;, quot;jpgquot;, quot;gifquot; initialize: function(name, url, options) { var args = []; args.push(name, url, {}, options); OpenLayers.Layer.TMS.prototype.initialize.apply(this, args); }, // Returns an object with the x, y, and z of a tile for a given bounds getCoordinate: function(bounds) { bounds = this.adjustBounds(bounds); var res = this.map.getResolution(); var x = Math.round((bounds.left - this.tileOrigin.lon) / (res * this.tileSize.w)); var y = Math.round((bounds.bottom - this.tileOrigin.lat) / (res * this.tileSize.h)); var z = this.map.getZoom(); return {x: x, y: y, z: z}; }, getPath: function(x, y, z) { return this.version + quot;/quot; + this.layername + quot;/quot; + z + q...</li></ul>