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
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
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
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
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
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
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
110
112 return (string.rfind('kml') != -1)
113
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 '''
140
142 raise NotImplementedError()
143
144 - def do_template(self, key, internal_message='', extra_context={}):
148
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
171
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
182 - def __init__(self, message="", status_html=None, success="0"):
190
192 return repr(self._message)
193
195 - def __init__(self, message="", status_html=None, success="0"):
203
205 return repr(self._message)
206
208 - def __init__(self, message="", status_html="", success="0"):
209 self._message = message
210 self.html = status_html
211 self.success = success
212
214 return repr(self._message)
215
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):
255
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 '''
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
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):
358
388
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
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):
437
481
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
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
563
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
577
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
602
604
605 geom_extent = shape.extent
606
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
636
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
659
660 - class Options(BaseManipulator.Options):
666
667 manipulatorsDict[NullManipulator.Options.name] = NullManipulator
668
674
676 required = []
677 display_names = {}
678 descriptions = {}
679 options = model.get_options()
680
681
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
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
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