import logging from django.forms.widgets import Textarea from django.template import loader, Context from django.templatetags.static import static from django.utils import six from django.utils import translation from django.contrib.gis.gdal import OGRException from django.contrib.gis.geos import GEOSGeometry, GEOSException, fromstr # Creating a template context that contains Django settings # values needed by admin map templates. geo_context = Context({'LANGUAGE_BIDI' : translation.get_language_bidi()}) logger = logging.getLogger('django.contrib.gis') class OpenLayersWidget(Textarea): """ Renders an OpenLayers map using the WKT of the geometry. """ def render(self, name, value, attrs=None): # Update the template parameters with any attributes passed in. if attrs: self.params.update(attrs) # Defaulting the WKT value to a blank string -- this # will be tested in the JavaScript and the appropriate # interface will be constructed. self.params['wkt'] = '' # If a string reaches here (via a validation error on another # field) then just reconstruct the Geometry. if isinstance(value, six.string_types): try: value = GEOSGeometry(value) except (GEOSException, ValueError) as err: logger.error( "Error creating geometry from value '%s' (%s)" % ( value, err) ) value = None if value and value.geom_type.upper() != self.geom_type: value = None # Constructing the dictionary of the map options. self.params['map_options'] = self.map_options() # Constructing the JavaScript module name using the name of # the GeometryField (passed in via the `attrs` keyword). # Use the 'name' attr for the field name (rather than 'field') self.params['name'] = name # note: we must switch out dashes for underscores since js # functions are created using the module variable js_safe_name = self.params['name'].replace('-','_') self.params['module'] = 'geodjango_%s' % js_safe_name if value: # Transforming the geometry to the projection used on the # OpenLayers map. srid = self.params['srid'] if value.srid != srid: try: ogr = value.ogr ogr.transform(srid) wkt = ogr.wkt except OGRException as err: logger.error( "Error transforming geometry from srid '%s' to srid '%s' (%s)" % ( value.srid, srid, err) ) wkt = '' else: wkt = value.wkt # Setting the parameter WKT with that of the transformed # geometry. self.params['wkt'] = wkt return loader.render_to_string(self.template, self.params, context_instance=geo_context) def map_options(self): "Builds the map options hash for the OpenLayers template." # JavaScript construction utilities for the Bounds and Projection. def ol_bounds(extent): return 'new OpenLayers.Bounds(%s)' % str(extent) def ol_projection(srid): return 'new OpenLayers.Projection("EPSG:%s")' % srid # An array of the parameter name, the name of their OpenLayers # counterpart, and the type of variable they are. map_types = [('srid', 'projection', 'srid'), ('display_srid', 'displayProjection', 'srid'), ('units', 'units', str), ('max_resolution', 'maxResolution', float), ('max_extent', 'maxExtent', 'bounds'), ('num_zoom', 'numZoomLevels', int), ('max_zoom', 'maxZoomLevels', int), ('min_zoom', 'minZoomLevel', int), ] # Building the map options hash. map_options = {} for param_name, js_name, option_type in map_types: if self.params.get(param_name, False): if option_type == 'srid': value = ol_projection(self.params[param_name]) elif option_type == 'bounds': value = ol_bounds(self.params[param_name]) elif option_type in (float, int): value = self.params[param_name] elif option_type in (str,): value = '"%s"' % self.params[param_name] else: raise TypeError map_options[js_name] = value return map_options def _has_changed(self, initial, data): """ Compare geographic value of data with its initial value. """ # Ensure we are dealing with a geographic object if isinstance(initial, six.string_types): try: initial = GEOSGeometry(initial) except (GEOSException, ValueError): initial = None # Only do a geographic comparison if both values are available if initial and data: data = fromstr(data) data.transform(initial.srid) # If the initial value was not added by the browser, the geometry # provided may be slightly different, the first time it is saved. # The comparison is done with a very low tolerance. return not initial.equals_exact(data, tolerance=0.000001) else: # Check for change of state of existence return bool(initial) != bool(data)