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 += " | "; 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, "<"); 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.