|
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-map.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-single.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 = set() |
|
225 for map in self.maps: icons |= map.icons |
|
226 return icons |