Package madrona :: Package manipulators :: Module manipulators
[hide private]

Source Code for Module madrona.manipulators.manipulators

  1  from django.contrib.gis.geos import GEOSGeometry, Polygon, LineString, Point, LinearRing, fromstr 
  2  from django import forms 
  3  from madrona.studyregion.models import * 
  4  from django.conf import settings 
  5  from madrona.common.utils import LargestPolyFromMulti, LargestLineFromMulti 
  6  from django.template.loader import render_to_string 
  7  from django.core.urlresolvers import reverse 
  8  # manipulatorsDict is bound to this module (won't be reinitialized if module is imported twice) 
  9  manipulatorsDict = {} 
 10  from elementtree.ElementTree import fromstring 
 11  from django.contrib.gis.geos import LinearRing, Polygon 
 12  from madrona.common.utils import clean_geometry, ensure_clean 
 13   
14 -def simplify(geom):
15 if geom.srid != settings.GEOMETRY_DB_SRID: 16 geom.transform(settings.GEOMETRY_DB_SRID) 17 from django.db import connection 18 cursor = connection.cursor() 19 query = "select simplify(st_geomfromewkt(\'%s\'), %s) as geometry" % (geom.ewkt,settings.KML_SIMPLIFY_TOLERANCE) 20 cursor.execute(query) 21 row = cursor.fetchone() 22 try: 23 newgeom = fromstr(row[0]) 24 newgeom.transform(settings.GEOMETRY_CLIENT_SRID) 25 return newgeom 26 except: 27 raise Exception("KML_SIMPLIFY_TOLERANCE might be too high; simplify failed. Try setting the srid on the input geometry")
28
29 -def display_kml(geom):
30 geom = simplify(geom) 31 if hasattr(geom, 'shell'): 32 coords = [] 33 for coord in geom.shell.coords: 34 coords.append(','.join([str(coord[0]), str(coord[1]), str(settings.KML_EXTRUDE_HEIGHT)])) 35 coords = ' '.join(coords) 36 geom_kml = """<Polygon> 37 <extrude>1</extrude> 38 <altitudeMode>absolute</altitudeMode> 39 <outerBoundaryIs> 40 <LinearRing> 41 <coordinates>%s</coordinates> 42 </LinearRing> 43 </outerBoundaryIs> 44 </Polygon> 45 """ % (coords, ) 46 else: 47 geom_kml = geom.kml 48 49 return """<?xml version="1.0" encoding="UTF-8"?> 50 <kml xmlns="http://www.opengis.net/kml/2.2" xmlns:gx="http://www.google.com/kml/ext/2.2" xmlns:kml="http://www.opengis.net/kml/2.2" xmlns:atom="http://www.w3.org/2005/Atom"> 51 <Placemark> 52 <Style> 53 <LineStyle> 54 <color>ffffffff</color> 55 <width>2</width> 56 </LineStyle> 57 <PolyStyle> 58 <color>8000ff00</color> 59 </PolyStyle> 60 </Style> 61 %s 62 </Placemark> 63 </kml>""" % (geom_kml, )
64
65 -def parsekmlpoly(kmlstring):
66 e = fromstring(kmlstring) 67 coords = coords = e.find('{http://www.opengis.net/kml/2.2}Placemark/{http://www.opengis.net/kml/2.2}Polygon/{http://www.opengis.net/kml/2.2}outerBoundaryIs/{http://www.opengis.net/kml/2.2}LinearRing/{http://www.opengis.net/kml/2.2}coordinates').text 68 coords = coords.lstrip(' ').rstrip(' ').replace('\n', '').replace('\t', '') 69 lra = [] 70 for yxz in coords.split(' '): 71 a = yxz.split(',') 72 if len(a) > 1: 73 lra.append((float(a[0]), float(a[1]))) 74 lr = LinearRing(lra) 75 poly = Polygon(lr) 76 return poly
77
78 -def parsekmllinestring(kmlstring):
79 e = fromstring(kmlstring) 80 coords = coords = e.find('{http://www.opengis.net/kml/2.2}Placemark/{http://www.opengis.net/kml/2.2}LineString/{http://www.opengis.net/kml/2.2}coordinates').text 81 coords = coords.lstrip(' ').rstrip(' ').replace('\n', '').replace('\t', '') 82 lra = [] 83 for yxz in coords.split(' '): 84 a = yxz.split(',') 85 if len(a) > 1: 86 lra.append((float(a[0]), float(a[1]))) 87 linestring = LineString(lra) 88 return linestring
89
90 -def parsekmlpoint(kmlstring):
91 e = fromstring(kmlstring) 92 coords = coords = e.find('{http://www.opengis.net/kml/2.2}Placemark/{http://www.opengis.net/kml/2.2}Point/{http://www.opengis.net/kml/2.2}coordinates').text 93 coords = coords.lstrip(' ').rstrip(' ').replace('\n', '').replace('\t', '') 94 lra = [] 95 for yxz in coords.split(' '): 96 a = yxz.split(',') 97 if len(a) > 1: 98 lra.append((float(a[0]), float(a[1]))) 99 point = Point(lra[0]) 100 return point
101
102 -def parsekml(shape):
103 if shape.find('Polygon') is not -1: 104 return parsekmlpoly(shape) 105 elif shape.find('LineString') is not -1: 106 return parsekmllinestring(shape) 107 else: 108 # point 109 return parsekmlpoint(shape)
110
111 -def iskml(string):
112 return (string.rfind('kml') != -1)
113
114 -class BaseManipulator(object):
115 ''' 116 BaseManipulator should be used as the parent class to all manipulator classes. 117 The manipulate() function should be overridden with suitable definition, it is this function that will 118 be called automatically when your manipulator class is included in the Mpa.Options.manipulators list. 119 This function generally takes as input a target shape geometry, and should return a call to result() 120 containing the 'clipped_shape' and optionally a rendered template 'html' and 'success' value. 121 'clipped_shape' is the new shape as a result of the manipulator 122 'html' is generally a template that might be displayed by the client 123 'success' is a signal, '1' or '0', as to whether the manipulation succeeded or not 124 The do_template() function can be used to render a template with appropriate context 125 The target_to_valid_geom() function can be used to generate a geometry from target shape 126 The result() function should be used for the manipulator return value to ensure that all necessary 127 key/value pairs are provided. 128 Three useful exceptions are provided as well: 129 InternalException is used for exceptions or errors that are considered 'server-side' 130 or 'out of the users control', such as failed database access, or failed geometry operation. 131 InvalidGeometryException is used for exceptions or errors resulting from an innapropriate mpa geometry 132 such as a point, line, or otherwise invalid geometry. 133 HaltManipulations is used for errors, not already handled by InternalException or InvalidGeometryException, 134 that should prevent any further manipulations from taking place. This could be useful in cases such as 135 when an mpa geometry is outside of the study region. In such cases there is no need for further 136 manipulations as such an mpa entry is already deemed inappropriate for our use. 137 '''
138 - def __init__(self, **kwargs):
139 self.kwargs = kwargs
140
141 - def manipulate(self):
142 raise NotImplementedError()
143
144 - def do_template(self, key, internal_message='', extra_context={}):
145 context = {'MEDIA_URL':settings.MEDIA_URL, 'INTERNAL_MESSAGE': internal_message} 146 context.update(extra_context) 147 return render_to_string(self.Options.html_templates[key], context)
148
149 - def target_to_valid_geom(self, shape):
150 try: 151 if iskml(shape): 152 target = parsekml(shape) 153 else: 154 target = GEOSGeometry(shape) 155 except Exception, e: 156 raise self.InvalidGeometryException(e.message) 157 if not target.valid: 158 target = target.buffer(0) 159 if not target.valid: 160 raise self.InvalidGeometryException() 161 # if target.srid != settings.GEOMETRY_DB_SRID: 162 target.set_srid(settings.GEOMETRY_CLIENT_SRID) 163 return target
164
165 - def result(self, clipped_shape, html="", success="1"):
166 clipped_shape = ensure_clean(clipped_shape, settings.GEOMETRY_DB_SRID) 167 return {"clipped_shape": clipped_shape, "html": html, "success": success}
168
169 - class Form:
170 available = False
171
172 - class Options:
173 name = 'Manipulator base class' 174 template_name = 'manipulators/manipulator_default.html' 175 html_templates = { 176 'invalid_geom':'manipulators/invalid_geometry.html', 177 'internal':'manipulators/internal_error.html', 178 'unexpected':'manipulators/unexpected_error.html' 179 }
180
181 - class InternalException(Exception):
182 - def __init__(self, message="", status_html=None, success="0"):
183 self._message = message 184 if status_html == None: 185 self.template = BaseManipulator.do_template(BaseManipulator(), 'internal', message) 186 self.html = self.template 187 else: 188 self.html = status_html 189 self.success = success
190
191 - def __str__(self):
192 return repr(self._message)
193
194 - class InvalidGeometryException(Exception):
195 - def __init__(self, message="", status_html=None, success="0"):
196 self._message = message 197 if status_html == None: 198 self.template = BaseManipulator.do_template(BaseManipulator(), 'invalid_geom', message) 199 self.html = self.template 200 else: 201 self.html = status_html 202 self.success = success
203
204 - def __str__(self):
205 return repr(self._message)
206
207 - class HaltManipulations(Exception):
208 - def __init__(self, message="", status_html="", success="0"):
209 self._message = message 210 self.html = status_html 211 self.success = success
212
213 - def __str__(self):
214 return repr(self._message)
215
216 -class ClipToShapeManipulator(BaseManipulator):
217 ''' 218 required arguments: 219 target_shape: GEOSGeometry of the shape to be clipped, in srid GEOMETRY_CLIENT_SRID (4326) 220 clip_against: GEOSGeometry of the shape to clip against, in srid GEOMETRY_CLIENT_SRID (4326) 221 zero: this value may be used to prevent issues that seem to arise from trying to simplify very small geometric results 222 concerning **kwargs: 223 kwargs is included to prevent errors resulting from extra arguments being passed to this manipulator from the generic view 224 manipulate() return value: 225 a call to self.result() 226 with required parameter 'clipped_shape': 227 The returned shape geometry should be in srid GEOMETRY_CLIENT_SRID (4326) 228 The clipped shape will be the largest (in area) polygon result from intersecting 'target_shape' with 'clip_against' 229 and optional parameters 'html' and 'success': 230 The html is usually a template that will be displayed to the client, explaining the manipulation 231 if not provided, this will remain empty 232 The success parameter is defined as '1' for success and '0' for failure 233 if not provided, the default value, '1', is used 234 235 html_templates=='internal' 236 This represents an 'internal error' and is accessed by raising a ManipulatorInternalException 237 This should occur under the following circumstances: 238 if geometry can not be generated from "clip_against" 239 or intersection call failed 240 clipped_shape will be returned as None 241 html_templates=='invalid_geom' 242 This represents a 'user error' and is accessed by raising an InvalidGeometryException 243 This should occur under the following circumstances: 244 if geometry can not be generated from "target_shape" 245 or if "target_shape" is not a valid geometry 246 clipped_shape will be returned as None 247 html_templates==2 clipped shape is empty (no overlap with "clip_against") 248 html_templates==0 if "target_shape" is successfully clipped to "clip_against" 249 ''' 250
251 - def __init__(self, target_shape, clip_against=None, zero=0.0, **kwargs):
252 self.target_shape = target_shape 253 self.clip_against = clip_against 254 self.zero = zero
255
256 - def manipulate(self):
257 #extract target_shape geometry 258 target_shape = self.target_to_valid_geom(self.target_shape) 259 260 #extract clip_against geometry 261 try: 262 clip_against = GEOSGeometry(self.clip_against) 263 clip_against.set_srid(settings.GEOMETRY_CLIENT_SRID) 264 except Exception, e: 265 raise self.InternalException("Exception raised in ClipToShapeManipulator while initializing geometry on self.clip_against: " + e.message) 266 267 if not clip_against.valid: 268 raise self.InternalException("ClipToShapeManipulator: 'clip_against' is not a valid geometry") 269 270 #intersect the two geometries 271 try: 272 clipped_shape = target_shape.intersection(clip_against) 273 except Exception, e: 274 raise self.InternalException("Exception raised in ClipToShapeManipulator while intersecting geometries: " + e.message) 275 276 #if there was no overlap (intersection was empty) 277 if clipped_shape.area <= self.zero: 278 status_html = self.do_template("2") 279 message = "intersection resulted in empty geometry" # ALTERATION #1 280 #return self.result(clipped_shape, target_shape, status_html, message) 281 raise self.HaltManipulations(message, status_html) # ALTERATION #2 282 283 #if there was overlap 284 largest_poly = LargestPolyFromMulti(clipped_shape) 285 status_html = self.do_template("0") 286 #message = "'target_shape' was clipped successfully to 'clip_against'" 287 #return self.result(largest_poly, target_shape, status_html, message) 288 return self.result(largest_poly, status_html)
289 ''' 290 #the following is USED FOR TESTING, 291 #assigns db current studyregion as the shape to clip against 292 class Form(forms.Form): 293 available = True 294 target_shape = forms.CharField( widget=forms.HiddenInput ) 295 clip_against = forms.CharField( widget=forms.HiddenInput, required=False ) 296 297 def clean(self): 298 data = self.cleaned_data 299 300 #used for sandbox testing 301 clippy = StudyRegion.objects.current().geometry 302 clippy.transform(settings.GEOMETRY_CLIENT_SRID) 303 data["clip_against"] = clippy.wkt 304 305 #my_manipulator = ClipToShapeManipulator( **kwargs ) 306 my_manipulator = ClipToShapeManipulator( data['target_shape'], data['clip_against'] ) 307 self.manipulation = my_manipulator.manipulate() 308 return self.cleaned_data 309 '''
310 - class Options:
311 name = 'ClipToShape' 312 html_templates = { 313 '0':'manipulators/shape_clip.html', 314 '2':'manipulators/outside_shape.html', 315 }
316 317 manipulatorsDict[ClipToShapeManipulator.Options.name] = ClipToShapeManipulator 318
319 -class DifferenceFromShapeManipulator(BaseManipulator):
320 ''' 321 required arguments: 322 target_shape: GEOSGeometry of the shape to be clipped, in srid GEOMETRY_CLIENT_SRID (4326) 323 clip_against: GEOSGeometry of the shape to clip against, in srid GEOMETRY_CLIENT_SRID (4326) 324 zero: this value may be used to prevent issues that seem to arise from trying to simplify very small geometric results 325 concerning **kwargs: 326 kwargs is included to prevent errors resulting from extra arguments being passed to this manipulator from the generic view 327 manipulate() return value: 328 a call to self.result() 329 with required parameter 'clipped_shape': 330 The returned shape geometry should be in srid GEOMETRY_CLIENT_SRID (4326) 331 The clipped shape will be the largest (in area) polygon result from taking the difference of 'target_shape' with 'clip_against' 332 and optional parameters 'html' and 'success': 333 The html is usually a template that will be displayed to the client, explaining the manipulation 334 if not provided, this will remain empty 335 The success parameter is defined as '1' for success and '0' for failure 336 if not provided, the default value, '1', is used 337 338 html_templates=='internal' 339 This represents an 'internal error' and is accessed by raising a ManipulatorInternalException 340 This should occur under the following circumstances: 341 if geometry can not be generated from "clip_against" 342 or intersection call failed 343 clipped_shape will be returned as None 344 html_templates=='invalid_geom' 345 This represents a 'user error' and is accessed by raising an InvalidGeometryException 346 This should occur under the following circumstances: 347 if geometry can not be generated from "target_shape" 348 or if "target_shape" is not a valid geometry 349 clipped_shape will be returned as None 350 html_templates==2 clipped shape is empty (no overlap with "clip_against") 351 html_templates==0 if "target_shape" is successfully clipped to "clip_against" 352 ''' 353
354 - def __init__(self, target_shape, clip_against=None, zero=0.0, **kwargs):
355 self.target_shape = target_shape 356 self.diff_geom = clip_against 357 self.zero = zero
358
359 - def manipulate(self):
360 #extract target_shape geometry 361 target_shape = self.target_to_valid_geom(self.target_shape) 362 #extract diff_geom geometry 363 try: 364 diff_geom = GEOSGeometry(self.diff_geom) 365 diff_geom.set_srid(settings.GEOMETRY_CLIENT_SRID) 366 except Exception, e: 367 raise self.InternalException("Exception raised in DifferenceFromShapeManipulator while initializing geometry on self.diff_geom: " + e.message) 368 369 if not diff_geom.valid: 370 raise self.InternalException("DifferenceFromShapeManipulator: 'diff_geom' is not a valid geometry") 371 372 #determine the difference in the two geometries 373 try: 374 clipped_shape = target_shape.difference(diff_geom) 375 except Exception, e: 376 raise self.InternalException("Exception raised in DifferenceFromShapeManipulator while intersecting geometries: " + e.message) 377 378 #if there is no geometry left (difference was empty) 379 if clipped_shape.area <= self.zero: 380 status_html = self.do_template("2") 381 message = "difference resulted in empty geometry" 382 raise self.HaltManipulations(message, status_html) 383 384 #if there was overlap 385 largest_poly = LargestPolyFromMulti(clipped_shape) 386 status_html = self.do_template("0") 387 return self.result(largest_poly, status_html)
388
389 - class Options:
390 name = 'DifferenceFromShape' 391 html_templates = { 392 '0':'manipulators/shape_clip.html', 393 '2':'manipulators/outside_shape.html', 394 }
395 396 manipulatorsDict[DifferenceFromShapeManipulator.Options.name] = DifferenceFromShapeManipulator 397
398 -class ClipToStudyRegionManipulator(BaseManipulator):
399 ''' 400 required argument: 401 target_shape: GEOSGeometry of the shape to be clipped, in srid GEOMETRY_CLIENT_SRID (4326) 402 optional argument: 403 generally USED FOR TESTING ONLY 404 study_region: GEOSGeometry of the shape to be clipped, in srid GEOMETRY_CLIENT_SRID (4326) 405 concerning **kwargs: 406 kwargs is included to prevent errors resulting from extra arguments being passed to this manipulator from the generic view 407 manipulate() return value: 408 a call to self.result() 409 with required parameter 'clipped_shape': 410 The returned shape geometry should be in srid GEOMETRY_CLIENT_SRID (4326) 411 The clipped shape will be the largest (in area) polygon result from intersecting target_shape with the study region 412 and optional parameters 'html' and 'success': 413 The html is usually a template that will be displayed to the client, explaining the manipulation 414 if not provided, this will remain empty 415 The success parameter is defined as '1' for success and '0' for failure 416 if not provided, the default value, '1', is used 417 418 html_templates=='internal' 419 This represents an 'internal error' and is accessed by raising a ManipulatorInternalException 420 This should occur under the following circumstances: 421 if Study Region not found in database 422 or intersection call failed 423 clipped_shape will be returned as None 424 html_templates=='invalid_geom' 425 This represents an 'user error' and is accessed by raising a InvalidGeometryException 426 This should occur under the following circumstances: 427 if geometry can not be generated from target_shape 428 or if target_shape is not a valid geometry 429 clipped_shape will be returned as None 430 html_templates==2 clipped shape is empty (no overlap with Study Region) 431 html_templates==0 if target_shape is successfully clipped to Study Region 432 ''' 433
434 - def __init__(self, target_shape, study_region=None, **kwargs):
435 self.target_shape = target_shape 436 self.study_region = study_region
437
438 - def manipulate(self):
439 #extract target_shape geometry 440 target_shape = self.target_to_valid_geom(self.target_shape) 441 442 #extract study_region geometry 443 #study_region argument is FOR UNIT-TESTING PURPOSES ONLY, otherwise we access the database 444 if self.study_region is not None: 445 try: 446 study_region = GEOSGeometry(self.study_region) 447 study_region.set_srid(settings.GEOMETRY_CLIENT_SRID) 448 study_region.transform(settings.GEOMETRY_DB_SRID) 449 except Exception, e: 450 raise self.InternalException("Exception raised in ClipToStudyRegionManipulator while initializing study region geometry: " + e.message) 451 else: 452 try: 453 study_region = StudyRegion.objects.current().geometry 454 except Exception, e: 455 raise self.InternalException("Exception raised in ClipToStudyRegionManipulator while obtaining study region geometry from database: " + e.message) 456 457 #intersect the two geometries 458 try: 459 target_shape.transform(settings.GEOMETRY_DB_SRID) 460 clipped_shape = target_shape.intersection(study_region) 461 target_shape.transform(settings.GEOMETRY_CLIENT_SRID) 462 clipped_shape.transform(settings.GEOMETRY_CLIENT_SRID) 463 except Exception, e: 464 raise self.InternalException("Exception raised in ClipToStudyRegionManipulator while intersecting geometries: " + e.message) 465 466 out_geom = None 467 if target_shape.geom_type == 'Polygon' and clipped_shape.area > 0: 468 out_geom = LargestPolyFromMulti(clipped_shape) 469 elif target_shape.geom_type == 'LineString' and clipped_shape.length > 0: 470 out_geom = LargestLineFromMulti(clipped_shape) 471 elif target_shape.geom_type == 'Point' and not clipped_shape.empty: 472 out_geom = clipped_shape 473 474 if out_geom is None: 475 message = "clipped geometry is empty (there was no intersection/overlap with study region)" 476 status_html = self.do_template("2") 477 raise self.HaltManipulations(message, status_html) 478 479 status_html = self.do_template("0") 480 return self.result(out_geom, status_html)
481
482 - class Options:
483 name = 'ClipToStudyRegion' 484 supported_geom_fields = ['PolygonField', 'PointField', 'LineStringField'] 485 display_name = "Study Region" 486 description = "Clip your shape to the study region" 487 html_templates = { 488 '0':'manipulators/studyregion_clip.html', 489 '2':'manipulators/outside_studyregion.html', 490 }
491 492 manipulatorsDict[ClipToStudyRegionManipulator.Options.name] = ClipToStudyRegionManipulator 493
494 -class ClipToGraticuleManipulator(BaseManipulator):
495 ''' 496 required argument: 497 target_shape: GEOSGeometry of the shape to be clipped, in srid GEOMETRY_CLIENT_SRID (4326) 498 optional arguments: 499 north, south, east, west: expressed in srid GEOMETRY_CLIENT_SRID (4326) 500 concerning **kwargs: 501 kwargs is included to prevent errors resulting from extra arguments being passed to this manipulator from the generic view 502 manipulate() return value: 503 a call to self.result() 504 with required parameter 'clipped_shape': 505 The returned shape geometry should be in srid GEOMETRY_CLIENT_SRID (4326) 506 The clipped shape will be the largest (in area) polygon result from clipping target_shape with the requested graticule(s) 507 and optional parameters 'html' and 'success': 508 The html is usually a template that will be displayed to the client, explaining the manipulation 509 if not provided, this will remain empty 510 The success parameter is defined as '1' for success and '0' for failure 511 if not provided, the default value, '1', is used 512 513 html_templates=='invalid' 514 This represents an 'internal error' and is accessed by raising a ManipulatorInternalException 515 This should occur under the following circumstances: 516 if polygon could not be created from graticules 517 or intersection call failed 518 clipped_shape will be returned as None 519 html_templates=='invalid_geom' 520 This represents an 'user error' and is accessed by raising a InvalidGeometryException 521 This should occur under the following circumstances: 522 if geometry can not be generated from target_shape 523 or target_shape is not valid geometry 524 clipped_shape will be returned as None 525 html_templates==2 if the clipped geometry is empty 526 html_templates==0 if target_shape is successfully clipped to the requested graticule(s) 527 ''' 528
529 - def __init__(self, target_shape, north=None, south=None, east=None, west=None, **kwargs):
530 self.target_shape = target_shape 531 self.north = north 532 self.south = south 533 self.east = east 534 self.west = west
535
536 - def manipulate(self):
537 #extract target_shape geometry 538 target_shape = self.target_to_valid_geom(self.target_shape) 539 540 #construct graticule box 541 box_builder = self.GraticuleBoxBuilder(self, target_shape) 542 graticule_box = box_builder.build_box() 543 544 #intersect the two geometries 545 try: 546 clipped_shape = target_shape.intersection(graticule_box) 547 except Exception, e: 548 raise self.InternalException("Exception raised in ClipToGraticuleManipulator while intersecting geometries: " + e.message) 549 550 #if there was no overlap (intersection was empty) 551 if clipped_shape.area == 0: 552 #message = "clipped geometry is empty (there was no intersection/overlap with the graticules)" 553 status_html = render_to_string(self.Options.html_templates["2"], {'MEDIA_URL':settings.MEDIA_URL}) 554 #return {"message": "clipped geometry is empty (there was no intersection/overlap with the graticules)", "html": status_html, "clipped_shape": clipped_shape, "original_shape": target_shape} 555 return self.result(clipped_shape, status_html) 556 557 #if there was overlap 558 largest_poly = LargestPolyFromMulti(clipped_shape) 559 #message = "Graticule clipping was a success" 560 status_html = render_to_string(self.Options.html_templates["0"], {'MEDIA_URL':settings.MEDIA_URL}) 561 #return {"message": "Graticule clipping was a success", "html": status_html, "clipped_shape": largest_poly, "original_shape": target_shape} 562 return self.result(largest_poly, status_html)
563
564 - class GraticuleBoxBuilder():
565 ''' 566 required argument: 567 target_shape: GEOSGeometry of the shape to be clipped, in srid GEOMETRY_CLIENT_SRID (4326) 568 build_box() return value: 569 a box like geometry built from the graticules provided to the manipulator class, completing any 570 missing north, south, east, or west values with the extent of the target shape geometry 571 returned shape geometry will be in srid GEOMETRY_CLIENT_SRID (4326) 572 ''' 573
574 - def __init__(self, parent, shape):
575 self.__extract_dirs(parent) 576 self.__build_extent(shape)
577
578 - def build_box(self):
579 ''' 580 top_left = (west, north) 581 top_right = (east, north) 582 bottom_right = (east, south) 583 bottom_left = (west, south) 584 ''' 585 try: 586 box = Polygon(LinearRing([Point(float(self.west), float(self.north)), 587 Point(float(self.east), float(self.north)), 588 Point(float(self.east), float(self.south)), 589 Point(float(self.west), float(self.south)), 590 Point(float(self.west), float(self.north))])) 591 box.set_srid(settings.GEOMETRY_CLIENT_SRID) 592 return box 593 except Exception, e: 594 raise self.InternalException("Exception raised in ClipToGraticuleManipulator while initializing graticule geometry: " + e.message)
595
596 - def __extract_dirs(self, parent):
597 self.parent = parent 598 self.north = self.parent.north 599 self.south = self.parent.south 600 self.east = self.parent.east 601 self.west = self.parent.west
602
603 - def __build_extent(self, shape):
604 #we will use target_shape.extent to fill in any missing graticule values 605 geom_extent = shape.extent 606 #fill in any missing graticule params with geom_extent (xmin, ymin, xmax, ymax) values 607 if self.north is None: 608 self.north = geom_extent[3] 609 if self.south is None: 610 self.south = geom_extent[1] 611 if self.east is None: 612 self.east = geom_extent[2] 613 if self.west is None: 614 self.west = geom_extent[0]
615
616 - class Form(forms.Form):
617 available = True 618 n = forms.FloatField(label='Northern boundary', required=False) 619 s = forms.FloatField(label='Southern boundary', required=False) 620 e = forms.FloatField(label='Eastern boundary', required=False) 621 w = forms.FloatField(label='Western boundary', required=False) 622 target_shape = forms.CharField(widget=forms.HiddenInput) 623
624 - def clean(self):
625 data = self.cleaned_data 626 627 #the following is used for manipulators testing only 628 #data["n"] = 33.75 629 #data["e"] = -118.75 630 #data["s"] = 33.25 631 #data["w"] = -119.25 632 633 my_manipulator = ClipToGraticuleManipulator(data['target_shape'], data['n'], data['s'], data['e'], data['w']) 634 self.manipulation = my_manipulator.manipulate() 635 return self.cleaned_data
636
637 - class Options:
638 name = 'ClipToGraticule' 639 supported_geom_fields = ['PolygonField', 'LineStringField'] 640 html_templates = { 641 '0':'manipulators/graticule.html', 642 '2':'manipulators/no_graticule_overlap.html', 643 }
644 645 manipulatorsDict[ClipToGraticuleManipulator.Options.name] = ClipToGraticuleManipulator 646
647 -class NullManipulator(BaseManipulator):
648 """ 649 This manipulator does nothing but ensure the geometry is clean. 650 Even if no manipulator is specified, this, at a minimum, needs to be run. 651 """
652 - def __init__(self, target_shape, **kwargs):
654
655 - def manipulate(self):
656 target_shape = self.target_to_valid_geom(self.target_shape) 657 status_html = self.do_template("0") 658 return self.result(target_shape, status_html)
659
660 - class Options(BaseManipulator.Options):
661 name = 'NullManipulator' 662 supported_geom_fields = ['PolygonField', 'PointField', 'LineStringField'] 663 html_templates = { 664 '0':'manipulators/valid.html', 665 }
666 667 manipulatorsDict[NullManipulator.Options.name] = NullManipulator 668
669 -def get_url_for_model(model):
670 names = [] 671 for manipulator in model.Options.manipulators: 672 names.append(manipulator.Options.name) 673 return reverse('manipulate', args=[','.join(names)])
674
675 -def get_manipulators_for_model(model):
676 required = [] 677 display_names = {} 678 descriptions = {} 679 options = model.get_options() 680 681 # required manipulators 682 for manipulator in options.manipulators: 683 required.append(manipulator.Options.name) 684 685 try: 686 display_names[manipulator.Options.name] = manipulator.Options.display_name 687 except AttributeError: 688 pass 689 690 try: 691 descriptions[manipulator.Options.name] = manipulator.Options.description 692 except AttributeError: 693 pass 694 695 # optional manipulators 696 optional = [] 697 for manipulator in options.optional_manipulators: 698 optional.append(manipulator.Options.name) 699 try: 700 display_names[manipulator.Options.name] = manipulator.Options.display_name 701 except AttributeError: 702 pass 703 704 try: 705 descriptions[manipulator.Options.name] = manipulator.Options.description 706 except AttributeError: 707 pass 708 709 manip = {'manipulators': required} 710 if optional: 711 manip['optional_manipulators'] = optional 712 713 if len(required) > 0: 714 url = reverse('manipulate', args=[','.join(required)]) 715 else: 716 url = reverse('manipulate-blank') 717 718 # Geometry Input Methods (defaults to 'digitize' only) 719 manip['geometry_input_methods'] = ['digitize'] 720 try: 721 for imethod in model.Options.geometry_input_methods: 722 if imethod not in manip['geometry_input_methods']: 723 manip['geometry_input_methods'].append(imethod) 724 except AttributeError: 725 pass 726 727 if 'loadshp' in manip['geometry_input_methods']: 728 manip['loadshp_url'] = reverse('loadshp-single') 729 730 manip['url'] = url 731 manip['display_names'] = display_names 732 manip['descriptions'] = descriptions 733 return manip
734