1 | /*jslint white: false */ |
---|
2 | /*jslint forin: true */ |
---|
3 | /*global OpenLayers Drupal $ document jQuery window */ |
---|
4 | |
---|
5 | /** |
---|
6 | * @file |
---|
7 | * This file holds the main javascript API for OpenLayers. It is |
---|
8 | * responsable for loading and displaying the map. |
---|
9 | * |
---|
10 | * @ingroup openlayers |
---|
11 | */ |
---|
12 | |
---|
13 | /** |
---|
14 | * This is a workaround for a bug involving IE and VML support. |
---|
15 | * See the Drupal Book page describing this problem: |
---|
16 | * http://drupal.org/node/613002 |
---|
17 | */ |
---|
18 | |
---|
19 | document.namespaces; |
---|
20 | |
---|
21 | Drupal.settings.openlayers = {}; |
---|
22 | Drupal.settings.openlayers.maps = {}; |
---|
23 | |
---|
24 | /** |
---|
25 | * Minimal OpenLayers map bootstrap. |
---|
26 | * All additional operations occur in additional Drupal behaviors. |
---|
27 | */ |
---|
28 | Drupal.behaviors.openlayers = function(context) { |
---|
29 | if (typeof(Drupal.settings.openlayers) === 'object' && Drupal.settings.openlayers.maps && !$(context).data('openlayers')) { |
---|
30 | $('.openlayers-map:not(.openlayers-processed)').each(function() { |
---|
31 | $(this).addClass('openlayers-processed'); |
---|
32 | var map_id = $(this).attr('id'); |
---|
33 | |
---|
34 | // Use try..catch for error handling. |
---|
35 | try { |
---|
36 | if (Drupal.settings.openlayers.maps[map_id]) { |
---|
37 | // Set OpenLayers language based on document language, |
---|
38 | // rather than browser language |
---|
39 | OpenLayers.Lang.setCode($('html').attr('lang')); |
---|
40 | |
---|
41 | var map = Drupal.settings.openlayers.maps[map_id]; |
---|
42 | |
---|
43 | $(this) |
---|
44 | // @TODO: move this into markup in theme function, doing this dynamically is a waste. |
---|
45 | .css('width', map.width) |
---|
46 | .css('height', map.height); |
---|
47 | |
---|
48 | var options = {}; |
---|
49 | // This is necessary because the input JSON cannot contain objects |
---|
50 | options.projection = new OpenLayers.Projection('EPSG:' + map.projection); |
---|
51 | options.displayProjection = new OpenLayers.Projection('EPSG:' + map.displayProjection); |
---|
52 | |
---|
53 | // TODO: work around this scary code |
---|
54 | if (map.projection === '900913') { |
---|
55 | options.maxExtent = new OpenLayers.Bounds( |
---|
56 | -20037508.34, -20037508.34, 20037508.34, 20037508.34); |
---|
57 | options.units = 'm'; |
---|
58 | } |
---|
59 | if (map.projection === '4326') { |
---|
60 | options.maxExtent = new OpenLayers.Bounds(-180, -90, 180, 90); |
---|
61 | } |
---|
62 | |
---|
63 | options.maxResolution = 1.40625; |
---|
64 | options.controls = []; |
---|
65 | |
---|
66 | // Change image, CSS, and proxy paths if specified |
---|
67 | if (map.image_path) { |
---|
68 | OpenLayers.ImgPath = Drupal.openlayers.relatePath(map.image_path, |
---|
69 | Drupal.settings.basePath); |
---|
70 | } |
---|
71 | if (map.css_path) { |
---|
72 | options.theme = Drupal.openlayers.relatePath(map.css_path, |
---|
73 | Drupal.settings.basePath); |
---|
74 | } |
---|
75 | if (map.proxy_host) { |
---|
76 | OpenLayers.ProxyHost = Drupal.openlayers.relatePath(map.proxy_host, |
---|
77 | Drupal.settings.basePath); |
---|
78 | } |
---|
79 | |
---|
80 | // Initialize openlayers map |
---|
81 | var openlayers = new OpenLayers.Map(map.id, options); |
---|
82 | |
---|
83 | // Run the layer addition first |
---|
84 | Drupal.openlayers.addLayers(map, openlayers); |
---|
85 | |
---|
86 | // Attach data to map DOM object |
---|
87 | $(this).data('openlayers', {'map': map, 'openlayers': openlayers}); |
---|
88 | |
---|
89 | // Finally, attach behaviors |
---|
90 | Drupal.attachBehaviors(this); |
---|
91 | |
---|
92 | if ($.browser.msie) { |
---|
93 | Drupal.openlayers.redrawVectors(); |
---|
94 | } |
---|
95 | } |
---|
96 | } |
---|
97 | catch (e) { |
---|
98 | if (typeof console != 'undefined') { |
---|
99 | console.log(e); |
---|
100 | } |
---|
101 | else { |
---|
102 | $(this).text('Error during map rendering: ' + e); |
---|
103 | } |
---|
104 | } |
---|
105 | }); |
---|
106 | } |
---|
107 | }; |
---|
108 | |
---|
109 | /** |
---|
110 | * Collection of helper methods. |
---|
111 | */ |
---|
112 | Drupal.openlayers = { |
---|
113 | |
---|
114 | /** |
---|
115 | * Determine path based on format. |
---|
116 | */ |
---|
117 | 'relatePath': function(path, basePath) { |
---|
118 | // Check for a full URL or an absolute path. |
---|
119 | if (path.indexOf('://') >= 0 || path.indexOf('/') == 0) { |
---|
120 | return path; |
---|
121 | } |
---|
122 | else { |
---|
123 | return basePath + path; |
---|
124 | } |
---|
125 | }, |
---|
126 | |
---|
127 | /** |
---|
128 | * Redraw Vectors. |
---|
129 | * This is necessary because various version of IE cannot draw vectors on |
---|
130 | * $(document).ready() |
---|
131 | */ |
---|
132 | 'redrawVectors': function() { |
---|
133 | $(window).load( |
---|
134 | function() { |
---|
135 | var map; |
---|
136 | for (map in Drupal.settings.openlayers.maps) { |
---|
137 | $.each($('#' + map).data('openlayers').openlayers.getLayersByClass('OpenLayers.Layer.Vector'), |
---|
138 | function(i, layer) { |
---|
139 | layer.redraw(); |
---|
140 | } |
---|
141 | ); |
---|
142 | } |
---|
143 | } |
---|
144 | ); |
---|
145 | }, |
---|
146 | |
---|
147 | /** |
---|
148 | * Add layers to the map |
---|
149 | * |
---|
150 | * @param map Drupal settings object for the map. |
---|
151 | * @param openlayers OpenLayers Map Object. |
---|
152 | */ |
---|
153 | 'addLayers': function(map, openlayers) { |
---|
154 | |
---|
155 | var sorted = []; |
---|
156 | for (var name in map.layers) { |
---|
157 | sorted.push({'name': name, 'weight': map.layers[name].weight }); |
---|
158 | } |
---|
159 | sorted.sort(function(a, b) { |
---|
160 | var x = a.weight; var y = b.weight; |
---|
161 | return ((x < y) ? -1 : ((x > y) ? 1 : 0)); |
---|
162 | }); |
---|
163 | |
---|
164 | for (var i = 0; i < sorted.length; ++i) { |
---|
165 | var layer; |
---|
166 | var name = sorted[i].name; |
---|
167 | var options = map.layers[name]; |
---|
168 | |
---|
169 | // Add reference to our layer ID |
---|
170 | options.drupalID = name; |
---|
171 | // Ensure that the layer handler is available |
---|
172 | if (options.layer_handler !== undefined && |
---|
173 | Drupal.openlayers.layer[options.layer_handler] !== undefined) { |
---|
174 | var layer = Drupal.openlayers.layer[options.layer_handler](map.layers[name].title, map, options); |
---|
175 | |
---|
176 | layer.visibility = !!(!map.layer_activated || map.layer_activated[name]); |
---|
177 | |
---|
178 | if (layer.isBaseLayer == false) { |
---|
179 | layer.displayInLayerSwitcher = !!(!map.layer_switcher || map.layer_switcher[name]); |
---|
180 | } else { |
---|
181 | layer.displayInLayerSwitcher = true; |
---|
182 | } |
---|
183 | |
---|
184 | if (map.center.wrapdateline === '1') { |
---|
185 | // TODO: move into layer specific settings |
---|
186 | layer.wrapDateLine = true; |
---|
187 | } |
---|
188 | |
---|
189 | openlayers.addLayer(layer); |
---|
190 | } |
---|
191 | } |
---|
192 | |
---|
193 | openlayers.setBaseLayer(openlayers.getLayersBy('drupalID', map.default_layer)[0]); |
---|
194 | |
---|
195 | // Zoom & center |
---|
196 | if (map.center.initial) { |
---|
197 | var center = OpenLayers.LonLat.fromString(map.center.initial.centerpoint).transform( |
---|
198 | new OpenLayers.Projection('EPSG:4326'), |
---|
199 | new OpenLayers.Projection('EPSG:' + map.projection)); |
---|
200 | var zoom = parseInt(map.center.initial.zoom, 10); |
---|
201 | openlayers.setCenter(center, zoom, false, false); |
---|
202 | } |
---|
203 | |
---|
204 | // Set the restricted extent if wanted. |
---|
205 | // Prevents the map from being panned outside of a specfic bounding box. |
---|
206 | if (typeof map.center.restrict !== 'undefined' && map.center.restrict.restrictextent) { |
---|
207 | openlayers.restrictedExtent = OpenLayers.Bounds.fromString( |
---|
208 | map.center.restrict.restrictedExtent); |
---|
209 | } |
---|
210 | }, |
---|
211 | /** |
---|
212 | * Abstraction of OpenLayer's feature adding syntax to work with Drupal output. |
---|
213 | * Ideally this should be rolled into the PHP code, because we don't want to manually |
---|
214 | * parse WKT |
---|
215 | */ |
---|
216 | 'addFeatures': function(map, layer, features) { |
---|
217 | var newFeatures = []; |
---|
218 | |
---|
219 | // Go through features |
---|
220 | for (var key in features) { |
---|
221 | var feature = features[key]; |
---|
222 | var newFeatureObject = this.objectFromFeature(feature); |
---|
223 | |
---|
224 | // If we have successfully extracted geometry add additional |
---|
225 | // properties and queue it for addition to the layer |
---|
226 | if (newFeatureObject) { |
---|
227 | var newFeatureSet = []; |
---|
228 | |
---|
229 | // Check to see if it is a new feature, or an array of new features. |
---|
230 | if ('geometry' in newFeatureObject) { |
---|
231 | newFeatureSet[0] = newFeatureObject; |
---|
232 | } |
---|
233 | else { |
---|
234 | newFeatureSet = newFeatureObject; |
---|
235 | } |
---|
236 | |
---|
237 | if (newFeatureSet.length == 1 && newFeatureSet[0] == undefined) { |
---|
238 | newFeatureSet = []; |
---|
239 | } |
---|
240 | |
---|
241 | // Go through new features |
---|
242 | for (var i=0; i<newFeatureSet.length; i++) { |
---|
243 | var newFeature = newFeatureSet[i]; |
---|
244 | |
---|
245 | // Transform the geometry if the 'projection' property is different from the map projection |
---|
246 | if (feature.projection) { |
---|
247 | if (feature.projection !== map.projection) { |
---|
248 | var featureProjection = new OpenLayers.Projection('EPSG:' + feature.projection); |
---|
249 | var mapProjection = new OpenLayers.Projection('EPSG:' + map.projection); |
---|
250 | newFeature.geometry.transform(featureProjection, mapProjection); |
---|
251 | } |
---|
252 | } |
---|
253 | |
---|
254 | // Add attribute data |
---|
255 | if (feature.attributes) { |
---|
256 | // Attributes belong to features, not single component geometries |
---|
257 | // of them. But we're creating a geometry for each component for |
---|
258 | // better performance and clustering support. Let's call these |
---|
259 | // "pseudofeatures". |
---|
260 | // |
---|
261 | // In order to identify the real feature each geometry belongs to |
---|
262 | // we then add a 'drupalFID' parameter to the "pseudofeature". |
---|
263 | // NOTE: 'drupalFID' is only unique within a single layer. |
---|
264 | newFeature.attributes = feature.attributes; |
---|
265 | // See http://drupal.org/node/949434 before wiping out |
---|
266 | //newFeature.data = feature.attributes; |
---|
267 | newFeature.drupalFID = key; |
---|
268 | } |
---|
269 | |
---|
270 | // Add style information |
---|
271 | if (feature.style) { |
---|
272 | newFeature.style = jQuery.extend({}, OpenLayers.Feature.Vector.style['default'], feature.style); |
---|
273 | } |
---|
274 | |
---|
275 | // Push new features |
---|
276 | newFeatures.push(newFeature); |
---|
277 | } |
---|
278 | } |
---|
279 | } |
---|
280 | |
---|
281 | // Add new features if there are any |
---|
282 | if (newFeatures.length !== 0) { |
---|
283 | layer.addFeatures(newFeatures); |
---|
284 | } |
---|
285 | }, |
---|
286 | /** |
---|
287 | * Build an OpenLayers style from a drupal style object |
---|
288 | * |
---|
289 | * @param map Drupal settings object for the map (const). |
---|
290 | * @param style_in Drupal settings object for the style (const). |
---|
291 | */ |
---|
292 | 'buildStyle': function(map, style_in) { |
---|
293 | // Build context object and callback values (if needed) |
---|
294 | var style_out = {}; |
---|
295 | var newContext = {}; |
---|
296 | for (var propname in style_in) { |
---|
297 | if (typeof style_in[propname] == 'object') { |
---|
298 | var plugin_spec = style_in[propname]; |
---|
299 | var plugin_name = plugin_spec['plugin']; |
---|
300 | var plugin_class = Drupal.openlayers.style_plugin[plugin_name]; |
---|
301 | if (typeof plugin_class !== 'function') { |
---|
302 | throw 'Style plugin ' + plugin_name + |
---|
303 | ' did not install a constructor in Drupal.openlayers.style_plugin["' + plugin_name + '"]'; |
---|
304 | } |
---|
305 | |
---|
306 | var plugin_options = plugin_spec['conf']; |
---|
307 | var plugin_method_name = plugin_spec['method']; |
---|
308 | if (typeof plugin_method_name === 'undefined') { |
---|
309 | throw "Name of method handler for property '" + propname + |
---|
310 | "' of style plugin '" + plugin_name + "' is undefined"; |
---|
311 | } |
---|
312 | |
---|
313 | var plugin_context = new plugin_class(plugin_options); |
---|
314 | |
---|
315 | var plugin_method = plugin_context[plugin_method_name]; |
---|
316 | if (typeof plugin_method !== 'function') { |
---|
317 | throw "Style plugin '" + plugin_name + "' advertised method '" + |
---|
318 | plugin_method_name + "' as an handler for property " + propname + |
---|
319 | ' but that method is not found in instance of plugin class'; |
---|
320 | } |
---|
321 | |
---|
322 | var new_method_name = plugin_name + '_' + |
---|
323 | propname + '_' + |
---|
324 | plugin_method_name; |
---|
325 | newContext[new_method_name] = |
---|
326 | OpenLayers.Function.bind(plugin_method, plugin_context); |
---|
327 | |
---|
328 | style_out[propname] = '${' + new_method_name + '}'; |
---|
329 | } else { |
---|
330 | style_out[propname] = style_in[propname]; |
---|
331 | } |
---|
332 | } |
---|
333 | |
---|
334 | // Instantiate an OL style object. |
---|
335 | var olStyle = new OpenLayers.Style(style_out, { context: newContext }); |
---|
336 | return olStyle; |
---|
337 | }, |
---|
338 | 'getStyleMap': function(map, layername) { |
---|
339 | if (map.styles) { |
---|
340 | |
---|
341 | var stylesAdded = {}; |
---|
342 | var roles = ['default', 'delete', 'select', 'temporary']; |
---|
343 | // Grab and map base styles. |
---|
344 | for (var i = 0; i < roles.length; ++i) { |
---|
345 | role = roles[i]; |
---|
346 | if (map.styles[role]) { |
---|
347 | var style = map.styles[role]; |
---|
348 | stylesAdded[role] = this.buildStyle(map, style); |
---|
349 | } |
---|
350 | } |
---|
351 | // Override with layer-specific styles. |
---|
352 | if (map.layer_styles !== undefined && map.layer_styles[layername]) { |
---|
353 | var layer_styles = map.layer_styles[layername]; |
---|
354 | for (var i = 0; i < roles.length; ++i) { |
---|
355 | role = roles[i]; |
---|
356 | if (layer_styles[role]) { |
---|
357 | var style_name = layer_styles[role]; |
---|
358 | var style = map.styles[style_name]; // TODO: skip if undef |
---|
359 | stylesAdded[role] = this.buildStyle(map, style); |
---|
360 | } |
---|
361 | } |
---|
362 | } |
---|
363 | |
---|
364 | return new OpenLayers.StyleMap(stylesAdded); |
---|
365 | } |
---|
366 | // Default styles |
---|
367 | return new OpenLayers.StyleMap({ |
---|
368 | 'default': new OpenLayers.Style({ |
---|
369 | pointRadius: 5, |
---|
370 | fillColor: '#ffcc66', |
---|
371 | strokeColor: '#ff9933', |
---|
372 | strokeWidth: 4, |
---|
373 | fillOpacity: 0.5 |
---|
374 | }), |
---|
375 | 'select': new OpenLayers.Style({ |
---|
376 | fillColor: '#66ccff', |
---|
377 | strokeColor: '#3399ff' |
---|
378 | }) |
---|
379 | }); |
---|
380 | }, |
---|
381 | 'objectFromFeature': function(feature) { |
---|
382 | var wktFormat = new OpenLayers.Format.WKT(); |
---|
383 | // Extract geometry either from wkt property or lon/lat properties |
---|
384 | if (feature.wkt) { |
---|
385 | return wktFormat.read(feature.wkt); |
---|
386 | } |
---|
387 | else if (feature.lon) { |
---|
388 | return wktFormat.read('POINT(' + feature.lon + ' ' + feature.lat + ')'); |
---|
389 | } |
---|
390 | } |
---|
391 | }; |
---|
392 | |
---|
393 | Drupal.openlayers.layer = {}; |
---|
394 | Drupal.openlayers.style_plugin = {}; |
---|