Ticket #5472: olwidget_admin.diff

File olwidget_admin.diff, 79.3 KB (added by Charlie DeTar, 15 years ago)

Initial patch replacing geodjango admin with olwidget

  • contrib/gis/admin/options.py

     
    1 from django.conf import settings
     1import copy
     2
     3from django.utils.encoding import force_unicode
    24from django.contrib.admin import ModelAdmin
    3 from django.contrib.gis.admin.widgets import OpenLayersWidget
    4 from django.contrib.gis.gdal import OGRGeomType
    55from django.contrib.gis.db import models
     6from django.contrib.gis.widgets import EditableMap, InfoMap
    67
    78class GeoModelAdmin(ModelAdmin):
    8     """
    9     The administration options class for Geographic models. Map settings
    10     may be overloaded from their defaults to create custom maps.
    11     """
    12     # The default map settings that may be overloaded -- still subject
    13     # to API changes.
    14     default_lon = 0
    15     default_lat = 0
    16     default_zoom = 4
    17     display_wkt = False
    18     display_srid = False
    19     extra_js = []
    20     num_zoom = 18
    21     max_zoom = False
    22     min_zoom = False
    23     units = False
    24     max_resolution = False
    25     max_extent = False
    26     modifiable = True
    27     mouse_position = True
    28     scale_text = True
    29     layerswitcher = True
    30     scrollable = True
    31     map_width = 600
    32     map_height = 400
    33     map_srid = 4326
    34     map_template = 'gis/admin/openlayers.html'
    35     openlayers_url = 'http://openlayers.org/api/2.8/OpenLayers.js'
    36     point_zoom = num_zoom - 6
    37     wms_url = 'http://labs.metacarta.com/wms/vmap0'
    38     wms_layer = 'basic'
    39     wms_name = 'OpenLayers WMS'
    40     debug = False
    41     widget = OpenLayersWidget
     9    options = {}
     10    list_map = None
     11    list_map_options = {}
     12    change_list_template = "gis/admin/olwidget_change_list.html"
    4213
    43     def _media(self):
    44         "Injects OpenLayers JavaScript into the admin."
    45         media = super(GeoModelAdmin, self)._media()
    46         media.add_js([self.openlayers_url])
    47         media.add_js(self.extra_js)
    48         return media
    49     media = property(_media)
    50 
    5114    def formfield_for_dbfield(self, db_field, **kwargs):
    5215        """
    53         Overloaded from ModelAdmin so that an OpenLayersWidget is used
    54         for viewing/editing GeometryFields.
     16        Overloaded from ModelAdmin to use our map widget.
    5517        """
    5618        if isinstance(db_field, models.GeometryField):
    5719            request = kwargs.pop('request', None)
    58             # Setting the widget with the newly defined widget.
    5920            kwargs['widget'] = self.get_map_widget(db_field)
    6021            return db_field.formfield(**kwargs)
    6122        else:
    62             return super(GeoModelAdmin, self).formfield_for_dbfield(db_field, **kwargs)
     23            return super(GeoModelAdmin, self).formfield_for_dbfield(
     24                    db_field, **kwargs)
    6325
    6426    def get_map_widget(self, db_field):
    6527        """
    66         Returns a subclass of the OpenLayersWidget (or whatever was specified
    67         in the `widget` attribute) using the settings from the attributes set
    68         in this class.
     28        Returns an EditableMap subclass with options appropriate for the given
     29        field.
    6930        """
    70         is_collection = db_field.geom_type in ('MULTIPOINT', 'MULTILINESTRING', 'MULTIPOLYGON', 'GEOMETRYCOLLECTION')
    71         if is_collection:
    72             if db_field.geom_type == 'GEOMETRYCOLLECTION': collection_type = 'Any'
    73             else: collection_type = OGRGeomType(db_field.geom_type.replace('MULTI', ''))
     31        is_collection = db_field.geom_type in ('MULTIPOINT', 'MULTILINESTRING',
     32                'MULTIPOLYGON', 'GEOMETRYCOLLECTION')
     33        if db_field.geom_type == 'GEOMETRYCOLLECTION':
     34            geometry = ['polygon', 'point', 'linestring']
    7435        else:
    75             collection_type = 'None'
     36            if db_field.geom_type in ('MULTIPOINT', 'POINT'):
     37                geometry = 'point'
     38            elif db_field.geom_type in ('POLYGON', 'MULTIPOLYGON'):
     39                geometry = 'polygon'
     40            elif db_field.geom_type in ('LINESTRING', 'MULTILINESTRING'):
     41                geometry = 'linestring'
     42            else:
     43                # fallback: allow all types.
     44                geometry = ['polygon', 'point', 'linestring']
    7645
    77         class OLMap(self.widget):
    78             template = self.map_template
    79             geom_type = db_field.geom_type
    80             params = {'default_lon' : self.default_lon,
    81                       'default_lat' : self.default_lat,
    82                       'default_zoom' : self.default_zoom,
    83                       'display_wkt' : self.debug or self.display_wkt,
    84                       'geom_type' : OGRGeomType(db_field.geom_type),
    85                       'field_name' : db_field.name,
    86                       'is_collection' : is_collection,
    87                       'scrollable' : self.scrollable,
    88                       'layerswitcher' : self.layerswitcher,
    89                       'collection_type' : collection_type,
    90                       'is_linestring' : db_field.geom_type in ('LINESTRING', 'MULTILINESTRING'),
    91                       'is_polygon' : db_field.geom_type in ('POLYGON', 'MULTIPOLYGON'),
    92                       'is_point' : db_field.geom_type in ('POINT', 'MULTIPOINT'),
    93                       'num_zoom' : self.num_zoom,
    94                       'max_zoom' : self.max_zoom,
    95                       'min_zoom' : self.min_zoom,
    96                       'units' : self.units, #likely shoud get from object
    97                       'max_resolution' : self.max_resolution,
    98                       'max_extent' : self.max_extent,
    99                       'modifiable' : self.modifiable,
    100                       'mouse_position' : self.mouse_position,
    101                       'scale_text' : self.scale_text,
    102                       'map_width' : self.map_width,
    103                       'map_height' : self.map_height,
    104                       'point_zoom' : self.point_zoom,
    105                       'srid' : self.map_srid,
    106                       'display_srid' : self.display_srid,
    107                       'wms_url' : self.wms_url,
    108                       'wms_layer' : self.wms_layer,
    109                       'wms_name' : self.wms_name,
    110                       'debug' : self.debug,
    111                       }
    112         return OLMap
     46        options = copy.deepcopy(self.options)
     47        options.update({
     48            'geometry': geometry,
     49            'isCollection': is_collection,
     50            'name': db_field.name,
     51        })
     52        class Widget(EditableMap):
     53            def __init__(self, *args, **kwargs):
     54                kwargs['options'] = options
     55                # OL rendering bug with floats requires this.
     56                kwargs['template'] = "gis/admin/admin_editable_map.html"
     57                super(Widget, self).__init__(*args, **kwargs)
    11358
    114 # Using the Beta OSM in the admin requires the following:
    115 #  (1) The Google Maps Mercator projection needs to be added
    116 #      to your `spatial_ref_sys` table.  You'll need at least GDAL 1.5:
    117 #      >>> from django.contrib.gis.gdal import SpatialReference
    118 #      >>> from django.contrib.gis.utils import add_postgis_srs
    119 #      >>> add_postgis_srs(SpatialReference(900913)) # Adding the Google Projection
    120 from django.contrib.gis import gdal
    121 if gdal.HAS_GDAL:
    122     class OSMGeoAdmin(GeoModelAdmin):
    123         map_template = 'gis/admin/osm.html'
    124         extra_js = ['http://openstreetmap.org/openlayers/OpenStreetMap.js']
    125         num_zoom = 20
    126         map_srid = 900913
    127         max_extent = '-20037508,-20037508,20037508,20037508'
    128         max_resolution = 156543.0339
    129         point_zoom = num_zoom - 6
    130         units = 'm'
     59        return Widget
     60
     61    def get_changelist_map(self, cl):
     62        """
     63        Display a map in the admin changelist, with info popups
     64        """
     65        if self.list_map:
     66            info = []
     67            for obj in cl.get_query_set():
     68                info.append((
     69                    [getattr(obj, field) for field in self.list_map],
     70                    "<a href='%s'>%s</a>" % (
     71                        cl.url_for_result(obj),
     72                        force_unicode(obj)
     73                    )
     74                ))
     75
     76            return InfoMap(info, options=self.list_map_options)
     77        return None
     78
     79    def get_changelist_context(self, request):
     80        """
     81        Add changelist map, if any, to the context.
     82        """
     83        context = super(GeoModelAdmin, self).get_changelist_context(request)
     84        map = self.get_changelist_map(context['cl'])
     85        if map:
     86            context['media'] += map.media
     87            context['map'] = map
     88        return context
     89
  • contrib/gis/admin/__init__.py

     
    11# Getting the normal admin routines, classes, and `site` instance.
    22from django.contrib.admin import autodiscover, site, AdminSite, ModelAdmin, StackedInline, TabularInline, HORIZONTAL, VERTICAL
    33
    4 # Geographic admin options classes and widgets.
     4# Geographic admin options classes
    55from django.contrib.gis.admin.options import GeoModelAdmin
    6 from django.contrib.gis.admin.widgets import OpenLayersWidget
    7 
    8 try:
    9     from django.contrib.gis.admin.options import OSMGeoAdmin
    10     HAS_OSM = True
    11 except ImportError:
    12     HAS_OSM = False
  • contrib/gis/admin/widgets.py

     
    1 from django.conf import settings
    2 from django.contrib.gis.gdal import OGRException
    3 from django.contrib.gis.geos import GEOSGeometry, GEOSException
    4 from django.forms.widgets import Textarea
    5 from django.template import loader, Context
    6 from django.utils import translation
    7 
    8 # Creating a template context that contains Django settings
    9 # values needed by admin map templates.
    10 geo_context = Context({'ADMIN_MEDIA_PREFIX' : settings.ADMIN_MEDIA_PREFIX,
    11                        'LANGUAGE_BIDI' : translation.get_language_bidi(),
    12                        })
    13 
    14 class OpenLayersWidget(Textarea):
    15     """
    16     Renders an OpenLayers map using the WKT of the geometry.
    17     """
    18     def render(self, name, value, attrs=None):
    19         # Update the template parameters with any attributes passed in.
    20         if attrs: self.params.update(attrs)
    21 
    22         # Defaulting the WKT value to a blank string -- this
    23         # will be tested in the JavaScript and the appropriate
    24         # interface will be constructed.
    25         self.params['wkt'] = ''
    26 
    27         # If a string reaches here (via a validation error on another
    28         # field) then just reconstruct the Geometry.
    29         if isinstance(value, basestring):
    30             try:
    31                 value = GEOSGeometry(value)
    32             except (GEOSException, ValueError):
    33                 value = None
    34 
    35         if value and value.geom_type.upper() != self.geom_type:
    36             value = None
    37 
    38         # Constructing the dictionary of the map options.
    39         self.params['map_options'] = self.map_options()
    40 
    41         # Constructing the JavaScript module name using the name of
    42         # the GeometryField (passed in via the `attrs` keyword).
    43         # Use the 'name' attr for the field name (rather than 'field')
    44         self.params['name'] = name
    45         # note: we must switch out dashes for underscores since js
    46         # functions are created using the module variable
    47         js_safe_name = self.params['name'].replace('-','_')
    48         self.params['module'] = 'geodjango_%s' % js_safe_name
    49 
    50         if value:
    51             # Transforming the geometry to the projection used on the
    52             # OpenLayers map.
    53             srid = self.params['srid']
    54             if value.srid != srid:
    55                 try:
    56                     ogr = value.ogr
    57                     ogr.transform(srid)
    58                     wkt = ogr.wkt
    59                 except OGRException:
    60                     wkt = ''
    61             else:
    62                 wkt = value.wkt
    63 
    64             # Setting the parameter WKT with that of the transformed
    65             # geometry.
    66             self.params['wkt'] = wkt
    67 
    68         return loader.render_to_string(self.template, self.params,
    69                                        context_instance=geo_context)
    70 
    71     def map_options(self):
    72         "Builds the map options hash for the OpenLayers template."
    73 
    74         # JavaScript construction utilities for the Bounds and Projection.
    75         def ol_bounds(extent):
    76             return 'new OpenLayers.Bounds(%s)' % str(extent)
    77         def ol_projection(srid):
    78             return 'new OpenLayers.Projection("EPSG:%s")' % srid
    79 
    80         # An array of the parameter name, the name of their OpenLayers
    81         # counterpart, and the type of variable they are.
    82         map_types = [('srid', 'projection', 'srid'),
    83                      ('display_srid', 'displayProjection', 'srid'),
    84                      ('units', 'units', str),
    85                      ('max_resolution', 'maxResolution', float),
    86                      ('max_extent', 'maxExtent', 'bounds'),
    87                      ('num_zoom', 'numZoomLevels', int),
    88                      ('max_zoom', 'maxZoomLevels', int),
    89                      ('min_zoom', 'minZoomLevel', int),
    90                      ]
    91 
    92         # Building the map options hash.
    93         map_options = {}
    94         for param_name, js_name, option_type in map_types:
    95             if self.params.get(param_name, False):
    96                 if option_type == 'srid':
    97                     value = ol_projection(self.params[param_name])
    98                 elif option_type == 'bounds':
    99                     value = ol_bounds(self.params[param_name])
    100                 elif option_type in (float, int):
    101                     value = self.params[param_name]
    102                 elif option_type in (str,):
    103                     value = '"%s"' % self.params[param_name]
    104                 else:
    105                     raise TypeError
    106                 map_options[js_name] = value
    107         return map_options
  • contrib/gis/widgets.py

     
     1from os.path import join
     2
     3from django.contrib.gis.gdal import OGRException
     4from django.contrib.gis.geos import GEOSGeometry, GEOSException
     5from django.forms.widgets import Textarea
     6from django.template.loader import render_to_string
     7from django.utils import simplejson
     8from django.conf import settings
     9from django import forms
     10
     11try:
     12    GOOGLE_API_KEY = settings.GOOGLE_API_KEY
     13except AttributeError:
     14    GOOGLE_API_KEY = ""
     15
     16try:
     17    YAHOO_APP_ID = settings.YAHOO_APP_ID
     18except AttributeError:
     19    YAHOO_APP_ID = ""
     20
     21
     22GOOGLE_API = "http://maps.google.com/maps?file=api&v=2&key=%s" % GOOGLE_API_KEY
     23YAHOO_API = "http://api.maps.yahoo.com/ajaxymap?v=3.0&appid=%s" % YAHOO_APP_ID
     24MS_VE_API = "http://dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=6.1"
     25OSM_API = "http://openstreetmap.org/openlayers/OpenStreetMap.js"
     26OL_API = "http://openlayers.org/api/2.8/OpenLayers.js"
     27OLWIDGET_JS = join(settings.ADMIN_MEDIA_PREFIX, "js/olwidget.js")
     28OLWIDGET_CSS = join(settings.ADMIN_MEDIA_PREFIX, "css/olwidget.css")
     29
     30DEFAULT_PROJ = "4326"
     31
     32PEP8_OPTIONS_TRANSLATION = {
     33    # map constructor options
     34    'map_options': 'mapOptions',
     35    'max_resolution': 'maxResolution',
     36    'min_resolution': 'minResolution',
     37    'max_extent': 'maxExtent',
     38    'min_extent': 'minExtent',
     39    'min_scale': 'minScale',
     40    'max_scale': 'maxScale',
     41    'display_projection': 'displayProjection',
     42    'num_zoom_levels': 'numZoomLevels',
     43    # olwidget options
     44    'map_div_class': 'mapDivClass',
     45    'map_div_style': 'mapDivStyle',
     46    'default_lon': 'defaultLon',
     47    'default_lat': 'defaultLat',
     48    'default_zoom': 'defaultZoom',
     49    'hide_textarea': 'hideTextarea',
     50    'is_collection': 'isCollection',
     51    'popups_outside': 'popupsOutside',
     52    'popup_direction': 'popupDirection',
     53    'popup_pagination_separator': 'popupPaginationSeparator',
     54    'cluster_display': 'clusterDisplay',
     55    'cluster_style': 'clusterStyle',
     56    'font_size': 'fontSize',
     57    'font_family': 'fontFamily',
     58    'font_color': 'fontColor',
     59    # overlay style options
     60    'overlay_style': 'overlayStyle',
     61    'fill_color': 'fillColor',
     62    'fill_opacity': 'fillOpacity',
     63    'stroke_color': 'strokeColor',
     64    'stroke_width': 'strokeWidth',
     65    'stroke_linecap': 'strokeLinecap',
     66    'stroke_dash_style': 'strokeDashstyle',
     67    'point_radius': 'pointRadius',
     68    'external_graphic': 'externalGraphic',
     69    'graphic_height': 'graphicHeight',
     70    'graphic_x_offset': 'graphicXOffset',
     71    'graphic_y_offset': 'graphicYOffset',
     72    'graphic_opacity': 'graphicOpacity',
     73    'graphic_name': 'graphicName',
     74}
     75
     76def translate_options(options):
     77    translated = {}
     78    for key, value in options.iteritems():
     79        new_key = PEP8_OPTIONS_TRANSLATION.get(key, key)
     80        # recurse
     81        if isinstance(value, dict):
     82            translated[new_key] = translate_options(value)
     83        else:
     84            translated[new_key] = value
     85    return translated
     86
     87class MapMixin(object):
     88    def set_options(self, options, template):
     89        self.options = options or {}
     90        # Though this is the olwidget.js default, it must be explicitly set so
     91        # form.media knows to include osm.
     92        self.options['layers'] = self.options.get('layers', ['osm.mapnik'])
     93        self.template = template or self.default_template
     94
     95    def _media(self):
     96        js = set()
     97        # collect scripts necessary for various layers
     98        for layer in self.options['layers']:
     99            if layer.startswith("osm."):
     100                js.add(OSM_API)
     101            elif layer.startswith("google."):
     102                js.add(GOOGLE_API)
     103            elif layer.startswith("yahoo."):
     104                js.add(YAHOO_API)
     105            elif layer.startswith("ve."):
     106                js.add(MS_VE_API)
     107        js = [OL_API, OLWIDGET_JS] + list(js)
     108        return forms.Media(css={'all': (OLWIDGET_CSS,)}, js=js)
     109    media = property(_media)
     110
     111class EditableMap(forms.Textarea, MapMixin):
     112    """
     113    An OpenLayers mapping widget for geographic data.
     114
     115    Example::
     116
     117        from django import forms
     118        from olwidget.widgets import OLWidget
     119
     120        class MyForm(forms.Form):
     121            location = forms.CharField(widget=EditableMap(
     122                options={'geometry': 'point'}))
     123    """
     124    default_template = 'gis/editable_map.html'
     125
     126    def __init__(self, options=None, template=None):
     127        self.set_options(options, template)
     128        super(EditableMap, self).__init__()
     129
     130    def render(self, name, value, attrs=None):
     131        if not attrs:
     132            attrs = {}
     133        # without an id, javascript fails
     134        if attrs.has_key('id'):
     135            element_id = attrs['id']
     136        else:
     137            element_id = "id_%s" % id(self)
     138
     139        # Allow passing of wkt for MapDisplay subclass
     140        if attrs.has_key('wkt'):
     141            wkt = attrs['wkt']
     142        else:
     143            # Use the default SRID's
     144            wkt = add_srid(get_wkt(value))
     145
     146        if name and not self.options.has_key('name'):
     147            self.options['name'] = name
     148
     149        context = {
     150            'id': element_id,
     151            'name': name,
     152            'wkt': wkt,
     153            'map_opts': simplejson.dumps(
     154                translate_options(self.options)
     155            ),
     156        }
     157        return render_to_string(self.template, context)
     158
     159class MapDisplay(EditableMap):
     160    """
     161    Object for display of geometries on an OpenLayers map.  Arguments (all are
     162    optional):
     163
     164    * ``fields`` - a list of geometric fields or WKT strings to display on the
     165      map.  If none are given, the map will have no overlay.
     166    * ``name`` - a name to use for display of the field data layer.
     167    * ``options`` - a dict of options for map display.  A complete list of
     168      options is in the documentation for olwidget.js.
     169    """
     170
     171    def __init__(self, fields=None, options=None, template=None):
     172        self.fields = fields
     173
     174        options = options or {}
     175        if not options.has_key('editable'):
     176            options['editable'] = False
     177
     178        if (self.fields and len(self.fields) > 1) or \
     179                (fields[0].geom_type.upper() == 'GEOMETRYCOLLECTION'):
     180            options['isCollection'] = True
     181
     182        super(MapDisplay, self).__init__(options, template)
     183
     184    def __unicode__(self):
     185        wkt = add_srid(collection_wkt(self.fields))
     186        name = self.options.get('name', 'data')
     187        return self.render(name, None, attrs={'wkt': wkt})
     188
     189class InfoMap(forms.Widget, MapMixin):
     190    """
     191    Widget for displaying maps with pop-up info boxes over geometries.
     192    Arguments:
     193
     194    * ``info``: an array of [geometry, HTML] pairs that specify geometries, and
     195      the popup contents associated with them. Geometries can be expressed as
     196      geometry fields, or as WKT strings.  Example::
     197
     198          [
     199            [geomodel1.geofield, "<p>Model One</p>"],
     200            [geomodel2.geofield, "<p>Model Two</p>"],
     201            ...
     202          ]
     203
     204    * ``options``: an optional dict of options for map display.
     205     
     206    """
     207    default_template = 'gis/info_map.html'
     208
     209    def __init__(self, info=None, options=None, template=None):
     210        self.info = info
     211        self.set_options(options, template)
     212        super(InfoMap, self).__init__()
     213
     214    def render(self, name, value, attrs=None):
     215        if self.info is None:
     216            info_json = '[]'
     217        else:
     218            # convert fields to wkt
     219            for geom, html in self.info:
     220                wkt_array = [[add_srid(collection_wkt(geom)), html] for geom, html in self.info]
     221            info_json = simplejson.dumps(wkt_array)
     222
     223        # arbitrary unique id
     224        div_id = "id_%s" % id(self)
     225
     226        context = {
     227            'id': div_id,
     228            'info_array': info_json,
     229            'map_opts': simplejson.dumps(
     230                translate_options(self.options)
     231            ),
     232        }
     233        return render_to_string(self.template, context)
     234
     235    def __unicode__(self):
     236        return self.render(None, None)
     237
     238def get_wkt(value, srid=DEFAULT_PROJ):
     239    """
     240    `value` is either a WKT string or a geometry field.  Returns WKT in the
     241    projection for the given SRID.
     242    """
     243    if isinstance(value, basestring):
     244        try:
     245            value = GEOSGeometry(value)
     246        except (GEOSException, ValueError):
     247            value = None
     248
     249    wkt = ''
     250    if value:
     251        try:
     252            ogr = value.ogr
     253            ogr.transform(srid)
     254            wkt = ogr.wkt
     255        except OGRException:
     256            pass
     257    return wkt
     258
     259def collection_wkt(fields):
     260    """ Returns WKT for the given list of geometry fields. """
     261
     262    if not fields:
     263        return ""
     264
     265    if len(fields) == 1:
     266        return get_wkt(fields[0])
     267
     268    return "GEOMETRYCOLLECTION(%s)" % \
     269            ",".join(get_wkt(field) for field in fields)
     270
     271def add_srid(wkt, srid=DEFAULT_PROJ):
     272    """
     273    Returns EWKT (WKT with a specified SRID) for the given wkt and SRID
     274    (default 4326).
     275    """
     276    if wkt:
     277        return "SRID=%s;%s" % (srid, wkt)
     278    return ""
     279
  • contrib/gis/templates/gis/admin/osm.html

     
    1 {% extends "gis/admin/openlayers.html" %}
    2 {% block openlayers %}{% include "gis/admin/osm.js" %}{% endblock %}
    3  No newline at end of file
  • contrib/gis/templates/gis/admin/admin_editable_map.html

     
     1<span style='float: left;'>
     2    {% include "gis/editable_map.html" %}
     3</span>
     4<div style='clear: both;'></div>
  • contrib/gis/templates/gis/admin/olwidget_change_list.html

     
     1{% extends "admin/change_list.html" %}
     2
     3{% block content %}
     4    {% if not is_popup %}
     5        {{ map }}
     6    {% endif %}
     7
     8    {{ block.super }}
     9{% endblock %}
  • contrib/gis/templates/gis/admin/openlayers.html

     
    1 {% block extrastyle %}
    2 <style type="text/css">
    3   #{{ id }}_map { width: {{ map_width }}px; height: {{ map_height }}px; }
    4   #{{ id }}_map .aligned label { float:inherit; }
    5   #{{ id }}_admin_map { position: relative; vertical-align: top; float: {{ LANGUAGE_BIDI|yesno:"right,left" }}; }
    6   {% if not display_wkt %}#{{ id }} { display: none; }{% endif %}
    7   .olControlEditingToolbar .olControlModifyFeatureItemActive {
    8      background-image: url("{{ ADMIN_MEDIA_PREFIX }}img/gis/move_vertex_on.png");
    9      background-repeat: no-repeat;
    10   }
    11   .olControlEditingToolbar .olControlModifyFeatureItemInactive {
    12      background-image: url("{{ ADMIN_MEDIA_PREFIX }}img/gis/move_vertex_off.png");
    13      background-repeat: no-repeat;
    14   }
    15 </style>
    16 <!--[if IE]>
    17 <style type="text/css">
    18   /* This fixes the mouse offset issues in IE. */
    19   #{{ id }}_admin_map { position: static; vertical-align: top; }
    20   /* `font-size: 0` fixes the 1px border between tiles, but borks LayerSwitcher.
    21       Thus, this is disabled until a better fix is found.
    22   #{{ id }}_map { width: {{ map_width }}px; height: {{ map_height }}px; font-size: 0; } */
    23 </style>
    24 <![endif]-->
    25 {% endblock %}
    26 <span id="{{ id }}_admin_map">
    27 <script type="text/javascript">
    28 //<![CDATA[
    29 {% block openlayers %}{% include "gis/admin/openlayers.js" %}{% endblock %}
    30 //]]>
    31 </script>
    32 <div id="{{ id }}_map"{% if LANGUAGE_BIDI %} dir="ltr"{% endif %}></div>
    33 <a href="javascript:{{ module }}.clearFeatures()">Delete all Features</a>
    34 {% if display_wkt %}<p> WKT debugging window:</p>{% endif %}
    35 <textarea id="{{ id }}" class="vWKTField required" cols="150" rows="10" name="{{ name }}">{{ wkt }}</textarea>
    36 <script type="text/javascript">{% block init_function %}{{ module }}.init();{% endblock %}</script>
    37 </span>
  • contrib/gis/templates/gis/admin/osm.js

     
    1 {% extends "gis/admin/openlayers.js" %}
    2 {% block base_layer %}new OpenLayers.Layer.OSM.Mapnik("OpenStreetMap (Mapnik)");{% endblock %}
  • contrib/gis/templates/gis/admin/openlayers.js

     
    1 {# Author: Justin Bronn, Travis Pinney & Dane Springmeyer #}
    2 {% block vars %}var {{ module }} = {};
    3 {{ module }}.map = null; {{ module }}.controls = null; {{ module }}.panel = null; {{ module }}.re = new RegExp("^SRID=\d+;(.+)", "i"); {{ module }}.layers = {};
    4 {{ module }}.wkt_f = new OpenLayers.Format.WKT();
    5 {{ module }}.is_collection = {{ is_collection|yesno:"true,false" }};
    6 {{ module }}.collection_type = '{{ collection_type }}';
    7 {{ module }}.is_linestring = {{ is_linestring|yesno:"true,false" }};
    8 {{ module }}.is_polygon = {{ is_polygon|yesno:"true,false" }};
    9 {{ module }}.is_point = {{ is_point|yesno:"true,false" }};
    10 {% endblock %}
    11 {{ module }}.get_ewkt = function(feat){return 'SRID={{ srid }};' + {{ module }}.wkt_f.write(feat);}
    12 {{ module }}.read_wkt = function(wkt){
    13   // OpenLayers cannot handle EWKT -- we make sure to strip it out.
    14   // EWKT is only exposed to OL if there's a validation error in the admin.
    15   var match = {{ module }}.re.exec(wkt);
    16   if (match){wkt = match[1];}
    17   return {{ module }}.wkt_f.read(wkt);
    18 }
    19 {{ module }}.write_wkt = function(feat){
    20   if ({{ module }}.is_collection){ {{ module }}.num_geom = feat.geometry.components.length;}
    21   else { {{ module }}.num_geom = 1;}
    22   document.getElementById('{{ id }}').value = {{ module }}.get_ewkt(feat);
    23 }
    24 {{ module }}.add_wkt = function(event){
    25   // This function will sync the contents of the `vector` layer with the
    26   // WKT in the text field.
    27   if ({{ module }}.is_collection){
    28     var feat = new OpenLayers.Feature.Vector(new OpenLayers.Geometry.{{ geom_type }}());
    29     for (var i = 0; i < {{ module }}.layers.vector.features.length; i++){
    30       feat.geometry.addComponents([{{ module }}.layers.vector.features[i].geometry]);
    31     }
    32     {{ module }}.write_wkt(feat);
    33   } else {
    34     // Make sure to remove any previously added features.
    35     if ({{ module }}.layers.vector.features.length > 1){
    36       old_feats = [{{ module }}.layers.vector.features[0]];
    37       {{ module }}.layers.vector.removeFeatures(old_feats);
    38       {{ module }}.layers.vector.destroyFeatures(old_feats);
    39     }
    40     {{ module }}.write_wkt(event.feature);
    41   }
    42 }
    43 {{ module }}.modify_wkt = function(event){
    44   if ({{ module }}.is_collection){
    45     if ({{ module }}.is_point){
    46       {{ module }}.add_wkt(event);
    47       return;
    48     } else {
    49       // When modifying the selected components are added to the
    50       // vector layer so we only increment to the `num_geom` value.
    51       var feat = new OpenLayers.Feature.Vector(new OpenLayers.Geometry.{{ geom_type }}());
    52       for (var i = 0; i < {{ module }}.num_geom; i++){
    53         feat.geometry.addComponents([{{ module }}.layers.vector.features[i].geometry]);
    54       }
    55       {{ module }}.write_wkt(feat);
    56     }
    57   } else {
    58     {{ module }}.write_wkt(event.feature);
    59   }
    60 }
    61 // Function to clear vector features and purge wkt from div
    62 {{ module }}.deleteFeatures = function(){
    63   {{ module }}.layers.vector.removeFeatures({{ module }}.layers.vector.features);
    64   {{ module }}.layers.vector.destroyFeatures();
    65 }
    66 {{ module }}.clearFeatures = function (){
    67   {{ module }}.deleteFeatures();
    68   document.getElementById('{{ id }}').value = '';
    69   {{ module }}.map.setCenter(new OpenLayers.LonLat({{ default_lon }}, {{ default_lat }}), {{ default_zoom }});
    70 }
    71 // Add Select control
    72 {{ module }}.addSelectControl = function(){   
    73   var select = new OpenLayers.Control.SelectFeature({{ module }}.layers.vector, {'toggle' : true, 'clickout' : true});
    74   {{ module }}.map.addControl(select);
    75   select.activate();
    76 }
    77 {{ module }}.enableDrawing = function(){ {{ module }}.map.getControlsByClass('OpenLayers.Control.DrawFeature')[0].activate();}
    78 {{ module }}.enableEditing = function(){ {{ module }}.map.getControlsByClass('OpenLayers.Control.ModifyFeature')[0].activate();}
    79 // Create an array of controls based on geometry type
    80 {{ module }}.getControls = function(lyr){
    81   {{ module }}.panel = new OpenLayers.Control.Panel({'displayClass': 'olControlEditingToolbar'});
    82   var nav = new OpenLayers.Control.Navigation();
    83   var draw_ctl;
    84   if ({{ module }}.is_linestring){
    85     draw_ctl = new OpenLayers.Control.DrawFeature(lyr, OpenLayers.Handler.Path, {'displayClass': 'olControlDrawFeaturePath'});
    86   } else if ({{ module }}.is_polygon){
    87     draw_ctl = new OpenLayers.Control.DrawFeature(lyr, OpenLayers.Handler.Polygon, {'displayClass': 'olControlDrawFeaturePolygon'});
    88   } else if ({{ module }}.is_point){
    89     draw_ctl = new OpenLayers.Control.DrawFeature(lyr, OpenLayers.Handler.Point, {'displayClass': 'olControlDrawFeaturePoint'});
    90   }
    91   {% if modifiable %}
    92   var mod = new OpenLayers.Control.ModifyFeature(lyr, {'displayClass': 'olControlModifyFeature'});
    93   {{ module }}.controls = [nav, draw_ctl, mod];
    94   {% else %}
    95   {{ module }}.controls = [nav, darw_ctl];
    96   {% endif %} 
    97 }
    98 {{ module }}.init = function(){
    99     {% block map_options %}// The options hash, w/ zoom, resolution, and projection settings.
    100     var options = {
    101 {% autoescape off %}{% for item in map_options.items %}      '{{ item.0 }}' : {{ item.1 }}{% if not forloop.last %},{% endif %}
    102 {% endfor %}{% endautoescape %}    };{% endblock %}
    103     // The admin map for this geometry field.
    104     {{ module }}.map = new OpenLayers.Map('{{ id }}_map', options);
    105     // Base Layer
    106     {{ module }}.layers.base = {% block base_layer %}new OpenLayers.Layer.WMS( "{{ wms_name }}", "{{ wms_url }}", {layers: '{{ wms_layer }}'} );{% endblock %}
    107     {{ module }}.map.addLayer({{ module }}.layers.base);
    108     {% block extra_layers %}{% endblock %}
    109     {% if is_linestring %}OpenLayers.Feature.Vector.style["default"]["strokeWidth"] = 3; // Default too thin for linestrings. {% endif %}
    110     {{ module }}.layers.vector = new OpenLayers.Layer.Vector(" {{ field_name }}");
    111     {{ module }}.map.addLayer({{ module }}.layers.vector);
    112     // Read WKT from the text field.
    113     var wkt = document.getElementById('{{ id }}').value;
    114     if (wkt){
    115       // After reading into geometry, immediately write back to
    116       // WKT <textarea> as EWKT (so that SRID is included).
    117       var admin_geom = {{ module }}.read_wkt(wkt);
    118       {{ module }}.write_wkt(admin_geom);
    119       if ({{ module }}.is_collection){
    120         // If geometry collection, add each component individually so they may be
    121         // edited individually.
    122         for (var i = 0; i < {{ module }}.num_geom; i++){
    123           {{ module }}.layers.vector.addFeatures([new OpenLayers.Feature.Vector(admin_geom.geometry.components[i].clone())]);
    124         }
    125       } else {
    126         {{ module }}.layers.vector.addFeatures([admin_geom]);
    127       }
    128       // Zooming to the bounds.
    129       {{ module }}.map.zoomToExtent(admin_geom.geometry.getBounds());
    130       if ({{ module }}.is_point){
    131           {{ module }}.map.zoomTo({{ point_zoom }});
    132       }
    133     } else {
    134       {{ module }}.map.setCenter(new OpenLayers.LonLat({{ default_lon }}, {{ default_lat }}), {{ default_zoom }});
    135     }
    136     // This allows editing of the geographic fields -- the modified WKT is
    137     // written back to the content field (as EWKT, so that the ORM will know
    138     // to transform back to original SRID).
    139     {{ module }}.layers.vector.events.on({"featuremodified" : {{ module }}.modify_wkt});
    140     {{ module }}.layers.vector.events.on({"featureadded" : {{ module }}.add_wkt});
    141     {% block controls %}
    142     // Map controls:
    143     // Add geometry specific panel of toolbar controls
    144     {{ module }}.getControls({{ module }}.layers.vector);
    145     {{ module }}.panel.addControls({{ module }}.controls);
    146     {{ module }}.map.addControl({{ module }}.panel);
    147     {{ module }}.addSelectControl();
    148     // Then add optional visual controls
    149     {% if mouse_position %}{{ module }}.map.addControl(new OpenLayers.Control.MousePosition());{% endif %}
    150     {% if scale_text %}{{ module }}.map.addControl(new OpenLayers.Control.Scale());{% endif %}
    151     {% if layerswitcher %}{{ module }}.map.addControl(new OpenLayers.Control.LayerSwitcher());{% endif %}
    152     // Then add optional behavior controls
    153     {% if not scrollable %}{{ module }}.map.getControlsByClass('OpenLayers.Control.Navigation')[0].disableZoomWheel();{% endif %}
    154     {% endblock %}
    155     if (wkt){
    156       {{ module }}.enableEditing();
    157     } else {
    158       {{ module }}.enableDrawing();
    159     }
    160 }
  • contrib/gis/templates/gis/editable_map.html

     
     1<textarea id="{{ id }}" name="{{ name }}">{{ wkt }}</textarea>
     2<script type='text/javascript'>
     3    new olwidget.EditableMap("{{ id }}", {{map_opts|safe}});
     4</script>
  • contrib/gis/templates/gis/info_map.html

     
     1<div id="{{ id }}"></div>
     2<script type='text/javascript'>
     3    new olwidget.InfoMap("{{ id }}", {{ info_array|safe }}, {{ map_opts|safe }});
     4</script>
  • contrib/admin/media/css/olwidget.css

     
     1/*
     2 * Extra editing icons
     3 */
     4.olControlEditingToolbar .olControlModifyFeatureItemInactive {
     5    background: url("../img/gis/extra_edit_icons.png") no-repeat scroll -48px 0px;
     6}
     7.olControlEditingToolbar .olControlModifyFeatureItemActive {
     8    background: url("../img/gis/extra_edit_icons.png") no-repeat scroll -24px 0px;
     9}
     10.olControlEditingToolbar .olControlClearFeaturesItemInactive,
     11.olControlEditingToolbar .olControlClearFeaturesItemActive {
     12    background: url("../img/gis/extra_edit_icons.png") no-repeat scroll -0px 0px;
     13}
     14div.olControlEditingToolbar {
     15    width: 210px;
     16}
     17
     18/* Override padding to avoid position computation errors */
     19.olPopupContent {
     20    padding: 0px !important;
     21    overflow: visible !important;
     22}
     23/* Show popups over other controls */
     24.olPopup {
     25    z-index: 1005 !important;
     26}
     27
     28/* Div containing all popup elements */
     29.olwidgetPopupContent {
     30    overflow: visible;
     31    padding: 2px;
     32    border: 2px solid #777;
     33    background-color: #777;
     34}
     35
     36.olwidgetPopupCloseBox {
     37    cursor: pointer;
     38    position: absolute;
     39    right: 10px;
     40    top: 10px;
     41    background: #fff url("../img/gis/popup_icons.png") no-repeat scroll -80px 0px;
     42    padding-top: 16px;
     43    width: 16px;
     44    height: 0px;
     45    overflow: hidden;
     46}
     47.olwidgetPopupCloseBox:hover {
     48    background-position: -64px 0px;
     49}
     50/* Div containing content user HTML */
     51.olwidgetPopupPage {
     52    overflow: auto;
     53    background: #fff;
     54    padding: 8px;
     55    padding-top: 23px;
     56
     57    /* IE6 fix */
     58    zoom: 1;
     59}
     60
     61.olwidgetPopupPagination {
     62    clear: both;
     63    float: right;
     64    background: #fff;
     65    padding-left: 5px;
     66    padding-right: 5px;
     67}
     68.olwidgetPaginationPrevious {
     69    float: left;
     70    cursor: pointer;
     71    /* Replace text with background image trick */
     72    background: url("../img/gis/popup_icons.png") no-repeat scroll -48px 0px;
     73    padding-top: 16px;
     74    width: 16px;
     75    height: 0px;
     76    overflow: hidden;
     77}
     78.olwidgetPaginationPrevious:hover {
     79    background-position: -32px 0px;
     80}
     81
     82/* Div containing page count, e.g. "1 of 5". */
     83.olwidgetPaginationCount {
     84    float: left;
     85    margin-left: 0.5em;
     86    margin-right: 0.5em;
     87}
     88.olwidgetPaginationNext {
     89    float: left;
     90    cursor: pointer;
     91    /* Replace text with background image trick */
     92    background: url("../img/gis/popup_icons.png") no-repeat scroll -16px 0px;
     93    padding-top: 16px;
     94    width: 16px;
     95    height: 0px;
     96    overflow: hidden;
     97}
     98.olwidgetPaginationNext:hover {
     99    background-position: 0px 0px;
     100}
     101.olwidgetClusterList {
     102    margin: 0;
     103    padding-left: 1em;
     104}
     105
     106/* position blocks */
     107.olwidgetPopupStemTL, .olwidgetPopupStemTR {
     108    background: url("../img/gis/popup_icons.png") no-repeat scroll -124px 0px;
     109}
     110.olwidgetPopupStemBL, .olwidgetPopupStemBR {
     111    background: url("../img/gis/popup_icons.png") no-repeat scroll -96px 0px;
     112}
  • contrib/admin/media/img/gis/jquery_ui_license.txt

    Cannot display: file marked as a binary type.
    svn:mime-type = application/octet-stream
    
    Property changes on: contrib/admin/media/img/gis/popup_icons.png
    ___________________________________________________________________
    Added: svn:mime-type
       + application/octet-stream
    
    Cannot display: file marked as a binary type.
    svn:mime-type = application/octet-stream
    
    Property changes on: contrib/admin/media/img/gis/extra_edit_icons.png
    ___________________________________________________________________
    Added: svn:mime-type
       + application/octet-stream
    
     
     1The file "popup_icons.png" contain images taken from the jquery-ui library,
     2under the following license:
     3
     4Copyright (c) 2009 Paul Bakaus, http://jqueryui.com/
     5
     6This software consists of voluntary contributions made by many
     7individuals (AUTHORS.txt, http://jqueryui.com/about) For exact
     8contribution history, see the revision history and logs, available
     9at http://jquery-ui.googlecode.com/svn/
     10
     11Permission is hereby granted, free of charge, to any person obtaining
     12a copy of this software and associated documentation files (the
     13"Software"), to deal in the Software without restriction, including
     14without limitation the rights to use, copy, modify, merge, publish,
     15distribute, sublicense, and/or sell copies of the Software, and to
     16permit persons to whom the Software is furnished to do so, subject to
     17the following conditions:
     18
     19The above copyright notice and this permission notice shall be
     20included in all copies or substantial portions of the Software.
     21
     22THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
     23EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
     24MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
     25NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
     26LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
     27OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
     28WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
     29
  • contrib/admin/media/js/olwidget.js

     
     1/*
     2 *  Simplified open layers mapping widgets.
     3 *
     4 *  olwidget.EditableMap(textareaID, options) -- replace a textarea containing
     5 *  (E)WKT data with an editable map.
     6 *
     7 *  olwidget.InfoMap(div_id, data, options) -- replace a div with a map which
     8 *  displays the data (an array of [WKT, html] pairs) in clickable popups.
     9 *
     10 */
     11(function() {
     12
     13/*
     14 *  Base namespace and utility functions
     15 */
     16var olwidget = {
     17    /*
     18     * WKT transformations
     19     */
     20    wktFormat: new OpenLayers.Format.WKT(),
     21    featureToEWKT: function(feature, proj) {
     22        // convert "EPSG:" in projCode to 'SRID='
     23        var srid = 'SRID=' + proj.projCode.substring(5) + ';';
     24        return srid + this.wktFormat.write(feature);
     25    },
     26    stripSRIDre: new RegExp("^SRID=\\d+;(.+)", "i"),
     27    ewktToFeature: function(wkt) {
     28        var match = this.stripSRIDre.exec(wkt);
     29        if (match) {
     30            wkt = match[1];
     31        }
     32        return this.wktFormat.read(wkt);
     33    },
     34    multiGeometryClasses: {
     35        'linestring': OpenLayers.Geometry.MultiLineString,
     36        'point': OpenLayers.Geometry.MultiPoint,
     37        'polygon': OpenLayers.Geometry.MultiPolygon,
     38        'collection': OpenLayers.Geometry.Collection
     39    },
     40    /*
     41     * Projection transformation
     42     */
     43    transformVector: function(vector, fromProj, toProj) {
     44        // Transform the projection of a feature vector or an array of feature
     45        // vectors (as used in a collection) between the given projections.
     46        if (fromProj.projCode == toProj.projCode) {
     47            return vector;
     48        }
     49        var transformed;
     50        if (vector.constructor == Array) {
     51            transformed = [];
     52            for (var i = 0; i < vector.length; i++) {
     53                transformed.push(this.transformVector(vector[i], fromProj, toProj));
     54            }
     55        } else {
     56            var cloned = vector.geometry.clone();
     57            transformed = new OpenLayers.Feature.Vector(cloned.transform(fromProj, toProj));
     58        }
     59        return transformed;
     60    },
     61    /*
     62     * Constructors for layers
     63     */
     64    wms: {
     65        map: function() {
     66            return new OpenLayers.Layer.WMS(
     67                    "OpenLayers WMS",
     68                    "http://labs.metacarta.com/wms/vmap0",
     69                    {layers: 'basic'}
     70            );
     71        },
     72        nasa: function() {
     73            return new OpenLayers.Layer.WMS(
     74                    "NASA Global Mosaic",
     75                    "http://t1.hypercube.telascience.org/cgi-bin/landsat7",
     76                    {layers: "landsat7"}
     77            );
     78        },
     79        blank: function() {
     80            return new OpenLayers.Layer("", {isBaseLayer: true});
     81        }
     82    },
     83    osm: {
     84        mapnik: function() {
     85            // Not using OpenLayers.Layer.OSM.Mapnik constructor because of
     86            // an IE6 bug.  This duplicates that constructor.
     87            return new OpenLayers.Layer.OSM("OpenStreetMap (Mapnik)",
     88                    [
     89                        "http://a.tile.openstreetmap.org/${z}/${x}/${y}.png",
     90                        "http://b.tile.openstreetmap.org/${z}/${x}/${y}.png",
     91                        "http://c.tile.openstreetmap.org/${z}/${x}/${y}.png"
     92                    ],
     93                    { numZoomLevels: 19 });
     94        },
     95        osmarender: function() {
     96            return new OpenLayers.Layer.OSM.Osmarender(
     97                    'OpenStreetMap (Osmarender)');
     98        }
     99    },
     100    google: {
     101        streets: function() {
     102            return new OpenLayers.Layer.Google("Google Streets",
     103                    {sphericalMercator: true, numZoomLevels: 20});
     104        },
     105        physical: function() {
     106            return new OpenLayers.Layer.Google("Google Physical",
     107                    {sphericalMercator: true, type: G_PHYSICAL_MAP});
     108        },
     109        satellite: function() {
     110            return new OpenLayers.Layer.Google("Google Satellite",
     111                    {sphericalMercator: true, type: G_SATELLITE_MAP});
     112        },
     113        hybrid: function() {
     114            return new OpenLayers.Layer.Google("Google Hybrid",
     115                    {sphericalMercator: true, type: G_HYBRID_MAP, numZoomLevels: 20});
     116        }
     117    },
     118    yahoo: {
     119        map: function() {
     120            return new OpenLayers.Layer.Yahoo("Yahoo",
     121                    {sphericalMercator: true, numZoomLevels: 20});
     122        }
     123    },
     124    ve: {
     125        map: function(type, typeName) {
     126            /*
     127               VE does not play nice with vector layers at zoom level 1.
     128               Also, map may need "panMethod: OpenLayers.Easing.Linear.easeOut"
     129               to avoid drift.  See:
     130
     131               http://openlayers.com/dev/examples/ve-novibrate.html
     132
     133            */
     134               
     135            return new OpenLayers.Layer.VirtualEarth("Microsoft VE (" + typeName + ")",
     136                {sphericalMercator: true, minZoomLevel: 2, type: type });
     137        },
     138        road: function() { return this.map(VEMapStyle.Road, "Road"); },
     139        shaded: function() { return this.map(VEMapStyle.Shaded, "Shaded"); },
     140        aerial: function() { return this.map(VEMapStyle.Aerial, "Aerial"); },
     141        hybrid: function() { return this.map(VEMapStyle.Hybrid, "Hybrid"); }
     142    },
     143
     144
     145    /*
     146     * Deep copies all attributes in 'source' object into 'destination' object.
     147     * Useful for applying defaults in nested objects.
     148     */
     149    deepJoinOptions: function(destination, source) {
     150        if (destination == undefined) {
     151            destination = {};
     152        }
     153        for (var a in source) {
     154            if (source[a] != undefined) {
     155                if (typeof(source[a]) == 'object' && source[a].constructor != Array) {
     156                    destination[a] = this.deepJoinOptions(destination[a], source[a]);
     157                } else {
     158                    destination[a] = source[a];
     159                }
     160            }
     161        }
     162        return destination;
     163    }
     164};
     165
     166/*
     167 *  Base olwidget map type.  Extends OpenLayers.Map
     168 */
     169olwidget.BaseMap = OpenLayers.Class(OpenLayers.Map, {
     170    initialize: function(mapDivID, options) {
     171        this.opts = this.initOptions(options);
     172        this.initMap(mapDivID, this.opts);
     173    },
     174    /*
     175     * Extend the passed in options with defaults, and create unserialized
     176     * objects for serialized options (such as projections).
     177     */
     178    initOptions: function(options) {
     179        var defaults = {
     180            mapOptions: {
     181                units: 'm',
     182                maxResolution: 156543.0339,
     183                projection: "EPSG:900913",
     184                displayProjection: "EPSG:4326",
     185                maxExtent: [-20037508.34, -20037508.34, 20037508.34, 20037508.34],
     186                controls: ['LayerSwitcher', 'Navigation', 'PanZoom', 'Attribution']
     187            },
     188            mapDivClass: '',
     189            mapDivStyle: {
     190                width: '600px',
     191                height: '400px'
     192            },
     193            name: 'data',
     194            layers: ['osm.mapnik'],
     195            defaultLon: 0,
     196            defaultLat: 0,
     197            defaultZoom: 4,
     198            overlayStyle: {
     199                fillColor: '#ff00ff',
     200                strokeColor: '#ff00ff',
     201                pointRadius: 6,
     202                fillOpacity: 0.5,
     203                strokeWidth: 2
     204            }
     205        };
     206        var opts = olwidget.deepJoinOptions(defaults, options);
     207
     208        opts.overrideZoom = options.defaultZoom != undefined;
     209        opts.overrideCenter = options.defaultLat != undefined && options.defaultLon != undefined;
     210
     211        // construct objects for serialized options
     212        var me = opts.mapOptions.maxExtent;
     213        opts.mapOptions.maxExtent = new OpenLayers.Bounds(me[0], me[1], me[2], me[3]);
     214        opts.mapOptions.projection = new OpenLayers.Projection(opts.mapOptions.projection);
     215        opts.mapOptions.displayProjection = new OpenLayers.Projection(
     216                opts.mapOptions.displayProjection);
     217        opts.default_center = new OpenLayers.LonLat(opts.defaultLon, opts.defaultLat);
     218        opts.default_center.transform(opts.mapOptions.displayProjection,
     219                opts.mapOptions.projection);
     220        var controls = [];
     221        for (var i = 0; i < opts.mapOptions.controls.length; i++) {
     222            var control = opts.mapOptions.controls[i];
     223            controls.push(new OpenLayers.Control[control]());
     224        }
     225        opts.mapOptions.controls = controls;
     226        return opts;
     227    },
     228    /*
     229     * Initialize the OpenLayers Map instance and add layers.
     230     */
     231    initMap: function(mapDivID, opts) {
     232        var mapDiv = document.getElementById(mapDivID);
     233        OpenLayers.Util.extend(mapDiv.style, opts.mapDivStyle);
     234        mapDiv.className = opts.mapDivClass;
     235        var layers = [];
     236        for (var i = 0; i < opts.layers.length; i++) {
     237            var parts = opts.layers[i].split(".");
     238            layers.push(olwidget[parts[0]][parts[1]]());
     239           
     240            // workaround for problems with Micorsoft layers and vector layer drift
     241            // (see http://openlayers.com/dev/examples/ve-novibrate.html)
     242            if (parts[0] == "ve") {
     243                if (opts.mapOptions.panMethod == undefined) {
     244                    opts.mapOptions.panMethod = OpenLayers.Easing.Linear.easeOut;
     245                }
     246            }
     247        }
     248        var styleMap = new OpenLayers.StyleMap({'default': new OpenLayers.Style(opts.overlayStyle)});
     249
     250        // Super constructor
     251        OpenLayers.Map.prototype.initialize.apply(this, [mapDiv.id, opts.mapOptions]);
     252        this.vectorLayer = new OpenLayers.Layer.Vector(this.opts.name, { styleMap: styleMap });
     253        layers.push(this.vectorLayer);
     254        this.addLayers(layers);
     255        this.setDefaultCenter();
     256    },
     257    setDefaultCenter: function() {
     258        this.setCenter(this.opts.default_center.clone(), this.opts.defaultZoom);
     259    },
     260    clearFeatures: function() {
     261        this.vectorLayer.removeFeatures(this.vectorLayer.features);
     262        this.vectorLayer.destroyFeatures();
     263    }
     264});
     265
     266/*
     267 *  Map type that implements editable vectors.
     268 */
     269olwidget.EditableMap = OpenLayers.Class(olwidget.BaseMap, {
     270    initialize: function(textareaID, options) {
     271        var defaults = {
     272            editable: true,
     273            geometry: 'point',
     274            hideTextarea: true,
     275            isCollection: false
     276        };
     277        options = olwidget.deepJoinOptions(defaults, options);
     278
     279        // set up map div
     280        var mapDiv = document.createElement("div");
     281        mapDiv.id = textareaID + "_map";
     282        this.textarea = document.getElementById(textareaID);
     283        this.textarea.parentNode.insertBefore(mapDiv, this.textarea);
     284
     285        // initialize map
     286        olwidget.BaseMap.prototype.initialize.apply(this, [mapDiv.id, options])
     287
     288        if (this.opts.hideTextarea) {
     289            this.textarea.style.display = 'none';
     290        }
     291
     292        this.initWKTAndCenter()
     293        this.initControls();
     294    },
     295    initWKTAndCenter: function() {
     296        // Read any initial WKT from the text field.  We assume that the
     297        // WKT uses the projection given in "displayProjection", and ignore
     298        // any initial SRID.
     299
     300        var wkt = this.textarea.value;
     301        if (wkt) {
     302            // After reading into geometry, immediately write back to
     303            // WKT <textarea> as EWKT (so the SRID is included if it wasn't
     304            // before).
     305            var geom = olwidget.ewktToFeature(wkt);
     306            geom = olwidget.transformVector(geom, this.displayProjection, this.projection);
     307            if (this.opts.isCollection) {
     308                this.vectorLayer.addFeatures(geom);
     309            } else {
     310                this.vectorLayer.addFeatures([geom]);
     311            }
     312            this.numGeom = this.vectorLayer.features.length;
     313           
     314            // Set center
     315            if (this.opts.geometry == 'point' && !this.opts.isCollection) {
     316                this.setCenter(geom.geometry.getBounds().getCenterLonLat(),
     317                        this.opts.defaultZoom);
     318            } else {
     319                this.zoomToExtent(this.vectorLayer.getDataExtent());
     320            }
     321        }
     322    },
     323    initControls: function() {
     324        // Initialize controls for editing geometries, navigating, and map data.
     325
     326        // This allows editing of the geographic fields -- the modified WKT is
     327        // written back to the content field (as EWKT)
     328        if (this.opts.editable) {
     329            var closuredThis = this;
     330            this.vectorLayer.events.on({
     331                "featuremodified" : function(event) { closuredThis.modifyWKT(event); },
     332                "featureadded": function(event) { closuredThis.addWKT(event); }
     333            });
     334
     335            // Map controls:
     336            // Add geometry specific panel of toolbar controls
     337            var panel = this.buildPanel(this.vectorLayer);
     338            this.addControl(panel);
     339            var select = new OpenLayers.Control.SelectFeature( this.vectorLayer,
     340                    {toggle: true, clickout: true, hover: false});
     341            this.addControl(select);
     342            select.activate();
     343        }
     344    },
     345    clearFeatures: function() {
     346        olwidget.BaseMap.prototype.clearFeatures.apply(this);
     347        this.textarea.value = '';
     348    },
     349    buildPanel: function(layer) {
     350        var panel = new OpenLayers.Control.Panel({displayClass: 'olControlEditingToolbar'});
     351        var controls = [];
     352
     353        var nav = new OpenLayers.Control.Navigation();
     354        controls.push(nav);
     355
     356        // Drawing control(s)
     357        var geometries;
     358        if (this.opts.geometry.constructor == Array) {
     359            geometries = this.opts.geometry;
     360        } else {
     361            geometries = [this.opts.geometry];
     362        }
     363        for (var i = 0; i < geometries.length; i++) {
     364            var drawControl;
     365            if (geometries[i] == 'linestring') {
     366                drawControl = new OpenLayers.Control.DrawFeature(layer,
     367                    OpenLayers.Handler.Path,
     368                    {'displayClass': 'olControlDrawFeaturePath'});
     369            } else if (geometries[i] == 'polygon') {
     370                drawControl = new OpenLayers.Control.DrawFeature(layer,
     371                    OpenLayers.Handler.Polygon,
     372                    {'displayClass': 'olControlDrawFeaturePolygon'});
     373            } else if (geometries[i] == 'point') {
     374                drawControl = new OpenLayers.Control.DrawFeature(layer,
     375                    OpenLayers.Handler.Point,
     376                    {'displayClass': 'olControlDrawFeaturePoint'});
     377            }
     378            controls.push(drawControl);
     379        }
     380           
     381        // Modify feature control
     382        var mod = new OpenLayers.Control.ModifyFeature(layer);
     383        controls.push(mod);
     384
     385        // Clear all control
     386        var closuredThis = this;
     387        var del = new OpenLayers.Control.Button({
     388            displayClass: 'olControlClearFeatures',
     389            trigger: function() {
     390                closuredThis.clearFeatures();
     391            }
     392        });
     393
     394        controls.push(del);
     395        panel.addControls(controls);
     396        return panel;
     397    },
     398    // Callback for openlayers "featureadded"
     399    addWKT: function(event) {
     400        // This function will sync the contents of the `vector` layer with the
     401        // WKT in the text field.
     402        if (this.opts.isCollection) {
     403            this.featureToTextarea(this.vectorLayer.features);
     404        } else {
     405            // Make sure to remove any previously added features.
     406            if (this.vectorLayer.features.length > 1) {
     407                old_feats = [this.vectorLayer.features[0]];
     408                this.vectorLayer.removeFeatures(old_feats);
     409                this.vectorLayer.destroyFeatures(old_feats);
     410            }
     411            this.featureToTextarea(event.feature);
     412        }
     413    },
     414    // Callback for openlayers "featuremodified"
     415    modifyWKT: function(event) {
     416        if (this.opts.isCollection){
     417            // OpenLayers adds points around the modified feature that we want to strip.
     418            // So only take the features up to "numGeom", the number of features counted
     419            // when we last added.
     420            var feat = [];
     421            for (var i = 0; i < this.numGeom; i++) {
     422                feat.push(this.vectorLayer.features[i].clone());
     423            }
     424            this.featureToTextarea(feat)
     425        } else {
     426            this.featureToTextarea(event.feature);
     427        }
     428    },
     429    featureToTextarea: function(feature) {
     430        if (this.opts.isCollection) {
     431            this.numGeom = feature.length;
     432        } else {
     433            this.numGeom = 1;
     434        }
     435        feature = olwidget.transformVector(feature,
     436                this.projection, this.displayProjection);
     437        if (this.opts.isCollection) {
     438            // Convert to multi-geometry types if we are a collection.
     439            // Passing arrays to the WKT formatter results in a
     440            // "GEOMETRYCOLLECTION" type, but if we have only one geometry,
     441            // we should use a "MULTI<geometry>" type.
     442            if (this.opts.geometry.constructor != Array) {
     443                var geoms = [];
     444                for (var i = 0; i < feature.length; i++) {
     445                    geoms.push(feature[i].geometry);
     446                }
     447                var GeoClass = olwidget.multiGeometryClasses[this.opts.geometry];
     448                feature = new OpenLayers.Feature.Vector(new GeoClass(geoms));
     449            }
     450        }
     451        this.textarea.value = olwidget.featureToEWKT(feature, this.displayProjection);
     452    }
     453});
     454
     455/*
     456 *  olwidget.InfoMap -- map type supporting clickable vectors that raise
     457 *  popups.  The popups are displayed optionally inside or outside the map's
     458 *  viewport.
     459 * 
     460 *  Usage: olwidget.Infomap(mapDivID, infoArray, options)
     461 *
     462 *  arguments:
     463 *     mapDivID: the DOM id of a div to replace with the map.
     464 *     infoArray: An array of the form:
     465 *         [ ["WKT", "html"], ... ]
     466 *         where "WKT" represents the well-known text form of the geometry,
     467 *         and "html" represents the HTML content for the popup.
     468 *     options: An options object.  See distribution documentation for details.
     469 */
     470olwidget.InfoMap = OpenLayers.Class(olwidget.BaseMap, {
     471    initialize: function(mapDivID, infoArray, options) {
     472        var infomapDefaults = {
     473            popupsOutside: false,
     474            popupDirection: 'auto',
     475            popupPaginationSeparator: ' of ',
     476            cluster: false,
     477            clusterDisplay: "paginate",
     478            clusterStyle: {
     479                pointRadius: "${radius}",
     480                strokeWidth: "${width}",
     481                label: "${label}",
     482                labelSelect: true,
     483                fontSize: "11px",
     484                fontFamily: "Helvetica, sans-serif",
     485                fontColor: "#ffffff"
     486            }
     487        };
     488
     489        options = olwidget.deepJoinOptions(infomapDefaults, options);
     490        olwidget.BaseMap.prototype.initialize.apply(this, [mapDivID, options]);
     491
     492        // Must have explicitly specified position for popups to work properly.
     493        if (!this.div.style.position) {
     494            this.div.style.position = 'relative';
     495        }
     496
     497        if (this.opts.cluster == true) {
     498            this.addClusterStrategy();
     499        }
     500
     501        var features = [];
     502        for (var i = 0; i < infoArray.length; i++) {
     503            var feature = olwidget.ewktToFeature(infoArray[i][0]);
     504            feature = olwidget.transformVector(feature,
     505                this.displayProjection, this.projection);
     506
     507            if (feature.constructor != Array) {
     508                feature = [feature];
     509            }
     510            for (var k = 0; k < feature.length; k++) {
     511                feature[k].attributes = { html: infoArray[i][1] };
     512                features.push(feature[k]);
     513            }
     514        }
     515        this.vectorLayer.addFeatures(features);
     516
     517        this.select = new OpenLayers.Control.SelectFeature(this.vectorLayer, { clickout: true, hover: false });
     518        this.select.events.register("featurehighlighted", this,
     519                function(evt) { this.createPopup(evt); });
     520        this.select.events.register("featureunhighlighted", this,
     521                function(evt) { this.deletePopup() });
     522       
     523        // Zooming changes clusters, so we must delete popup if we zoom.
     524        this.events.register("zoomend", this, function(event) { this.select.unselectAll(); });
     525
     526        this.addControl(this.select);
     527        this.select.activate();
     528
     529        // Set zoom level
     530        if (this.opts.overrideCenter) {
     531            this.setCenter(this.opts.default_center);
     532        } else {
     533            this.setCenter(this.vectorLayer.getDataExtent().getCenterLonLat());
     534        }
     535        if (this.opts.overrideZoom) {
     536            this.zoomTo(this.opts.defaultZoom);
     537        } else {
     538            this.zoomToExtent(this.vectorLayer.getDataExtent());
     539        }
     540    },
     541    addClusterStrategy: function() {
     542        var style_context = {
     543            width: function(feature) {
     544                return (feature.cluster) ? 2 : 1;
     545            },
     546            radius: function(feature) {
     547                var n = feature.attributes.count;
     548                var pix;
     549                if (n == 1) {
     550                    pix = 6;
     551                } else if (n <= 5) {
     552                    pix = 8;
     553                } else if (n <= 25) {
     554                    pix = 10;
     555                } else if (n <= 50) {
     556                    pix = 12;
     557                } else {
     558                    pix = 14;
     559                }
     560                return pix;
     561            },
     562            label: function(feature) {
     563                return (feature.cluster && feature.cluster.length > 1) ? feature.cluster.length : '';
     564            }
     565        };
     566
     567        var defaultStyleOpts = OpenLayers.Util.applyDefaults(
     568            OpenLayers.Util.applyDefaults({}, this.opts.clusterStyle),
     569            this.vectorLayer.styleMap.styles['default'].defaultStyle);
     570        var selectStyleOpts = OpenLayers.Util.applyDefaults(
     571            OpenLayers.Util.applyDefaults({}, this.opts.clusterStyle),
     572            this.vectorLayer.styleMap.styles['select'].defaultStyle);
     573
     574        var defaultStyle = new OpenLayers.Style(defaultStyleOpts, {context: style_context});
     575        var selectStyle = new OpenLayers.Style(selectStyleOpts, {context: style_context});
     576        this.removeLayer(this.vectorLayer);
     577        this.vectorLayer = new OpenLayers.Layer.Vector(this.opts.name, {
     578            styleMap: new OpenLayers.StyleMap({ 'default': defaultStyle, 'select': selectStyle }),
     579            strategies: [new OpenLayers.Strategy.Cluster()]
     580        });
     581        this.addLayer(this.vectorLayer);
     582    },
     583    /**
     584     * Override parent to allow placement of popups outside viewport
     585     */
     586    addPopup: function(popup, exclusive) {
     587        if (exclusive) {
     588            //remove all other popups from screen
     589            for (var i = this.popups.length - 1; i >= 0; --i) {
     590                this.removePopup(this.popups[i]);
     591            }
     592        }
     593
     594        popup.map = this;
     595        this.popups.push(popup);
     596        var popupDiv = popup.draw();
     597        if (popupDiv) {
     598            popupDiv.style.zIndex = this.Z_INDEX_BASE['Popup'] +
     599                                    this.popups.length;
     600            //if (this.opts.popupsOutside) {
     601                this.div.appendChild(popupDiv);
     602                // store a reference to this function so we can unregister on removal
     603                this.popupMoveFunc = function(event) {
     604                    var px = this.getPixelFromLonLat(this.popup.lonlat);
     605                    popup.moveTo(px);
     606                }
     607                this.events.register("move", this, this.popupMoveFunc);
     608                this.popupMoveFunc();
     609            //} else {
     610            //    this.layerContainerDiv.appendChild(popupDiv);
     611            //}
     612        }
     613    },
     614    /**
     615     * Override parent to allow placement of popups outside viewport
     616     */
     617    removePopup: function(popup) {
     618        OpenLayers.Util.removeItem(this.popups, popup);
     619        if (popup.div) {
     620            try {
     621                //if (this.opts.popupsOutside) {
     622                    this.div.removeChild(popup.div);
     623                    this.events.unregister("move", this, this.popupMoveFunc);
     624                //} else {
     625                //    this.layerContainerDiv.removeChild(popup.div);
     626                //}
     627            } catch (e) { }
     628        }
     629        popup.map = null;
     630    },
     631
     632    /**
     633     * Build a paginated popup
     634     */
     635    createPopup: function(evt) {
     636        var feature = evt.feature;
     637        var lonlat;
     638        if (feature.geometry.CLASS_NAME == "OpenLayers.Geometry.Point") {
     639            lonlat = feature.geometry.getBounds().getCenterLonLat();
     640        } else {
     641            lonlat = this.getLonLatFromViewPortPx(evt.object.handlers.feature.evt.xy);
     642        }
     643           
     644        var popupHTML = [];
     645        if (feature.cluster) {
     646            if (this.opts.clusterDisplay == 'list') {
     647                if (feature.cluster.length > 1) {
     648                    var html = "<ul class='olwidgetClusterList'>";
     649                    for (var i = 0; i < feature.cluster.length; i++) {
     650                        html += "<li>" + feature.cluster[i].attributes.html + "</li>";
     651                    }
     652                    html += "</ul>";
     653                    popupHTML.push(html);
     654                } else {
     655                    popupHTML.push(feature.cluster[0].attributes.html);
     656                }
     657            } else {
     658                for (var i = 0; i < feature.cluster.length; i++) {
     659                    popupHTML.push(feature.cluster[i].attributes.html);
     660                }
     661            }
     662        } else {
     663            popupHTML.push(feature.attributes.html);
     664        }
     665        var infomap = this;
     666        this.popup = new olwidget.Popup(null,
     667                lonlat, null, popupHTML, null, true,
     668                function() { infomap.select.unselect(feature) },
     669                this.opts.popupDirection,
     670                this.opts.popupPaginationSeparator);
     671        if (this.opts.popupsOutside) {
     672            this.popup.panMapIfOutOfView = false;
     673        }
     674        this.addPopup(this.popup);
     675    },
     676
     677    deletePopup: function() {
     678        if (this.popup) {
     679            this.popup.destroy();
     680            this.popup = null;
     681        }
     682    }
     683});
     684
     685/*
     686 * Paginated, framed popup type, CSS stylable.
     687 */
     688olwidget.Popup = OpenLayers.Class(OpenLayers.Popup.Framed, {
     689    autoSize: true,
     690    panMapIfOutOfView: true,
     691    fixedRelativePosition: false,
     692    // Position blocks.  Overriden to include additional "className" parameter,
     693    // allowing image paths relative to css rather than relative to the html
     694    // file (as paths included in a JS file are computed).
     695    positionBlocks: {
     696        "tl": {
     697            'offset': new OpenLayers.Pixel(44, -6),
     698            'padding': new OpenLayers.Bounds(5, 14, 5, 5),
     699            'blocks': [
     700                { // stem
     701                    className: 'olwidgetPopupStemTL',
     702                    size: new OpenLayers.Size(24, 14),
     703                    anchor: new OpenLayers.Bounds(null, 0, 32, null),
     704                    position: new OpenLayers.Pixel(0, -28)
     705                }
     706            ]
     707        },
     708        "tr": {
     709            'offset': new OpenLayers.Pixel(-44, -6),
     710            'padding': new OpenLayers.Bounds(5, 14, 5, 5),
     711            'blocks': [
     712                { // stem
     713                    className: "olwidgetPopupStemTR",
     714                    size: new OpenLayers.Size(24, 14),
     715                    anchor: new OpenLayers.Bounds(32, 0, null, null),
     716                    position: new OpenLayers.Pixel(0, -28)
     717                }
     718            ]
     719        },
     720        "bl": {
     721            'offset': new OpenLayers.Pixel(44, 6),
     722            'padding': new OpenLayers.Bounds(5, 5, 5, 14),
     723            'blocks': [
     724                { // stem
     725                    className: "olwidgetPopupStemBL",
     726                    size: new OpenLayers.Size(24, 14),
     727                    anchor: new OpenLayers.Bounds(null, null, 32, 0),
     728                    position: new OpenLayers.Pixel(0, 0)
     729                }
     730            ]
     731        },
     732        "br": {
     733            'offset': new OpenLayers.Pixel(-44, 6),
     734            'padding': new OpenLayers.Bounds(5, 5, 5, 14),
     735            'blocks': [
     736                { // stem
     737                    className: "olwidgetPopupStemBR",
     738                    size: new OpenLayers.Size(24, 14),
     739                    anchor: new OpenLayers.Bounds(32, null, null, 0),
     740                    position: new OpenLayers.Pixel(0, 0)
     741                }
     742            ]
     743        }
     744    },
     745
     746    initialize: function(id, lonlat, contentSize, contentHTML, anchor, closeBox,
     747                    closeBoxCallback, relativePosition, separator) {
     748        if (relativePosition && relativePosition != 'auto') {
     749            this.fixedRelativePosition = true;
     750            this.relativePosition = relativePosition;
     751        }
     752        if (separator == undefined) {
     753            this.separator = ' of ';
     754        } else {
     755            this.separator = separator;
     756        }
     757        // we don't use the default close box because we want it to appear in
     758        // the content div for easier CSS control.
     759        this.olwidgetCloseBox = closeBox;
     760        this.olwidgetCloseBoxCallback = closeBoxCallback;
     761        this.page = 0;
     762        OpenLayers.Popup.Framed.prototype.initialize.apply(this, [id, lonlat,
     763            contentSize, contentHTML, anchor, false, null]);
     764    },
     765
     766    /*
     767     * Construct the interior of a popup.  If contentHTML is an Array, display
     768     * the array element specified by this.page.
     769     */
     770    setContentHTML: function(contentHTML) {
     771        if (contentHTML != null) {
     772            this.contentHTML = contentHTML;
     773        }
     774
     775        var pageHTML;
     776        var showPagination;
     777        if (this.contentHTML.constructor != Array) {
     778            pageHTML = this.contentHTML;
     779            showPagination = false;
     780        } else {
     781            pageHTML = this.contentHTML[this.page];
     782            showPagination = this.contentHTML.length > 1;
     783        }
     784
     785        if ((this.contentDiv != null) && (pageHTML != null)) {
     786            var popup = this; // for closures
     787
     788            // Clear old contents
     789            this.contentDiv.innerHTML = "";
     790           
     791            // Build container div
     792            var containerDiv = document.createElement("div");
     793            containerDiv.className = 'olwidgetPopupContent';
     794            this.contentDiv.appendChild(containerDiv);
     795
     796            // Build close box
     797            if (this.olwidgetCloseBox) {
     798                closeDiv = document.createElement("div");
     799                closeDiv.className = "olwidgetPopupCloseBox";
     800                closeDiv.innerHTML = "close";
     801                closeDiv.onclick = function(event) {
     802                    popup.olwidgetCloseBoxCallback.apply(popup, arguments);
     803                }
     804                containerDiv.appendChild(closeDiv);
     805            }
     806           
     807            var pageDiv = document.createElement("div");
     808            pageDiv.innerHTML = pageHTML;
     809            pageDiv.className = "olwidgetPopupPage";
     810            containerDiv.appendChild(pageDiv);
     811
     812            if (showPagination) {
     813                // Build pagination control
     814
     815                paginationDiv = document.createElement("div");
     816                paginationDiv.className = "olwidgetPopupPagination";
     817                var prev = document.createElement("div");
     818                prev.className = "olwidgetPaginationPrevious";
     819                prev.innerHTML = "prev";
     820                prev.onclick = function(event) {
     821                    popup.page = (popup.page - 1 + popup.contentHTML.length) %
     822                        popup.contentHTML.length;
     823                    popup.setContentHTML();
     824                    popup.map.events.triggerEvent("move");
     825                }
     826
     827                var count = document.createElement("div");
     828                count.className = "olwidgetPaginationCount";
     829                count.innerHTML = (this.page + 1) + " of " + this.contentHTML.length;
     830                var next = document.createElement("div");
     831                next.className = "olwidgetPaginationNext";
     832                next.innerHTML = "next";
     833                next.onclick = function(event) {
     834                    popup.page = (popup.page + 1) % popup.contentHTML.length;
     835                    popup.setContentHTML();
     836                    popup.map.events.triggerEvent("move");
     837                }
     838
     839                paginationDiv.appendChild(prev);
     840                paginationDiv.appendChild(count);
     841                paginationDiv.appendChild(next);
     842                containerDiv.appendChild(paginationDiv);
     843
     844            }
     845            var clearFloat = document.createElement("div");
     846            clearFloat.style.clear = "both";
     847            containerDiv.appendChild(clearFloat);
     848
     849            if (this.autoSize) {
     850                this.registerImageListeners();
     851                this.updateSize();
     852            }
     853        }
     854    },
     855
     856    /*
     857     * Override parent to make the popup more CSS-friendly.  Rather than
     858     * specifying img paths in javascript, give position blocks CSS classes
     859     * that can be used to apply background images to the divs.
     860     */
     861    createBlocks: function() {
     862        this.blocks = [];
     863
     864        //since all positions contain the same number of blocks, we can
     865        // just pick the first position and use its blocks array to create
     866        // our blocks array
     867        var firstPosition = null;
     868        for(var key in this.positionBlocks) {
     869            firstPosition = key;
     870            break;
     871        }
     872
     873        var position = this.positionBlocks[firstPosition];
     874        for (var i = 0; i < position.blocks.length; i++) {
     875
     876            var block = {};
     877            this.blocks.push(block);
     878
     879            var divId = this.id + '_FrameDecorationDiv_' + i;
     880            block.div = OpenLayers.Util.createDiv(divId,
     881                null, null, null, "absolute", null, "hidden", null
     882            );
     883            this.groupDiv.appendChild(block.div);
     884        }
     885    },
     886    /*
     887     * Override parent to make the popup more CSS-friendly, reflecting
     888     * modifications to createBlocks.
     889     */
     890    updateBlocks: function() {
     891        if (!this.blocks) {
     892            this.createBlocks();
     893        }
     894        if (this.size && this.relativePosition) {
     895            var position = this.positionBlocks[this.relativePosition];
     896            for (var i = 0; i < position.blocks.length; i++) {
     897
     898                var positionBlock = position.blocks[i];
     899                var block = this.blocks[i];
     900
     901                // adjust sizes
     902                var l = positionBlock.anchor.left;
     903                var b = positionBlock.anchor.bottom;
     904                var r = positionBlock.anchor.right;
     905                var t = positionBlock.anchor.top;
     906
     907                //note that we use the isNaN() test here because if the
     908                // size object is initialized with a "auto" parameter, the
     909                // size constructor calls parseFloat() on the string,
     910                // which will turn it into NaN
     911                //
     912                var w = (isNaN(positionBlock.size.w)) ? this.size.w - (r + l)
     913                                                      : positionBlock.size.w;
     914
     915                var h = (isNaN(positionBlock.size.h)) ? this.size.h - (b + t)
     916                                                      : positionBlock.size.h;
     917
     918                block.div.style.width = (w < 0 ? 0 : w) + 'px';
     919                block.div.style.height = (h < 0 ? 0 : h) + 'px';
     920
     921                block.div.style.left = (l != null) ? l + 'px' : '';
     922                block.div.style.bottom = (b != null) ? b + 'px' : '';
     923                block.div.style.right = (r != null) ? r + 'px' : '';
     924                block.div.style.top = (t != null) ? t + 'px' : '';
     925
     926                block.div.className = positionBlock.className;
     927            }
     928
     929            this.contentDiv.style.left = this.padding.left + "px";
     930            this.contentDiv.style.top = this.padding.top + "px";
     931        }
     932    },
     933    updateSize: function() {
     934        if (this.map.opts.popupsOutside == true) {
     935            var preparedHTML = "<div class='" + this.contentDisplayClass+ "'>" +
     936                this.contentDiv.innerHTML +
     937                "</div>";
     938
     939            var containerElement = document.body;
     940            var realSize = OpenLayers.Util.getRenderedDimensions(
     941                preparedHTML, null, {
     942                    displayClass: this.displayClass,
     943                    containerElement: containerElement
     944                }
     945            );
     946            return this.setSize(realSize);
     947        } else {
     948            return OpenLayers.Popup.prototype.updateSize.apply(this, arguments);
     949        }
     950    },
     951
     952    CLASS_NAME: "olwidget.Popup"
     953});
     954
     955// finish anonymous function.  Add olwidget to 'window'.
     956this.olwidget = olwidget;
     957})();
  • contrib/admin/options.py

     
    873873        return self.render_change_form(request, context, change=True, obj=obj)
    874874    change_view = transaction.commit_on_success(change_view)
    875875
    876     def changelist_view(self, request, extra_context=None):
    877         "The 'change list' admin view for this model."
     876    def get_changelist_context(self, request):
     877        "Get the default context for the changelist view."
    878878        from django.contrib.admin.views.main import ChangeList, ERROR_FLAG
    879879        opts = self.model._meta
    880880        app_label = opts.app_label
     
    977977            'actions_on_top': self.actions_on_top,
    978978            'actions_on_bottom': self.actions_on_bottom,
    979979        }
     980        return context
     981
     982    def changelist_view(self, request, extra_context=None):
     983        "The 'change list' admin view for this model."
     984
     985        context = self.get_changelist_context(request)
    980986        context.update(extra_context or {})
    981987        context_instance = template.RequestContext(request, current_app=self.admin_site.name)
    982988        return render_to_response(self.change_list_template or [
Back to Top