PageSourceSearch

https://watchthemed.net/media/js/ushahidi.js?m=1584278827&

js watchthemed.net collected 2026-10-02 04:32:45 UTC 35,365 bytes, 1,193 lines download raw bytes

1/**
2 * Copyright (c) 2008-2012 by Ushahidi Dev Team
3 * Published under the LGPL license. See License.txt for the
4 * full text of the license
5 *
6 * @requires media/js/OpenLayers.js
7 */
8(function(){
9	
10	/**
11	 * Namespace: Ushahidi
12	 * The Ushahidi object provides a namespace for all things Ushahidi
13	 */
14	 window.Ushahidi = {
15
16	 	/**
17	 	 * APIProperty: proj_4326
18	 	 * Projection for representing latitude and longitude coordinates
19	 	 */
20	 	proj_4326: new OpenLayers.Projection('EPSG:4326'),
21
22	 	/**
23	 	 * APIProperty: proj_900913
24	 	 * An unofficial code but maintained nonetheless - for describing
25	 	 * coordinates in meters in x/y
26	 	 */
27	 	proj_900913: new OpenLayers.Projection('EPSG:900913'),
28
29	 	/**
30	 	 * APIProperty: markerOpacity
31	 	 * Default opacity for the markers
32	 	 */
33	 	markerOpacity: 0.9,
34
35	 	/**
36	 	 * APIProperty: markerRadius
37	 	 * Default radius for the markers
38	 	 */
39	 	markerRadius: 5,
40
41	 	/**
42	 	 * APIProperty: markerStrokeWidth
43	 	 * Stroke width for the markers
44	 	 */
45	 	markerStrokeWidth: 2,
46
47	 	/**
48	 	 * APIProperty: markerStrokeOpacity
49	 	 */
50	 	markerStrokeOpacity: 0.9,
51
52		// Layer types
53		GEOJSON: "GeoJSON",
54
55		KML: "KML",
56
57		DEFAULT: "default",
58
59		/**
60		 * APIProperty: baseURL
61		 * Base URL for the application
62		 */
63		baseURL: '',
64
65	 	/**
66	 	 * APIProperty: geoJSONStyle
67	 	 * Default styling for GeoJSON data
68	 	 * the map
69	 	 */
70	 	 GeoJSONStyle: function() {
71	 	 	var style = new OpenLayers.Style({
72	 	 		'externalGraphic': "${icon}",
73	 	 		'graphicTitle': "${cluster_count}",
74				pointRadius: "${radius}",
75				fillColor: "${color}",
76				fillOpacity: "${opacity}",
77				strokeColor: "${strokeColor}",
78				strokeWidth: "${strokeWidth}",
79				strokeOpacity: "${strokeOpacity}",
80				label:"${clusterCount}",
81				fontWeight: "${fontweight}",
82				fontColor: "#ffffff",
83				fontSize: "${fontsize}",
84				title: "${title}"    
85			},
86			{
87				context: {
88					title: function(feature) {
89            			if (feature.attributes.count >= 2) {
90              				return feature.attributes.count + " reports";
91            			} else {
92              				return feature.attributes.title;
93            			}
94          			},
95					count: function(feature) {
96						if (feature.attributes.count < 2) {
97							return 2 * Ushahidi.markerRadius;
98						}  else if (feature.attributes.count == 2) {
99							return (Math.min(feature.attributes.count, 7) + 1) *
100							(Ushahidi.markerRadius * 0.8);
101						} else {
102							return (Math.min(feature.attributes.count, 7) + 1) *
103							(Ushahidi.markerRadius * 0.6);
104						}
105					},
106					fontsize: function(feature) {
107						feature_icon = feature.attributes.icon;
108						if (feature_icon !== "") {
109							return "9px";
110						}
111						else {
112							feature_count = feature.attributes.count;
113							if (feature_count > 1000) {
114								return "20px";
115							} else if (feature_count > 500) {
116								return "18px";
117							} else if (feature_count > 100) {
118								return "14px";
119							} else if (feature_count > 10) {
120								return "12px";
121							} else if (feature_count >= 2) {
122								return "10px";
123							} else {
124								return "";
125							}
126						}
127					},
128					fontweight: function(feature) {
129						feature_icon = feature.attributes.icon;
130						if (feature_icon!=="") {
131							return "normal";
132						} else {
133							return "bold";
134						}
135					},
136					radius: function(feature) {
137						if (typeof(feature.attributes.radius) != 'undefined' && 
138							feature.attributes.radius != '') {
139							return feature.attributes.radius;
140						} else {
141							feature_count = feature.attributes.count;
142							if (feature_count > 10000)
143							{
144								return Ushahidi.markerRadius * 17;
145							}
146							else if (feature_count > 5000)
147							{
148								return Ushahidi.markerRadius * 10;
149							}
150							else if (feature_count > 1000)
151							{
152								return Ushahidi.markerRadius * 8;
153							}
154							else if (feature_count > 500)
155							{
156								return Ushahidi.markerRadius * 7;
157							}
158							else if (feature_count > 100)
159							{
160								return Ushahidi.markerRadius * 6;
161							}
162							else if (feature_count > 10)
163							{
164								return Ushahidi.markerRadius * 5;
165							}
166							else if (feature_count >= 2)
167							{
168								return Ushahidi.markerRadius * 3;
169							}
170							else
171							{
172								return Ushahidi.markerRadius * 2;
173							}
174						}
175					},
176					strokeWidth: function(feature) {
177						if ( typeof(feature.attributes.strokewidth) != 'undefined' && 
178							feature.attributes.strokewidth != '')
179						{
180							return feature.attributes.strokewidth;
181						}
182						else
183						{
184							feature_count = feature.attributes.count;
185							if (feature_count > 10000)
186							{
187								return 45;
188							}
189							else if (feature_count > 5000)
190							{
191								return 30;
192							}
193							else if (feature_count > 1000)
194							{
195								return 22;
196							}
197							else if (feature_count > 100)
198							{
199								return 15;
200							}
201							else if (feature_count > 10)
202							{
203								return 10;
204							}
205							else if (feature_count >= 2)
206							{
207								return 5;
208							}
209							else
210							{
211								return 1;
212							}
213						}
214					},
215					color: function(feature) {
216						return "#" + feature.attributes.color;
217					},
218					strokeColor: function(feature) {
219						if ( typeof(feature.attributes.strokecolor) != 'undefined' && 
220							feature.attributes.strokecolor != '')
221						{
222							return "#"+feature.attributes.strokecolor;
223						}
224						else
225						{
226							return "#"+feature.attributes.color;
227						}
228					},
229					icon: function(feature) {
230						feature_icon = feature.attributes.icon;
231						
232						return (feature_icon !== "") ? feature_icon : "";
233					},
234					clusterCount: function(feature) {
235						if (feature.attributes.count > 1)
236						{
237							if ($.browser.msie && $.browser.version=="6.0")
238							{ // IE6 Bug with Labels
239								return "";
240							}
241
242							return feature.attributes.count;
243						}
244						else
245						{
246							return "";
247						}
248					},
249					opacity: function(feature) {
250						feature_icon = feature.attributes.icon;
251						if (typeof(feature.attributes.opacity) != 'undefined' && 
252							feature.attributes.opacity != '')
253						{
254							return feature.attributes.opacity
255						}
256						else if (feature_icon!=="")
257						{
258							return "1";
259						}
260						else
261						{
262							return Ushahidi.markerOpacity;
263						}
264					},
265					strokeOpacity: function(feature) {
266						if(typeof(feature.attributes.strokeopacity) != 'undefined' && 
267							feature.attributes.strokeopacity != '')
268						{
269							return feature.attributes.strokeopacity;
270						}
271						else
272						{
273							return Ushahidi.markerStrokeOpacity;
274						}
275					},
276					labelalign: function(feature) {
277						return "c";
278					}
279				}
280			});
281
282			return style;
283		}
284
285	 };
286
287	 /**
288	  * Constructor: Ushahidi.Map
289	  * Base class used to construct all OpenLayers.Map objects
290	  *
291	  * Parameters:
292	  * div - {DOMElement|String} The element or id of an element in the page
293	  *       that will contain the map
294	  * config = {Object} Optional object with the configuration to be used for
295	  *          generating the map
296	  *
297	  * Valid config options are:
298	  * zoom - Initial zoom level of the map
299	  * center - {Object} The default initial center of the map
300	  * redrawOnZoom - {Boolean} Whether to redraw the layers when the map zoom changes.
301	  *                Default is false
302	  * detectMapZoom - {Boolean} Whether to detect change in the zoom level. If the
303	  *                 redrawOnZoom property is true, this option is ignored
304	  * mapControls - {Array(OpenLayers.Control)} The list of controls to add to the map
305	  * reportFilters - Initial report filters to be passed in URL
306	  */
307	 Ushahidi.Map = function(div, config) {
308	 	// Internal registry for the marker layers
309	 	this._registry = [];
310	 	
311	 	// Internal list of layers to keep at the top
312	 	this._onTop = [];
313
314	 	// Markers are not yet loaded on the map
315	 	this._isLoaded = 0;
316
317	 	// Timeouts for layers about to be loaded, keyed by layer name
318	 	this._addLayerTimeouts = {};
319
320	 	// List of supported events
321	 	this._EVENTS = [
322	 		// When the report filters change
323	 	    "filterschanged",
324
325	 	    // Map's zoom level changes
326	 	    "zoomchanged",
327
328	 	    // When the size of the map's viewport changes
329	 	    "resize",
330
331	 	    // When a layer is deleted
332	 	    "deletelayer",
333
334	 	    // Maker position changed
335	 	    "markerpositionchanged",
336
337	 	    // Base layer is changed
338	 	    "baselayerchanged",
339
340	 	    // Map center changed
341	 	    "mapcenterchanged"
342	 	];
343
344	 	// Register for the callbacks to be invoked when the report filters
345	 	// are updated. The updated paramters will passed to the callback
346	 	// as a parameter
347	 	this._callbacks = {};
348
349	 	// Tracks the current marker position
350	 	this._currentMarkerPosition = {};
351
352	 	// Check for the mapDiv
353	 	if (div == undefined) {
354	 		throw "The element or id of an element that will contain the map must be specified";
355	 	}
356
357	 	// Sanitize input parameters
358	 	var config = config || {};
359	 	if (config.zoom == undefined) config.zoom = 8;
360
361	 	// Internally track the current zoom
362	 	this.currentZoom = config.zoom;
363
364	 	// The set of filters/parameters to pass to the URL that fetches
365	 	// overlay data - not applicable to KMLs
366	 	this._reportFilters = config.reportFilters || {};
367
368	 	// Update the report filters with the zoom level
369	 	this._reportFilters.z = config.zoom;
370
371	 	// Latitude and longitude
372	 	if (config.center == undefined) {
373	 		// Set the default latitude and longitude to Nairobi - no place like ground Ø
374	 		config.center = {latitude: -1.286449478924, longitude: 36.822838050049};
375		}
376
377		// Map options
378		var mapOptions = {
379			units: "dd",
380			numZoomLevels: 18,
381			theme: false,
382			controls: [],
383			projection: Ushahidi.proj_900913,
384			'displayProjection': Ushahidi.proj_4326,
385			maxExtent: new OpenLayers.Bounds(-20037508.34, -20037508.34, 
386			                                 20037508.34, 20037508.34),
387			maxResolution: 156543.0339,
388			// Shrink the popup padding so popups don't land under zoom control
389			paddingForPopups: new OpenLayers.Bounds(40,15,15,15),
390			eventListeners: { 
391				// Trigger keepOnTop fn whenever new layers are added
392				addlayer: this.keepOnTop,
393				scope: this
394			}
395		};
396
397		// Are the layers to be redrawn on zoom change
398		if (config.redrawOnZoom !== undefined && config.redrawOnZoom) {
399			mapOptions.eventListeners.zoomend = this.refresh;
400		}
401
402		// Zoom detection is enabled and redrawOnZoom has not been specified or is false
403		if ((config.redrawOnZoom == undefined || !config.redrawOnZoom) && config.detectMapZoom) {
404			this.register("zoomchanged", this.updateZoom, this);
405			mapOptions.eventListeners.zoomend = this.triggerZoomChanged;
406		}
407
408		// Check for the controls to add to the map
409		if (config.mapControls == undefined) {
410			// Default map controls
411			mapOptions.controls = [
412				new OpenLayers.Control.Navigation({ dragPanOptions: { enableKinetic: true } }),
413				new OpenLayers.Control.Zoom(),
414				new OpenLayers.Control.Attribution(),
415				new OpenLayers.Control.MousePosition({
416					formatOutput: Ushahidi.convertLongLat
417				}),
418				new OpenLayers.Control.LayerSwitcher()
419			];
420		} else {
421			mapOptions.controls = config.mapControls;
422		}
423
424		// Create the map
425		this._olMap = new OpenLayers.Map(div, mapOptions);
426		if (config.baseLayers != undefined && config.baseLayers.length > 0) {
427			this._olMap.addLayers(config.baseLayers);
428		}
429
430		var point = new OpenLayers.LonLat(config.center.longitude, config.center.latitude);
431		point.transform(Ushahidi.proj_4326, Ushahidi.proj_900913);
432
433		this._currentCenter = point;
434
435		// Set the map center
436		this._olMap.setCenter(point, config.zoom);
437
438		// Display the map projection
439		if (config.displayProjection != undefined && config.displayProjection) {
440			document.getElementById('mapProjection').innerHTML = this._olMap.projection;
441		}
442
443		// Register this instance in the namespace
444		// NOTE: Only one map is allowed in the namespace
445		Ushahidi._currentMap = this;
446
447		// Register events
448		this.register("resize", this.resize, this);
449		this.register("deletelayer", this.deleteLayer, this);
450		this.register("baselayerchanged", this.updateBaseLayer, this);
451		this.register("mapcenterchanged", this.updateMapCenter, this);
452		
453		// Pre-load the background image for the popup so that it is 
454		// fetched from the cache when when popup is displayed
455		var popupBgImage = new Image();
456		popupBgImage.src = OpenLayers.Util.getImagesLocation() + 'cloud-popup-relative.png';
457
458		return this;
459	};
460
461	/**
462	 * APIMethod addLayers
463	 *
464	 * Parameters:
465	 * layerType - {String} Type of marker to be added and could be one of the following 
466	 *             (Ushahidi.REPORTS, Ushahidi.KML, Ushahidi.SHARES. Ushahidi.DEFAULT)
467	 * options   - {Object} Optional object key/value pairs of the markers to be added to the map
468	 *
469	 * Valid options are:
470	 * name - {String} Name of the Layer being added
471	 * url  - {String} Fetch URL for the layer data
472	 * styleMap - {OpenLayers.StyleMap} Styling for the layer
473	 * detectMapClicks - {Boolean} - When true, registers a callback function to detect
474	 *               click events on the map. This option is only used with the default
475	 *               layer (Ushahidi.DEFAULT). The default value is true
476	 * transform - {Boolean} When true, transforms the featur geometry to spherical mercator
477	 *             The default value is false
478	 * features - {Array(OpenLayers.Feature.Vector)} Features to add to the layer 
479	 *            When the features ar specified, the protocol property is omitted from the
480	 *            options passed to the layer constructor. The features property is used instead
481	 *
482	 * save -      {bool} Whether to save the layer in the internal registry of Ushahidi.Map This
483	 *             parameter should be set to true, if the layer being added is new so as to ensure
484	 *             that it is redrawn when the map is zoomed in/out or the report filters are updated   
485	 * keepOnTop - {bool} Whether to keep this layer above others.
486	 */
487	Ushahidi.Map.prototype.addLayer = function(layerType, options, save, keepOnTop) {
488		// Default markers layer
489		if (layerType == Ushahidi.DEFAULT) {
490			this.deleteLayer("default");
491			
492			var markers = null;
493			
494			if (options == undefined) {
495				options = {};
496			}
497			
498			// Check for the style map
499			if (options.styleMap != undefined) {
500
501				// Create vector layer
502				markers = new OpenLayers.Layer.Vector("default", {
503					styleMap: styleMap
504				});
505
506				// Add features to the vector layer
507				makers.addFeatures(new OpenLayers.Feature.Vector(this._olMap.getCenter()));
508
509			} else {
510				markers = new OpenLayers.Layer.Markers("default");
511				markers.addMarker(new OpenLayers.Marker(this._olMap.getCenter()));
512			}
513
514			// Add the layer to the map
515			this._olMap.addLayer(markers);
516
517			// Is map-click detection enabled?
518			if (options.detectMapClicks == undefined || options.detectMapClicks) {
519				var context = this;
520				context._olMap.events.register("click", context._olMap, function(e){
521					var point = context._olMap.getLonLatFromViewPortPx(e.xy);
522					markers.clearMarkers();
523					markers.addMarker(new OpenLayers.Marker(point));
524
525					point.transform(Ushahidi.proj_900913, Ushahidi.proj_4326);
526					
527					var coords = {latitude: point.lat, longitude: point.lon};
528					context.trigger("markerpositionchanged", coords);
529
530					// Update the current map center
531					var newCenter = new OpenLayers.LonLat(coords.longitude, coords.latitude);
532					newCenter.transform(Ushahidi.proj_4326, Ushahidi.proj_900913);
533					context._currentCenter = newCenter;
534				});
535			}
536
537			this._isLoaded = 1;
538
539			return this;
540		}
541		
542		// Setup default protocol format
543		var protocolFormat = new OpenLayers.Format.GeoJSON();
544		// Switch protocol format if layer is KML
545		if (layerType == Ushahidi.KML) {
546			protocolFormat = new OpenLayers.Format.KML({
547				extractStyles: true,
548				extractAttributes: true,
549				maxDepth: 5
550			});
551		}
552
553		// No options defined - where layerType !== Ushahidi.DEFAULT
554		if (options == undefined) {
555			throw "Invalid layer options";
556		}
557
558		// Save the layer data in the internal registry
559		if (save !== undefined && save) {
560			this._registry.push({layerType: layerType, options: options});
561		}
562
563		// Save the layer name to keep on top
564		if (keepOnTop !== undefined && keepOnTop) {
565			this._onTop.push(options.name);
566		}
567
568		// Transform feature geometry to Spherical Mercator
569		preFeatureInsert = function(feature) {
570			if (feature.geometry) {
571				var point = new OpenLayers.Geometry.Point(feature.geometry.x, feature.geometry.y);
572				OpenLayers.Projection.transform(point, Ushahidi.proj_4326, Ushahidi.proj_900913);
573				feature.geometry.x = point.x;
574				feature.geometry.y = point.y;
575			}
576		}
577
578		// Layer options
579		var layerOptions = {
580			projection: Ushahidi.proj_4326,
581			formatOptions: {
582				extractStyles: true,
583				extractAttributes: true
584			}
585		};
586
587		if (options.transform) {
588			// Transform the feature geometry to spherical mercator
589			layerOptions.preFeatureInsert = preFeatureInsert;
590		}
591
592		// Build out the fetch url
593		var fetchURL = Ushahidi.baseURL + options.url;
594
595		// Apply the report filters to the layer fetch URL except where
596		// the layer type is KML
597		if (layerType !== Ushahidi.KML) {
598			var params = [];
599			for (var _key in this._reportFilters) {
600				params.push(_key + '=' + this._reportFilters[_key]);
601			}
602
603			if (fetchURL.indexOf("?") !== -1) {
604				var index = fetchURL.indexOf("?");
605				var args = fetchURL.substr(index+1, fetchURL.length).split("&");
606				fetchURL = fetchURL.substring(0, index);
607
608				for (var i=0; i<args.length; i++) {
609					params.push(args[i]);
610				}
611			}
612
613			// Update the fetch URL with parameters
614			fetchURL += (params.length > 0) ? '?' + params.join('&') : '';
615
616			// Get the styling to use
617			var styleMap = null;
618			if (options.styleMap !== undefined) {
619				styleMap = options.styleMap;
620			} else {
621				var style = Ushahidi.GeoJSONStyle();
622				styleMap = new OpenLayers.StyleMap({
623					"default": style,
624					"select": style
625				});
626			}
627
628			// Update the layer options with the style map
629			layerOptions.styleMap = styleMap;
630		}
631
632		// If no features were passed in layer options,
633		// set up protocal and strategy to grab GeoJSON
634		if (options.features === undefined) {
635			layerOptions.strategies = [new OpenLayers.Strategy.Fixed({preload: true})];
636
637			// Set the protocol
638			layerOptions.protocol = new OpenLayers.Protocol.HTTP({
639				url: fetchURL,
640				format: protocolFormat
641			});
642		}
643
644		// Create the layer
645		var layer = new OpenLayers.Layer.Vector(options.name, layerOptions);
646		
647		// Store context for callbacks
648		var context = this;
649		
650		// Hide the layer until its loaded
651		// only delete the old layer on loadend
652		layer.display(false);
653		// Hack to start with opacity 0 then fade in
654		layer.div.style['opacity'] = 0;
655		var oldLayer = this._olMap.getLayersByName(options.name);
656		var displayLayer = function () {
657			// Delete the old layers
658			this.deleteLayer(oldLayer);
659			// Display the new layer, fading in if we've got CSS3
660			layer.display(true);
661			layer.div.className += " olVectorLayerDiv";
662			layer.div.style['opacity'] = 1;
663
664			// Update / Create SelectFeature Control
665			if (typeof this._selectControl == "object")
666			{
667				// Update SelectFeature control with all vector layers
668				this._selectControl.setLayer(this._olMap.getLayersByClass("OpenLayers.Layer.Vector"));
669			}
670			else
671			{
672				// Select Feature control
673				this._selectControl = new OpenLayers.Control.SelectFeature(
674					this._olMap.getLayersByClass("OpenLayers.Layer.Vector"),
675					{ clickout: true, toggle: false, multiple: false, hover: false }
676				);
677				this._olMap.addControl(this._selectControl);
678				this._selectControl.activate();
679			}
680			
681			// Bind popup events for select/unselect
682			layer.events.on({
683				"featureselected": this.onFeatureSelect,
684				"featureunselected": this.onFeatureUnselect,
685				scope: this
686			});
687		}
688		// Register display layer fn to run on load end
689		layer.events.register('loadend', this, displayLayer);
690		
691		// If features were passed in layer options
692		// Add features to layer and register display layer on layer added
693		if (options.features !== undefined && options.features.length > 0) {
694			layer.addFeatures(options.features);
695			layer.events.register('added', this, displayLayer);
696		}
697
698		// Add the layer to the map
699		// We do this after a delay in case someone zooms multiple times
700		clearTimeout(this._addLayerTimeouts[options.name]);
701		this._addLayerTimeouts[options.name] = setTimeout(function(){ context._olMap.addLayer(layer); }, 100);
702
703		this._isLoaded = 1;
704
705		return this;
706	}
707
708	/**
709	 * APIMethod: updateReportFilters
710	 * This method updates the set of parameters used to filter out
711	 * the map content and then redraws the map
712	 *
713	 * Parameters:
714	 * filters - {Object} Set of filter to apply to the map.
715	 * Allowable filters are:
716	 *     z - {Number} The zoom level
717	 *     c - {Number} The category id
718	 *     m - {Number} The media type (0 - All, 1 - Pictures, 2 - Video 4 - News)
719	 *     s - {Number} Start date - Earliest date by which to filter the reports
720	 *     e - {Number} End date - Latest date by which to filter the reports
721	 */
722	Ushahidi.Map.prototype.updateReportFilters = function(filters) {
723		if (filters == undefined) {
724			throw "Missing filters";
725		}
726
727		var hasChanged = false;
728		var context = this;
729
730		// Overwrite the current set of filters with the new values
731		$.each(filters, function(filter, value) {
732			var currentValue = context._reportFilters[filter];
733			if ((currentValue == undefined && currentValue == null) ||
734				currentValue !== value) {
735				hasChanged = true;
736				context._reportFilters[filter] = value;
737			}
738		});
739
740		// Have the filters changed
741		if (hasChanged) {
742			context.redraw();
743
744			setTimeout(function() {
745				context.trigger("filterschanged", context._reportFilters);
746			}, 800);
747		}
748	}
749
750	/**
751	 * APIMethod: getReportFilters
752	 * Gets the set of filters used to filter the reports on the map
753	 */
754	Ushahidi.Map.prototype.getReportFilters = function() {
755		return this._reportFilters;
756	}
757
758	/**
759	 * APIMethod: refresh
760	 *
761	 * Deletes the markers and layers from the map and adds them afresh. This
762	 * method should be called when zoom level of the map changes
763	 */
764	Ushahidi.Map.prototype.refresh = function(e) {
765		if (this._isLoaded) {
766			// Update the zoom and redraw
767			this._reportFilters.z = this._olMap.getZoom();
768			this.redraw();
769		}
770		return this;
771	}
772
773	/**
774	 * APIMethod: redraw
775	 *
776	 * Redraws the layers on the map using the updated report filters
777	 * This method is automatically invoked each time the map is zoomed
778	 * or when the report filters are updated
779	 */
780	Ushahidi.Map.prototype.redraw = function() {
781		// Close any open popups
782		this.closePopups();
783
784		for (var i=0; i<this._registry.length; i++) {
785			var layer = this._registry[i];
786			this.addLayer(layer.layerType, layer.options);
787		}
788		return this;
789	}
790
791	/**
792	 * APIMethod: onFeatureSelect
793	 *
794	 * Callback that is executed when a feature that is on the map is
795	 * selected. When executed, it displays a popup with the content/information
796	 * about the selected feature
797	 */
798	Ushahidi.Map.prototype.onFeatureSelect = function(event) {
799		this._selectedFeature = event.feature;
800
801		zoom_point = event.feature.geometry.getBounds().getCenterLonLat();
802		lon = zoom_point.lon;
803		lat = zoom_point.lat;
804
805		// Image to display within the popup
806		var image = "";
807		if (event.feature.attributes.thumb !== undefined && event.feature.attributes.thumb != '') {
808			image = "<div class=\"infowindow_image\"><a href='"+event.feature.attributes.link+"'>";
809			image += "<img src=\""+event.feature.attributes.thumb+"\" height=\"59\" width=\"89\" /></a></div>";
810		} else if (event.feature.attributes.image !== undefined && event.feature.attributes.image != '') {
811			image = "<div class=\"infowindow_image\">";
812			image += "<a href=\""+event.feature.attributes.link+"\" title=\""+event.feature.attributes.name+"\">";
813			image += "<img src=\""+event.feature.attributes.image+"\" />";
814			image += "</a>";
815			image += "</div>";
816		}
817
818		var content = "<div class=\"infowindow\">
818" + image +
819		    "<div class=\"infowindow_content\">"+
820		    "<div class=\"infowindow_list\">"+event.feature.attributes.name+"</div>\n" +
821		    "<div class=\"infowindow_meta\">";
822
823		if (typeof(event.feature.attributes.link) != 'undefined' &&
824		    event.feature.attributes.link != '') {
825
826		    content += "<a href='"+event.feature.attributes.link+"'>" +
827			    "More Information</a><br/>";
828		}
829
830		content += "<a id=\"zoomIn\">";
831		content += "Zoom In</a>";
832		content += "&nbsp;&nbsp;|&nbsp;&nbsp;";
833		content += "<a id=\"zoomOut\">";
834		content += "Zoom Out</a></div>";
835		content += "</div><div style=\"clear:both;\"></div></div>";		
836
837		if (content.search("<script") != -1) {
838			content = "Content contained Javascript! Escaped content " +
839			    "below.<br />" + content.replace(/</g, "&lt;");
840		}
841		  
842		// Destroy existing popups before opening a new one
843		if (event.feature.popup != null) {
844			map.removePopup(event.feature.popup);
845		}
846
847		// Create the popup
848		var popup = new OpenLayers.Popup.FramedCloud("chicken", 
849			event.feature.geometry.getBounds().getCenterLonLat(),
850			new OpenLayers.Size(100,100),
851			content,
852			null, true, this.onPopupClose);
853
854		event.feature.popup = popup;
855		this._olMap.addPopup(popup);
856		popup.show();
857
858		// Register zoom in/out events
859		$("#zoomIn", popup.contentDiv).click(
860			{context: this, latitude: lat, longitude: lon, zoomFactor: 1}, 
861			this.zoomToSelectedFeature);
862
863		$("#zoomOut", popup.contentDiv).click(
864			{context: this, latitude: lat, longitude: lon, zoomFactor: -1}, 
865			this.zoomToSelectedFeature);
866	}
867
868	/**
869	 * APIMethod: onFeatureUnselect
870	 * Callback to be executed when a feature is unselected - when a click
871	 * is registered outside the feature's viewport
872	 */
873	Ushahidi.Map.prototype.onFeatureUnselect = function(e) {
874		if (e.feature.popup != null) {
875			this._olMap.removePopup(e.feature.popup);
876			e.feature.popup.destroy();
877			e.feature.popup = null;
878		}
879	}
880
881	/**
882	 * APIMethod: onPopupClose
883	 * Callback to be executed when the "close" button in the 
884	 * popup is clicked
885	 */
886	Ushahidi.Map.prototype.onPopupClose = function(e) {
887		Ushahidi._currentMap.closePopups();
888	}
889
890	/**
891	 * APIMethod: closePopups
892	 *
893	 * Closes all all open popups
894	 */
895	Ushahidi.Map.prototype.closePopups = function() {
896		if (this._selectedFeature !== undefined && this._selectedFeature != null) {
897			this._selectControl.unselect(this._selectedFeature);
898			this._selectedFeature = null;
899		}
900	}
901
902	/**
903	 * APIMethod: zoomToSelectedFeature
904	 * Callback to be executed when zooming in/out of a report
905	 */
906	Ushahidi.Map.prototype.zoomToSelectedFeature = function(e) {
907		// Get the event data
908		var data = e.data;
909		
910		var point = new OpenLayers.LonLat(data.longitude, data.latitude);
911		var zoomLevel = data.zoomFactor + data.context._olMap.getZoom();
912
913		// Center and zoom
914		data.context._olMap.zoomTo(zoomLevel);
915		data.context._olMap.panTo(point);
916
917		// Close any open popups
918		data.context.closePopups();
919
920		// Halt further event processing
921		return false;
922	}
923
924	/**
925	 * APIMethod: register
926	 * Registers a callback to be invoked when a specified event is triggered - the
927	 * report filters are passed to each of the registered callbacks as a parameter.
928	 *
929	 * Parameters:
930	 * eventName - {String} Name of the event for which to register the callback
931	 * callback  - {Function} Function to be executed when the event in 'eventName' is triggered
932	 * context   - {Object} Execution context of the callback function. This is necessary where
933	 *             the callback function is an object method
934	 */
935	Ushahidi.Map.prototype.register = function(eventName, callback, context) {
936
937		// Is the event known? i.e. in the internal event registry
938		if ($.inArray(eventName, this._EVENTS) === -1)
939			return;
940
941		// Has the event already been registered
942		if (this._callbacks[eventName] == undefined) {
943			this._callbacks[eventName] = [];
944		}
945
946		// Subscribe to the event
947		this._callbacks[eventName].push({
948			callback: callback,
949			context: context
950		});
951	}
952
953	/**
954	 * APIMethod: trigger
955	 * Triggers the event specified specified in the eventName parameter
956	 *
957	 * Parameters:
958	 * eventName - {String} Name of the event to be triggered. No error will be returned
959	 *              if the event is not found
960	 * data - {Object} Data to be passed to the subscribers of the event specified in eventName
961	 */
962	Ushahidi.Map.prototype.trigger = function(eventName, data) {
963		// If the event is unknown, exit without throwing an error
964		if (this._callbacks.length == 0 || this._callbacks[eventName] == undefined) {
965			return;
966		}
967
968		// Get the subscribers for the event
969		var subscribers = this._callbacks[eventName];
970
971		for (var i=0; i < subscribers.length; i++) {
972			var subscriber = subscribers[i];
973			if (subscriber.context == undefined || subscriber.context == null) {
974				subscriber.callback(data);
975			} else {
976				subscriber.callback.call(subscriber.context, data);
977			}
978		}
979	}
980
981	/**
982	 * APIMethod: resize
983	 * Resizes the map when the size of its container changes
984	 */
985	Ushahidi.Map.prototype.resize = function() {
986		this._olMap.updateSize();
987		this._olMap.pan(0, 1);
988	}
989
990	/**
991	 * APIMethod: deleteLayer
992	 * Deletes a layer from the map
993	 *
994	 * Parameters:
995	 * name - {String} Name of the layer to be deleted
996	 */
997	Ushahidi.Map.prototype.deleteLayer = function(name) {
998		var layers = name;
999		if (typeof name === 'string')
1000			layers = this._olMap.getLayersByName(name);
1001		
1002		for (var i=0; i < layers.length; i++) {
1003			// Set opacity to 0 then hide, using CSS3 transitions to fade the layer out
1004			layers[i].div.style['opacity'] = 0.2;
1005			layers[i].display(false);
1006			// Skip layer if its not on the map
1007			if (layers[i].map == null || this._olMap.getLayerIndex(layers[i]) == -1) continue;
1008			
1009			this._olMap.removeLayer(layers[i]);
1010			if (layers[i].destroyFeatures !== undefined)
1011				layers[i].destroyFeatures();
1012		}
1013	}
1014
1015	/**
1016	 * APIMethod: createRadiusLayer
1017	 * Creates a radius layer
1018	 * Parameters:
1019	 * config - {Object} The properties to be used to create the radius layer
1020	 */
1021	Ushahidi.Map.prototype.addRadiusLayer = function(config) {
1022
1023		// Check if the Layer Switcher control has been added to the map
1024		// and delete all instances of such
1025		var layerSwitchers = this._olMap.getControlsByClass("OpenLayers.Control.LayerSwitcher");
1026		for (var i = 0; i < layerSwitchers.length; i++) {
1027			this._olMap.removeControl(layerSwitchers[i]);
1028		}
1029
1030		this.updateRadius(config);
1031		var context = this;
1032
1033		// Detect click events on the map
1034		this._olMap.events.register("click", context._olMap, function(e) {
1035			var lonlat = context._olMap.getLonLatFromViewPortPx(e.xy);
1036			lonlat.transform(Ushahidi.proj_900913, Ushahidi.proj_4326);
1037
1038			var coords = {latitude: lonlat.lat, longitude: lonlat.lon};
1039			context.updateRadius(coords);
1040			context.trigger("markerpositionchanged", coords);
1041		});
1042		
1043	}
1044
1045	/**
1046	 * APIMethod: updateRadius
1047	 * Updates the location and size of the radius layer
1048	 *
1049	 * Parameters:
1050	 * options - {Object} Properties for updating the radius layer
1051	 *
1052	 * Valid options are:
1053	 * latitude - Latitude for the alert center
1054	 * longitude - Longitude for the alert center
1055	 * radius - {Number} Alert radius
1056	 */
1057	Ushahidi.Map.prototype.updateRadius = function(options) {
1058		// Get the radius
1059		this._radius = (options.radius == undefined) ? 20000 : options.radius;
1060
1061		if (options.latitude !== undefined && options.longitude !== undefined) {
1062			this._currentMarkerPosition = {latitude: options.latitude, longitude: options.longitude};
1063		} 
1064
1065		if (this._currentMarkerPosition.latitude == undefined ||
1066			this._currentMarkerPosition.longitude == undefined) {
1067			throw "Missing latitude and longitude in the options";
1068		}
1069
1070		// Update the options with latitude and longitude
1071		var latitude = this._currentMarkerPosition.latitude;
1072		var longitude = this._currentMarkerPosition.longitude;
1073
1074		// Delete the layers and re-add them
1075		this.deleteLayer("radius");
1076		this.deleteLayer("radiusmarker");
1077
1078		// Get the current map center
1079		var point  = new OpenLayers.LonLat(longitude, latitude);
1080		point.transform(Ushahidi.proj_4326, Ushahidi.proj_900913);
1081
1082		// Move the map to the new center
1083		this._olMap.moveTo(point);
1084
1085		var circleOrigin = new OpenLayers.Geometry.Point(longitude, latitude);
1086		circleOrigin.transform(Ushahidi.proj_4326, Ushahidi.proj_900913);
1087
1088		// Radius feature
1089		var radiusFeature = new OpenLayers.Feature.Vector(
1090			OpenLayers.Geometry.Polygon.createRegularPolygon(circleOrigin, this._radius, 40, 0),
1091			null,
1092			OpenLayers.Util.extend({}, OpenLayers.Feature.Vector.style["default"])
1093		);
1094
1095		var radiusLayer = new OpenLayers.Layer.Vector("radius");
1096		radiusLayer.addFeatures([radiusFeature]);
1097
1098		// Create marker to be used for adjusting the center
1099		// point for the radius
1100		var markers = new OpenLayers.Layer.Markers("radiusmarker");
1101		markers.addMarker(new OpenLayers.Marker(point));
1102
1103		this._olMap.addLayers([radiusLayer, markers]);
1104
1105	}
1106
1107	/**
1108	 * APIMethod: updateBaseLayer
1109	 * Changes the basealyer of the map
1110	 * Parameters:
1111	 * layerName - {String} Name of the new base layer
1112	 */
1113	Ushahidi.Map.prototype.updateBaseLayer = function(layerName) {
1114		var layers =  this._olMap.getLayersByName(layerName);
1115		if (layers.length < 1)
1116			throw "Layer " + layerName + " not found";
1117
1118		layers[0].setVisibility(true);
1119		this._olMap.setBaseLayer(layers[0]);
1120	}
1121
1122	/**
1123	 * APIMethod: updateZoom
1124	 * Updates the zoom level of the map
1125	 * Parameters:
1126	 * zoom - {Number} The zoom level to adjust to
1127	 */
1128	Ushahidi.Map.prototype.updateZoom = function(zoom) {
1129		if (this.currentZoom !== zoom) {
1130			this.currentZoom = zoom;
1131			this._olMap.setCenter(this._currentCenter, zoom);
1132		}
1133	}
1134
1135	/**
1136	 * APIMethod: updateMapCenter
1137	 * Updates the center of the map
1138	 */
1139	Ushahidi.Map.prototype.updateMapCenter = function(center) {
1140		var point = new OpenLayers.LonLat(center.longitude, center.latitude);
1141		point.transform(Ushahidi.proj_4326, Ushahidi.proj_900913);
1142		this._olMap.panTo(point);
1143
1144		// Re-add the default layer
1145		this.addLayer(Ushahidi.DEFAULT);
1146
1147		// Map center has changed, trigger 
1148		this.trigger("markerpositionchanged", center);
1149	}
1150
1151	/**
1152	 * APIMethod: triggerZoomChanged
1153	 * Callback that triggers the "zoomchanged" event. This method
1154	 * is called by OpenLayers when the map zoom changes and the
1155	 * Ushahidi.Map object was initialized with the detectMapZoom
1156	 * option set to TRUE
1157	 */
1158	Ushahidi.Map.prototype.triggerZoomChanged = function(e) {
1159		if (this._isLoaded) {
1160			this.trigger("zoomchanged", this._olMap.getZoom());
1161
1162			// EK <emmanuel(at)ushahidi.com>
1163			// Dirty hack to add back the default layer
1164			// The assumption here is that this method is only called
1165			// when we're using the default layer
1166			this.addLayer(Ushahidi.DEFAULT);
1167		}
1168	}
1169	
1170	/**
1171	 * APIMethod: keepOnTop
1172	 * Forces specified layer(s) to the top of the stack
1173	 */
1174	Ushahidi.Map.prototype.keepOnTop = function(layer) {
1175		for (var i=0; i<this._onTop.length; i++) {
1176			var layerName = this._onTop[i];
1177			var layers = this._olMap.getLayersByName(layerName);
1178			
1179			for (var j=0; j<layers.length; j++) {
1180				this._olMap.raiseLayer(layers[j], this._olMap.getNumLayers());
1181			}
1182		}
1183	}
1184
1185
1186	/**
1187	 * Helper method: convert LongLat
1188	 * Converts LongLat coordinates from Open Layers to "lat, long"
1189	 */
1190	Ushahidi.convertLongLat = function(longLat) {
1191		return longLat.lat.toFixed(5) + ", " + longLat.lon.toFixed(5)
1192	}
1193})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.