Package madrona :: Package staticmap :: Module views
[hide private]

Source Code for Module madrona.staticmap.views

  1  import mapnik 
  2  import settings 
  3  from django.http import HttpResponse, HttpResponseRedirect, HttpResponseBadRequest, HttpResponseServerError, HttpResponseForbidden, Http404 
  4  from django.template import RequestContext 
  5  from django.shortcuts import get_object_or_404, render_to_response 
  6  from django.db import connection 
  7  from madrona.common import default_mimetypes as mimetypes 
  8  from madrona.common import utils 
  9  from madrona.staticmap.models import MapConfig 
 10  from madrona.features import get_feature_models, get_collection_models, get_feature_by_uid, get_model_by_uid 
 11  from madrona.features.models import FeatureCollection, SpatialFeature, PointFeature, PolygonFeature, LineFeature 
 12  from djmapnik.adapter import PostgisLayer  
 13  from madrona.common.utils import get_logger 
 14  from django.template.defaultfilters import slugify 
 15  log = get_logger() 
 16   
 17  try: 
 18      settings_dbname = settings.DATABASES['default']['NAME'] 
 19  except: 
 20      settings_dbname = settings.DATABASE_NAME 
 21   
22 -def default_style():
23 default_style = mapnik.Style() 24 ps = mapnik.PolygonSymbolizer(mapnik.Color('#ffffff')) 25 ps.fill_opacity = 0.5 26 ls = mapnik.LineSymbolizer(mapnik.Color('#555555'),0.75) 27 ls.stroke_opacity = 0.5 28 r = mapnik.Rule() 29 r.symbols.append(ps) 30 r.symbols.append(ls) 31 r.symbols.append(mapnik.PointSymbolizer()) 32 default_style.rules.append(r) 33 return default_style
34
35 -def get_features(uids,user):
36 """ 37 Returns list of tuples representing mapnik layers 38 Tuple => (model_class, [pks]) 39 Note: currently just a single pk per 'layer' which is 40 incredibly inefficient but the only way to ensure 41 proper layer ordering (??). 42 features = [ (Mpa, [49, 50]), 43 (Pipeline, [32, 31]), 44 (Shipwreck, [32, 31]) 45 ] 46 """ 47 feature_models = get_feature_models() 48 collection_models = get_collection_models() 49 features = [] # list of tuples => (model, [pks]) 50 for uid in uids: 51 log.debug("processing uid %s" % uid) 52 applabel, modelname, pk = uid.split('_') 53 model = get_model_by_uid("%s_%s" % (applabel,modelname)) 54 feature = get_feature_by_uid(uid) 55 56 if user: 57 viewable, response = feature.is_viewable(user) 58 if not viewable: 59 continue 60 61 if model in collection_models: 62 collection = get_feature_by_uid(uid) 63 if user: 64 viewable, response = collection.is_viewable(user) 65 if not viewable: 66 continue 67 all_children = collection.feature_set(recurse=True) 68 children = [x for x in all_children if x.__class__ in feature_models] 69 for child in children: 70 features.append((child.__class__,[child.pk])) 71 else: 72 features.append((model,[int(pk)])) 73 74 return features
75
76 -def auto_extent(features,srid=settings.GEOMETRY_CLIENT_SRID):
77 """ 78 Given a set of staticmap features, 79 returns the bounding box required to zoom into those features. 80 Includes a configurable edge buffer 81 """ 82 minx = 99999999.0 83 miny = 99999999.0 84 maxx = -99999999.0 85 maxy = -99999999.0 86 for model, pks in features: 87 try: 88 geomfield = model.mapnik_geomfield() 89 except AttributeError: 90 geomfield = 'geometry_final' 91 92 try: 93 ugeom = model.objects.filter(pk__in=pks).collect(field_name=geomfield).transform(srid,clone=True) 94 bbox = ugeom.extent 95 if bbox[0] < minx: 96 minx = bbox[0] 97 if bbox[1] < miny: 98 miny = bbox[1] 99 if bbox[2] > maxx: 100 maxx = bbox[2] 101 if bbox[3] > maxy: 102 maxy = bbox[3] 103 except TypeError as e: 104 log.error("Failed to get extent for %r with pks %r; Exception: \n%s" % (model, pks, e)) 105 pass 106 107 width = maxx - minx 108 height = maxy - miny 109 buffer = .15 110 # if width and height are 0 (such as for a point geom) 111 # we need to take a stab a reasonable value 112 if width == 0: 113 if bbox[2] <= 180.1: 114 width = 0.1 115 else: 116 width = 1000 117 if height == 0: 118 if bbox[3] <= 90.1: 119 height = 0.1 120 else: 121 height = 1000 122 123 # If the following settings variables are not defined (or set to None), then the original method 124 # for determining width_buffer and heigh_buffer is used 125 try: 126 if settings.STATICMAP_WIDTH_BUFFER is not None and settings.STATICMAP_HEIGHT_BUFFER is not None: 127 width_buffer = settings.STATICMAP_WIDTH_BUFFER 128 height_buffer = settings.STATICMAP_HEIGHT_BUFFER 129 else: 130 raise AttributeError 131 except AttributeError: 132 width_buffer = width * buffer 133 height_buffer = height * buffer 134 135 return minx - width_buffer, miny - height_buffer, maxx + width_buffer, maxy + height_buffer
136 137 157 158
159 -def show(request, map_name="default"):
160 # Grab the image dimensions 161 try: 162 width = int(request.REQUEST['width']) 163 height = int(request.REQUEST['height']) 164 except: 165 # fall back on defaults 166 width, height = None, None 167 168 if 'uids' in request.REQUEST: 169 uids = str(request.REQUEST['uids']).split(',') 170 else: 171 uids = [] 172 173 if "autozoom" in request.REQUEST and request.REQUEST['autozoom'].lower() == 'true': 174 autozoom = True 175 else: 176 autozoom = False 177 178 if "bbox" in request.REQUEST: 179 bbox = request.REQUEST['bbox'] 180 else: 181 bbox = None 182 183 if "show_extent" in request.REQUEST and request.REQUEST['show_extent'].lower() == 'true': 184 show_extent = True 185 else: 186 show_extent = False 187 188 img = draw_map(uids, request.user, width, height, autozoom, bbox, show_extent, map_name) 189 190 if 'attachment' in request.REQUEST and request.REQUEST['attachment'].lower() == 'true': 191 attach = True 192 else: 193 attach = False 194 195 response = HttpResponse() 196 response['Content-length'] = len(img) 197 response['Content-Type'] = 'image/png' 198 if attach: 199 response['Content-Disposition'] = 'attachment; filename=madrona.png' 200 response.write(img) 201 202 return response
203
204 -def draw_map(uids, user, width, height, autozoom=False, bbox=None, show_extent=False, map_name='default'):
205 """Display a map with the study region geometry. """ 206 # if testing via django unit tests, close out the connection 207 conn = connection.settings_dict 208 testing = False 209 if conn['NAME'] != settings_dbname: 210 testing = True 211 212 map = get_object_or_404(MapConfig,mapname=map_name) 213 if not width: 214 width = map.default_width 215 if not height: 216 height = map.default_height 217 mapfile = str(map.mapfile.path) 218 219 # Create a blank image and map 220 draw = mapnik.Image(width,height) 221 m = mapnik.Map(width,height) 222 223 # load_map is NOT thread safe (?) 224 # load_map_from_string appears to work 225 #mapnik.load_map(m, mapfile) 226 xmltext = open(mapfile).read() 227 # Replace mediaroot 228 xmltext = xmltext.replace("[[MEDIA_ROOT]]",settings.MEDIA_ROOT) 229 mapnik.load_map_from_string(m, xmltext) 230 log.debug("Completed load_map_from_string(), Map object is %r" % m) 231 232 # Create the mapnik layers 233 features = get_features(uids,user) 234 for model, pks in features: 235 try: 236 geomfield = model.mapnik_geomfield() 237 except AttributeError: 238 geomfield = 'geometry_final' 239 240 if geomfield not in [str(x.name) for x in model._meta.fields]: 241 continue 242 243 if not issubclass(model, FeatureCollection): 244 try: 245 style = model.mapnik_style() 246 except AttributeError: 247 style = default_style() 248 style_name = str('%s_style' % model.model_uid()) # tsk mapnik cant take unicode 249 m.append_style(style_name, style) 250 if testing: 251 adapter = PostgisLayer(model.objects.filter(pk__in=pks), field_name=geomfield, persist_connection=False) 252 else: 253 adapter = PostgisLayer(model.objects.filter(pk__in=pks), field_name=geomfield) 254 lyr = adapter.to_mapnik() 255 lyr.styles.append(style_name) 256 m.layers.append(lyr) 257 258 # Grab the bounding coordinates and set them if specified 259 # first, assume default image extent 260 x1, y1 = map.default_x1, map.default_y1 261 x2, y2 = map.default_x2, map.default_y2 262 263 if not bbox and autozoom and features and len(features) > 0: 264 x1, y1, x2, y2 = auto_extent(features, map.default_srid) 265 if bbox: 266 try: 267 x1, y1, x2, y2 = [float(x) for x in bbox.split(',')] 268 except: 269 pass 270 271 bbox = mapnik.Box2d(mapnik.Coord(x1,y1), mapnik.Coord(x2,y2)) 272 273 if show_extent and features and len(features) > 0: 274 # Shows a bounding box for the extent of all specified features 275 # Useful for overview maps 276 x1, y1, x2, y2 = auto_extent(features, map.default_srid) 277 278 ps = mapnik.PolygonSymbolizer(mapnik.Color('#ffffff')) 279 ps.fill_opacity = 0.8 280 ls = mapnik.LineSymbolizer(mapnik.Color('#ff0000'),2.0) 281 r = mapnik.Rule() 282 r.symbols.append(ps) 283 r.symbols.append(ls) 284 extent_style = mapnik.Style() 285 extent_style.rules.append(r) 286 m.append_style('extent_style', extent_style) 287 lyr = mapnik.Layer("Features Extent") 288 bbox_sql = """ 289 (select 1 as id, st_setsrid(st_makebox2d(st_point(%s,%s),st_point(%s,%s)), %s) as geometry_final) as aoi 290 """ % (x1,y1,x2,y2,map.default_srid) 291 lyr.datasource = mapnik.PostGIS(host=connection.settings_dict['HOST'], 292 user=connection.settings_dict['USER'], 293 password=connection.settings_dict['PASSWORD'], 294 dbname=connection.settings_dict['NAME'], 295 table=bbox_sql, 296 geometry_field='geometry_final', 297 estimate_extent=False, 298 persist_connection=not testing, 299 extent='%s,%s,%s,%s' % (x1,y1,x2,y2)) 300 lyr.styles.append('extent_style') 301 m.layers.append(lyr) 302 303 # Render image and send out the response 304 m.zoom_to_box(bbox) 305 306 mapnik.render(m, draw) 307 img = draw.tostring('png') 308 309 if testing: 310 del m 311 312 return img
313