| 1 | from django.conf import settings
|
|---|
| 2 | from django.contrib.gis import geos
|
|---|
| 3 | from django.template.loader import render_to_string
|
|---|
| 4 | from django.utils.safestring import mark_safe
|
|---|
| 5 |
|
|---|
| 6 | class GoogleMapException(Exception): pass
|
|---|
| 7 | from django.contrib.gis.maps.google.overlays import GPolygon, GPolyline, GMarker, GIcon
|
|---|
| 8 |
|
|---|
| 9 | # The default Google Maps URL (for the API javascript)
|
|---|
| 10 | # TODO: Internationalize for Japan, UK, etc.
|
|---|
| 11 | GOOGLE_MAPS_URL='http://maps.google.com/maps?file=api&v=%s&key='
|
|---|
| 12 |
|
|---|
| 13 | class GoogleMap(object):
|
|---|
| 14 | "A class for generating Google Maps JavaScript."
|
|---|
| 15 |
|
|---|
| 16 | # String constants
|
|---|
| 17 | onunload = mark_safe('onunload="GUnload()"') # Cleans up after Google Maps
|
|---|
| 18 | vml_css = mark_safe('v\:* {behavior:url(#default#VML);}') # CSS for IE VML
|
|---|
| 19 | xmlns = mark_safe('xmlns:v="urn:schemas-microsoft-com:vml"') # XML Namespace (for IE VML).
|
|---|
| 20 |
|
|---|
| 21 | def __init__(self, key=None, api_url=None, version=None,
|
|---|
| 22 | center=None, zoom=None, dom_id='map',
|
|---|
| 23 | kml_urls=[], polylines=None, polygons=None, markers=None,
|
|---|
| 24 | template='gis/google/google-single.js',
|
|---|
| 25 | js_module='geodjango',
|
|---|
| 26 | extra_context={}):
|
|---|
| 27 |
|
|---|
| 28 | # The Google Maps API Key defined in the settings will be used
|
|---|
| 29 | # if not passed in as a parameter. The use of an API key is
|
|---|
| 30 | # _required_.
|
|---|
| 31 | if not key:
|
|---|
| 32 | try:
|
|---|
| 33 | self.key = settings.GOOGLE_MAPS_API_KEY
|
|---|
| 34 | except AttributeError:
|
|---|
| 35 | raise GoogleMapException('Google Maps API Key not found (try adding GOOGLE_MAPS_API_KEY to your settings).')
|
|---|
| 36 | else:
|
|---|
| 37 | self.key = key
|
|---|
| 38 |
|
|---|
| 39 | # Getting the Google Maps API version, defaults to using the latest ("2.x"),
|
|---|
| 40 | # this is not necessarily the most stable.
|
|---|
| 41 | if not version:
|
|---|
| 42 | self.version = getattr(settings, 'GOOGLE_MAPS_API_VERSION', '2.x')
|
|---|
| 43 | else:
|
|---|
| 44 | self.version = version
|
|---|
| 45 |
|
|---|
| 46 | # Can specify the API URL in the `api_url` keyword.
|
|---|
| 47 | if not api_url:
|
|---|
| 48 | self.api_url = mark_safe(getattr(settings, 'GOOGLE_MAPS_URL', GOOGLE_MAPS_URL) % self.version)
|
|---|
| 49 | else:
|
|---|
| 50 | self.api_url = api_url
|
|---|
| 51 |
|
|---|
| 52 | # Setting the DOM id of the map, the load function, the JavaScript
|
|---|
| 53 | # template, and the KML URLs array.
|
|---|
| 54 | self.dom_id = dom_id
|
|---|
| 55 | self.extra_context = extra_context
|
|---|
| 56 | self.js_module = js_module
|
|---|
| 57 | self.template = template
|
|---|
| 58 | self.kml_urls = kml_urls
|
|---|
| 59 |
|
|---|
| 60 | # Does the user want any GMarker, GPolygon, and/or GPolyline overlays?
|
|---|
| 61 | overlay_info = [[GMarker, markers, 'markers'],
|
|---|
| 62 | [GPolygon, polygons, 'polygons'],
|
|---|
| 63 | [GPolyline, polylines, 'polylines']]
|
|---|
| 64 |
|
|---|
| 65 | for overlay_class, overlay_list, varname in overlay_info:
|
|---|
| 66 | setattr(self, varname, [])
|
|---|
| 67 | if overlay_list:
|
|---|
| 68 | for overlay in overlay_list:
|
|---|
| 69 | if isinstance(overlay, overlay_class):
|
|---|
| 70 | getattr(self, varname).append(overlay)
|
|---|
| 71 | else:
|
|---|
| 72 | getattr(self, varname).append(overlay_class(overlay))
|
|---|
| 73 |
|
|---|
| 74 | # If GMarker, GPolygons, and/or GPolylines are used the zoom will be
|
|---|
| 75 | # automatically calculated via the Google Maps API. If both a zoom
|
|---|
| 76 | # level and a center coordinate are provided with polygons/polylines,
|
|---|
| 77 | # no automatic determination will occur.
|
|---|
| 78 | self.calc_zoom = False
|
|---|
| 79 | if self.polygons or self.polylines or self.markers:
|
|---|
| 80 | if center is None or zoom is None:
|
|---|
| 81 | self.calc_zoom = True
|
|---|
| 82 |
|
|---|
| 83 | # Defaults for the zoom level and center coordinates if the zoom
|
|---|
| 84 | # is not automatically calculated.
|
|---|
| 85 | if zoom is None: zoom = 4
|
|---|
| 86 | self.zoom = zoom
|
|---|
| 87 | if center is None: center = (0, 0)
|
|---|
| 88 | self.center = center
|
|---|
| 89 |
|
|---|
| 90 | def render(self):
|
|---|
| 91 | """
|
|---|
| 92 | Generates the JavaScript necessary for displaying this Google Map.
|
|---|
| 93 | """
|
|---|
| 94 | params = {'calc_zoom' : self.calc_zoom,
|
|---|
| 95 | 'center' : self.center,
|
|---|
| 96 | 'dom_id' : self.dom_id,
|
|---|
| 97 | 'js_module' : self.js_module,
|
|---|
| 98 | 'kml_urls' : self.kml_urls,
|
|---|
| 99 | 'zoom' : self.zoom,
|
|---|
| 100 | 'polygons' : self.polygons,
|
|---|
| 101 | 'polylines' : self.polylines,
|
|---|
| 102 | 'icons': self.icons,
|
|---|
| 103 | 'markers' : self.markers,
|
|---|
| 104 | }
|
|---|
| 105 | params.update(self.extra_context)
|
|---|
| 106 | return render_to_string(self.template, params)
|
|---|
| 107 |
|
|---|
| 108 | @property
|
|---|
| 109 | def body(self):
|
|---|
| 110 | "Returns HTML body tag for loading and unloading Google Maps javascript."
|
|---|
| 111 | return mark_safe('<body %s %s>' % (self.onload, self.onunload))
|
|---|
| 112 |
|
|---|
| 113 | @property
|
|---|
| 114 | def onload(self):
|
|---|
| 115 | "Returns the `onload` HTML <body> attribute."
|
|---|
| 116 | return mark_safe('onload="%s.%s_load()"' % (self.js_module, self.dom_id))
|
|---|
| 117 |
|
|---|
| 118 | @property
|
|---|
| 119 | def api_script(self):
|
|---|
| 120 | "Returns the <script> tag for the Google Maps API javascript."
|
|---|
| 121 | return mark_safe('<script src="%s%s" type="text/javascript"></script>' % (self.api_url, self.key))
|
|---|
| 122 |
|
|---|
| 123 | @property
|
|---|
| 124 | def js(self):
|
|---|
| 125 | "Returns only the generated Google Maps JavaScript (no <script> tags)."
|
|---|
| 126 | return self.render()
|
|---|
| 127 |
|
|---|
| 128 | @property
|
|---|
| 129 | def scripts(self):
|
|---|
| 130 | "Returns all <script></script> tags required with Google Maps JavaScript."
|
|---|
| 131 | return mark_safe('%s\n <script type="text/javascript">\n//<![CDATA[\n%s//]]>\n </script>' % (self.api_script, self.js))
|
|---|
| 132 |
|
|---|
| 133 | @property
|
|---|
| 134 | def style(self):
|
|---|
| 135 | "Returns additional CSS styling needed for Google Maps on IE."
|
|---|
| 136 | return mark_safe('<style type="text/css">%s</style>' % self.vml_css)
|
|---|
| 137 |
|
|---|
| 138 | @property
|
|---|
| 139 | def xhtml(self):
|
|---|
| 140 | "Returns XHTML information needed for IE VML overlays."
|
|---|
| 141 | return mark_safe('<html xmlns="http://www.w3.org/1999/xhtml" %s>' % self.xmlns)
|
|---|
| 142 |
|
|---|
| 143 | @property
|
|---|
| 144 | def icons(self):
|
|---|
| 145 | "Returns a sequence of GIcon objects in this map."
|
|---|
| 146 | return set([marker.icon for marker in self.markers if marker.icon])
|
|---|
| 147 |
|
|---|
| 148 | class GoogleMapSet(GoogleMap):
|
|---|
| 149 |
|
|---|
| 150 | def __init__(self, *args, **kwargs):
|
|---|
| 151 | """
|
|---|
| 152 | A class for generating sets of Google Maps that will be shown on the
|
|---|
| 153 | same page together.
|
|---|
| 154 |
|
|---|
| 155 | Example:
|
|---|
| 156 | gmapset = GoogleMapSet( GoogleMap( ... ), GoogleMap( ... ) )
|
|---|
| 157 | gmapset = GoogleMapSet( [ gmap1, gmap2] )
|
|---|
| 158 | """
|
|---|
| 159 | # The `google-multi.js` template is used instead of `google-single.js`
|
|---|
| 160 | # by default.
|
|---|
| 161 | template = kwargs.pop('template', 'gis/google/google-multi.js')
|
|---|
| 162 |
|
|---|
| 163 | # This is the template used to generate the GMap load JavaScript for
|
|---|
| 164 | # each map in the set.
|
|---|
| 165 | self.map_template = kwargs.pop('map_template', 'gis/google/google-map.js')
|
|---|
| 166 |
|
|---|
| 167 | # Running GoogleMap.__init__(), and resetting the template
|
|---|
| 168 | # value with default obtained above.
|
|---|
| 169 | super(GoogleMapSet, self).__init__(**kwargs)
|
|---|
| 170 | self.template = template
|
|---|
| 171 |
|
|---|
| 172 | # If a tuple/list passed in as first element of args, then assume
|
|---|
| 173 | if isinstance(args[0], (tuple, list)):
|
|---|
| 174 | self.maps = args[0]
|
|---|
| 175 | else:
|
|---|
| 176 | self.maps = args
|
|---|
| 177 |
|
|---|
| 178 | # Generating DOM ids for each of the maps in the set.
|
|---|
| 179 | self.dom_ids = ['map%d' % i for i in xrange(len(self.maps))]
|
|---|
| 180 |
|
|---|
| 181 | def load_map_js(self):
|
|---|
| 182 | """
|
|---|
| 183 | Returns JavaScript containing all of the loading routines for each
|
|---|
| 184 | map in this set.
|
|---|
| 185 | """
|
|---|
| 186 | result = []
|
|---|
| 187 | for dom_id, gmap in zip(self.dom_ids, self.maps):
|
|---|
| 188 | # Backup copies the GoogleMap DOM id and template attributes.
|
|---|
| 189 | # They are overridden on each GoogleMap instance in the set so
|
|---|
| 190 | # that only the loading JavaScript (and not the header variables)
|
|---|
| 191 | # is used with the generated DOM ids.
|
|---|
| 192 | tmp = (gmap.template, gmap.dom_id)
|
|---|
| 193 | gmap.template = self.map_template
|
|---|
| 194 | gmap.dom_id = dom_id
|
|---|
| 195 | result.append(gmap.js)
|
|---|
| 196 | # Restoring the backup values.
|
|---|
| 197 | gmap.template, gmap.dom_id = tmp
|
|---|
| 198 | return mark_safe(''.join(result))
|
|---|
| 199 |
|
|---|
| 200 | def render(self):
|
|---|
| 201 | """
|
|---|
| 202 | Generates the JavaScript for the collection of Google Maps in
|
|---|
| 203 | this set.
|
|---|
| 204 | """
|
|---|
| 205 | params = {'js_module' : self.js_module,
|
|---|
| 206 | 'dom_ids' : self.dom_ids,
|
|---|
| 207 | 'load_map_js' : self.load_map_js(),
|
|---|
| 208 | 'icons' : self.icons,
|
|---|
| 209 | }
|
|---|
| 210 | params.update(self.extra_context)
|
|---|
| 211 | return render_to_string(self.template, params)
|
|---|
| 212 |
|
|---|
| 213 | @property
|
|---|
| 214 | def onload(self):
|
|---|
| 215 | "Returns the `onload` HTML <body> attribute."
|
|---|
| 216 | # Overloaded to use the `load` function defined in the
|
|---|
| 217 | # `google-multi.js`, which calls the load routines for
|
|---|
| 218 | # each one of the individual maps in the set.
|
|---|
| 219 | return mark_safe('onload="%s.load()"' % self.js_module)
|
|---|
| 220 |
|
|---|
| 221 | @property
|
|---|
| 222 | def icons(self):
|
|---|
| 223 | "Returns a sequence of all icons in each map of the set."
|
|---|
| 224 | icons = []
|
|---|
| 225 | for map in self.maps: icons.extend(map.icons)
|
|---|
| 226 | return icons
|
|---|