/** * JavaScript for Google Maps v3 maps in the Maps extension. * @see http://www.mediawiki.org/wiki/Extension:Maps * * @author Jeroen De Dauw * @author Peter Grassberger < petertheone@gmail.com > */ (function ($, mw) { $.fn.googlemaps = function (options) { var _this = this; this.map = null; this.markercluster = null; this.options = options; /** * All markers that are currently on the map. * @type {Array} * @private */ this.markers = []; /** * All Polylines currently on the map, * @type {Array} * @private */ this.lines = []; /** * All polygons currently on the map, */ this.polygons = []; /** * All circles on the map */ this.circles = []; /** * All rectangles on the map */ this.rectangles = []; /** * All image overlays on the map */ this.imageoverlays = []; var getBounds = function() { if (( options.centre === false || options.zoom === false ) && options.locations.length > 1) { var bounds = new google.maps.LatLngBounds(); for (var i = _this.markers.length - 1; i >= 0; i--) { bounds.extend(_this.markers[i].getPosition()); } return bounds; } return null; }; var setZoom = function(bounds) { if (options.zoom === false) { _this.map.fitBounds(bounds); } else { _this.map.setZoom(options.zoom); } }; /** * Creates a new marker with the provided data, * adds it to the map, and returns it. * @param {Object} markerData Contains the fields lat, lon, title, text and icon * @return {google.maps.Marker} */ this.addMarker = function (markerData) { var markerOptions = { position: new google.maps.LatLng(markerData.lat, markerData.lon), title: markerData.title, text: markerData.text, labelContent: markerData.inlineLabel, labelAnchor: new google.maps.Point(-15, 34), labelClass: 'markerwithlabel' }; if ( markerOptions.text !== '' ) { markerOptions.text = $('
' + markerOptions.text + '
').text(); } if (!markerData.hasOwnProperty('icon') || markerData.icon !== '') { markerOptions.icon = markerData.icon; } if (markerData.visitedicon !== '') { if(markerData.visitedicon === 'on'){ //when keyword 'on' is set, set visitedicon to a default official marker markerOptions.visitedicon = mw.config.get('wgScriptPath')+'/extensions/Maps/resources/GoogleMaps/img/blue-dot.png'; }else{ markerOptions.visitedicon = markerData.visitedicon; } } var addToMapAndHandlers = function( marker ) { //Add onclick listener google.maps.event.addListener(marker, 'click', function (e) { if (e.target !== undefined && (e.target instanceof HTMLAnchorElement || e.target.tagName == 'A')) { //click link defined in inlinelabel window.location.href = e.target.href; } else { openBubbleOrLink.call(this, markerData, e, marker); } if (markerOptions.visitedicon) { marker.setIcon(markerOptions.visitedicon); markerOptions.visitedicon = undefined; } }); marker.setMap( _this.map ); _this.markers.push( marker ); return marker; }; var marker; if (markerData.inlineLabel === undefined || markerData.inlineLabel === null || markerData.inlineLabel.length == 0) { marker = new google.maps.Marker( markerOptions ); return addToMapAndHandlers( marker ); } else { mw.loader.using( 'ext.maps.gm3.markerwithlabel', function() { marker = new MarkerWithLabel( markerOptions ); addToMapAndHandlers( marker ); setZoom(getBounds()); } ); } }; /** * Removes a single marker from the map. * @param {google.maps.Marker} marker The marker to remove. */ this.removeMarker = function (marker) { marker.setMap(null); for (var i = this.markers.length - 1; i >= 0; i--) { if (this.markers[i] === marker) { delete this.markers[i]; break; } } delete marker; }; /** * Removes all markers from the map. */ this.removeMarkers = function () { if (this.markercluster) { this.markercluster.setMap(null); this.markercluster = null; } for (var i = this.markers.length - 1; i >= 0; i--) { this.markers[i].setMap(null); } this.markers = []; }; /** * Remove the "earth" type from options.types if it's present. * * @since 1.0.1 */ this.removeEarthType = function () { if (Array.prototype.filter) { options.types = options.types.filter(function (element, index, array) { return element !== 'earth'; }); } else { // Seems someone is using the o-so-awesome browser that is IE. var types = []; for (i in options.types) { if (typeof( options.types[i] ) !== 'function' && options.types[i] !== 'earth') { types.push(options.types[i]); } } options.types = types; } }; this.addOverlays = function () { // Add the Google KML/KMZ layers. for (i = options.gkml.length - 1; i >= 0; i--) { var kmlLayer = new google.maps.KmlLayer( options.gkml[i], { map:this.map, preserveViewport:!options.kmlrezoom } ); } // If there are any non-Google KML/KMZ layers, load the geoxml library and use it to add these layers. if (options.kml.length != 0) { mw.loader.using('ext.maps.gm3.geoxml', function () { function addToCopyCoords(doc){ if(options.copycoords){ for(var i = 0; i < doc.length; i++){ addCopyCoordsOnRightClick([ doc[i].gpolygons, doc[i].gpolylines, doc[i].ggroundoverlays ]); } } } var geoXml = new geoXML3.parser({ map:_this.map, zoom:options.kmlrezoom, failedParse:function(document){ console.log(options.kml); console.log(document); alert(mw.msg('maps-kml-parsing-failed')); } }); geoXml.options.afterParse = function(docs){ //add toggle functionality var toggleDiv = document.createElement('div'); toggleDiv.style.backgroundColor = 'white'; toggleDiv.style.marginTop = '5px'; toggleDiv.style.padding = '5px'; toggleDiv.style.border = '1px solid grey'; for(var i = docs.length-1; i >= 0; i--){ (function(doc){ var label = document.createElement('label'); label.style.display = 'block'; var text = document.createTextNode(doc.baseUrl.substring(doc.baseDir.length)); var checkbox = document.createElement('input'); checkbox.setAttribute('type','checkbox'); checkbox.style.verticalAlign = '-0.2em'; checkbox.checked = true; checkbox.onclick = function(){ if(this.checked){ geoXml.showDocument(doc); }else{ geoXml.hideDocument(doc); } }; label.appendChild(checkbox); label.appendChild(text); toggleDiv.appendChild(label); })(docs[i]); } _this.map.controls[google.maps.ControlPosition.TOP_RIGHT].push(toggleDiv); }; geoXml.parse(options.kml); }); } }; this.addLine = function (properties) { var paths = new google.maps.MVCArray(); for (var x = 0; x < properties.pos.length; x++) { paths.push(new google.maps.LatLng(properties.pos[x].lat, properties.pos[x].lon)); } var line = new google.maps.Polyline({ map:this.map, path:paths, strokeColor:properties.strokeColor, strokeOpacity:properties.strokeOpacity, strokeWeight:properties.strokeWeight }); this.lines.push(line); google.maps.event.addListener(line, "click", function (event) { openBubbleOrLink.call(this, properties, event, line); }); }; this.removeLine = function (line) { line.setMap(null); for (var i = this.line.length - 1; i >= 0; i--) { if (this.line[i] === line) { delete this.line[i]; break; } } delete line; }; this.removeLines = function () { for (var i = this.lines.length - 1; i >= 0; i--) { this.lines[i].setMap(null); } this.lines = []; }; this.addPolygon = function (properties) { var paths = new google.maps.MVCArray(); for (var x = 0; x < properties.pos.length; x++) { paths.push(new google.maps.LatLng(properties.pos[x].lat, properties.pos[x].lon)); } var polygon = new google.maps.Polygon({ map:this.map, path:paths, strokeColor:properties.strokeColor, strokeOpacity:properties.strokeOpacity, strokeWeight:properties.strokeWeight, fillColor:properties.fillColor, fillOpacity:properties.fillOpacity }); this.polygons.push(polygon); //add hover event/effect if (properties.onlyVisibleOnHover === true) { function hidePolygon(polygon) { polygon.setOptions({ fillOpacity:0, strokeOpacity:0 }); } hidePolygon(polygon); google.maps.event.addListener(polygon, "mouseover", function () { this.setOptions({ fillOpacity:properties.fillOpacity, strokeOpacity:properties.strokeOpacity }); }); google.maps.event.addListener(polygon, "mouseout", function () { hidePolygon(this); }); } //add click event google.maps.event.addListener(polygon, "click", function (event) { openBubbleOrLink.call(this, properties, event, polygon); }); }; this.addCircle = function (properties) { var circle = new google.maps.Circle({ map:this.map, center:new google.maps.LatLng(properties.centre.lat, properties.centre.lon), radius:properties.radius, fillColor:properties.fillColor, fillOpacity:properties.fillOpacity, strokeColor:properties.strokeColor, strokeOpacity:properties.strokeOpacity, strokeWeight:properties.strokeWeight }); this.circles.push(circle); //add click event google.maps.event.addListener(circle, "click", function (event) { openBubbleOrLink.call(this, properties, event, circle); }); }; this.addRectangle = function (properties) { var rectangle = new google.maps.Rectangle({ map:this.map, bounds:new google.maps.LatLngBounds( new google.maps.LatLng(properties.sw.lat, properties.sw.lon), //sw new google.maps.LatLng(properties.ne.lat, properties.ne.lon) //ne ), fillColor:properties.fillColor, fillOpacity:properties.fillOpacity, strokeColor:properties.strokeColor, strokeOpacity:properties.strokeOpacity, strokeWeight:properties.strokeWeight }); this.rectangles.push(rectangle); //add click event google.maps.event.addListener(rectangle, "click", function (event) { openBubbleOrLink.call(this, properties, event, rectangle); }); }; this.addImageOverlay = function(properties){ var imageBounds = new google.maps.LatLngBounds( new google.maps.LatLng(properties.sw.lat,properties.sw.lon), new google.maps.LatLng(properties.ne.lat,properties.ne.lon) ); var image = new google.maps.GroundOverlay(properties.image,imageBounds); image.setMap(this.map); this.imageoverlays.push(image); //add click event google.maps.event.addListener(image, "click", function (event) { openBubbleOrLink.call(this, properties, event, image); }); }; this.removePolygon = function (polygon) { polygon.setMap(null); for (var i = this.polygon.length - 1; i >= 0; i--) { if (this.polygon[i] === polygon) { delete this.polygon[i]; break; } } delete polygon; }; this.removePolygons = function () { for (var i = this.polygon.length - 1; i >= 0; i--) { this.polygon[i].setMap(null); } this.polygon = []; }; //Rezoom's the map to show all visible markers. this.reZoom = function(){ var bounds = new google.maps.LatLngBounds(); for(var x = 0; x < this.markers.length; x++){ var marker = this.markers[x]; if (marker.getVisible() === true) { bounds.extend(marker.getPosition()); } } this.map.fitBounds(bounds); }; this.createMarkerCluster = function() { if ( !options.cluster ) { return; } if (this.markercluster) { this.markercluster.setMap(null); this.markercluster = null; } this.markercluster = new MarkerClusterer( this.map, this.markers, { imagePath: mw.config.get( 'wgScriptPath' ) + '/extensions/Maps/resources/leaflet/cluster/m', gridSize: this.options.clustergridsize, maxZoom: this.options.clustermaxzoom, zoomOnClick: this.options.clusterzoomonclick, averageCenter: this.options.clusteraveragecenter, minimumClusterSize: this.options.clusterminsize } ); }; this.initializeMap = function () { var mapOptions = { disableDefaultUI:true, mapTypeId:options.type == 'earth' ? google.maps.MapTypeId.SATELLITE : google.maps.MapTypeId[ options.type ] }; // Map controls mapOptions.panControl = $.inArray('pan', options.controls) != -1; mapOptions.zoomControl = $.inArray('zoom', options.controls) != -1; mapOptions.mapTypeControl = $.inArray('type', options.controls) != -1; mapOptions.scaleControl = $.inArray('scale', options.controls) != -1; mapOptions.streetViewControl = $.inArray('streetview', options.controls) != -1; mapOptions.rotateControl = $.inArray('rotate', options.controls) != -1; for (i in options.types) { if (typeof( options.types[i] ) !== 'function') { options.types[i] = google.maps.MapTypeId[ options.types[i].toUpperCase() ]; } } // Map control styles mapOptions.zoomControlOptions = { style:google.maps.ZoomControlStyle[ options.zoomstyle ] }; mapOptions.mapTypeControlOptions = { style:google.maps.MapTypeControlStyle[ options.typestyle ], mapTypeIds:options.types }; //max/min -zoom mapOptions.maxZoom = options.maxzoom === false ? null : options.maxzoom; mapOptions.minZoom = options.minzoom === false ? null : options.minzoom; //static mode if (options['static']) { mapOptions.draggable = false; mapOptions.disableDoubleClickZoom = true; mapOptions.panControl = false; mapOptions.rotateControl = false; mapOptions.zoomControl = false; mapOptions.scrollwheel = false; mapOptions.streetViewControl = false; mapOptions.overviewMapControl = false; mapOptions.mapTypeControl = false; } if (options.scrollwheelzoom === true) { mapOptions.gestureHandling = 'greedy'; } var map = new google.maps.Map(this.get(0), mapOptions); google.maps.event.addListenerOnce(map, 'tilesloaded', function () { _this.addOverlays(); }); this.map = map; if (options.poi === false) { map.setOptions({ styles:[ { featureType:"poi", stylers:[ { visibility:"off" } ] } ] }); } if (!options.locations) { options.locations = []; } // Add the markers. for (var i = options.locations.length - 1; i >= 0; i--) { this.addMarker(options.locations[i]); } for (i = options.layers.length - 1; i >= 0; i--) { if ( options.layers[i] === 'traffic' ) { ( new google.maps.TrafficLayer() ).setMap(map); } else if ( options.layers[i] === 'bicycling' ) { ( new google.maps.BicyclingLayer() ).setMap(map); } else if ( options.layers[i] === 'transit' ) { ( new google.maps.TransitLayer() ).setMap(map); } } var bounds = getBounds(); setZoom(bounds); var centre; if (options.centre === false) { if (options.locations.length > 1) { centre = bounds.getCenter(); } else if (options.locations.length == 1) { centre = new google.maps.LatLng(options.locations[0].lat, options.locations[0].lon); } else { centre = new google.maps.LatLng(0, 0); } } else { centre = new google.maps.LatLng(options.centre.lat, options.centre.lon); } map.setCenter(centre); setTimeout( function() { if ( options.autoinfowindows ) { for ( var i = _this.markers.length - 1; i >= 0; i-- ) { google.maps.event.trigger( _this.markers[i], 'click', {} ); } } }, 500 // If we wait a bit, the map will re-position to accommodate for the info windows. ); if (options.resizable) { mw.loader.using('ext.maps.resizable', function () { _this.resizable(); }); } /** * used in display_line functionality * draws paths between markers */ if (options.lines) { for (var i = 0; i < options.lines.length; i++) { this.addLine(options.lines[i]); } } /** * used in display_line to draw polygons */ if (options.polygons) { for (var i = 0; i < options.polygons.length; i++) { this.addPolygon(options.polygons[i]); } } /** * used in display_line to draw circles */ if (options.circles) { for (var i = 0; i < options.circles.length; i++) { this.addCircle(options.circles[i]); } } /** * used in display_line to draw rectangles */ if (options.rectangles) { for (var i = 0; i < options.rectangles.length; i++) { this.addRectangle(options.rectangles[i]); } } /** * Allows grouping of markers. */ this.createMarkerCluster(); if (options.searchmarkers) { var searchBoxValue = mw.msg('maps-searchmarkers-text'); var searchBox = $(''); var searchContainer = document.createElement('div'); searchContainer.style.padding = '5px'; searchContainer.index = 1; searchBox.appendTo(searchContainer); map.controls[google.maps.ControlPosition.TOP_RIGHT].push(searchContainer); //prevents markers and other map objects to be placed beneath searchfield google.maps.event.addListenerOnce(map, 'bounds_changed', function () { map.panBy(0,-30); }); searchBox.on('keyup',function (e) { for (var i = 0; i < _this.markers.length; i++) { var haystack = ''; var marker = _this.markers[i]; if (options.searchmarkers == 'title') { haystack = marker.title; } else { haystack = marker.title + marker.text; } var visible = haystack.toLowerCase().indexOf(e.target.value.toLowerCase()) != -1; marker.setVisible(visible); } _this.reZoom(); }).on('focusin',function () { if ($(this).val() === searchBoxValue) { $(this).val(''); } }).on('focusout', function () { if ($(this).val() === '') { $(this).val(searchBoxValue); } }); } if(options.imageoverlays){ for (var i = 0; i < options.imageoverlays.length; i++) { this.addImageOverlay(options.imageoverlays[i]); } } if (options.copycoords) { addCopyCoordsOnRightClick([ this.lines, this.circles, this.polygons, this.markers, this.rectangles, this.imageoverlays, this.map ]); } if (options.wmsoverlay) { var wmsOptions = { alt: "OpenLayers", getTileUrl:function (tile, zoom) { var projection = _this.map.getProjection(); var zpow = Math.pow(2, zoom); var ul = new google.maps.Point(tile.x * 256.0 / zpow, (tile.y + 1) * 256.0 / zpow); var lr = new google.maps.Point((tile.x + 1) * 256.0 / zpow, (tile.y) * 256.0 / zpow); var ulw = projection.fromPointToLatLng(ul); var lrw = projection.fromPointToLatLng(lr); //The user will enter the address to the public WMS layer here. The data must be in WGS84 var baseURL = options.wmsoverlay.wmsServerUrl; //The layer ID. Can be found when using the layers properties tool in ArcMap or from the WMS settings var layers = options.wmsoverlay.wmsLayerName; var style = options.wmsoverlay.wmsStyleName; //With the 1.3.0 version the coordinates are read in LatLon, as opposed to LonLat in previous versions var bbox = ulw.lat() + "," + ulw.lng() + "," + lrw.lat() + "," + lrw.lng(); //Establish the baseURL. Several elements, including &EXCEPTIONS=INIMAGE and &Service are unique to openLayers addresses. var url = baseURL + "version=1.3.0&EXCEPTIONS=INIMAGE&Service=WMS" + "&request=GetMap&Styles=" + encodeURI(style) + "&format=image%2Fjpeg&CRS=EPSG:4326" + "&width=256&height=256" + "&Layers=" + layers + "&BBOX=" + bbox; return url; }, isPng: false, maxZoom: 17, minZoom: 1, name: "OpenLayers", tileSize: new google.maps.Size(256, 256) }; //Creating the object to create the ImageMapType that will call the WMS Layer Options. openlayersWMS = new google.maps.ImageMapType(wmsOptions); //Layers to appear on Map A. The first string will give the map the map a name in the dropdown and the second object calls the map type map.mapTypes.set('OpenLayers', openlayersWMS); map.setMapTypeId('OpenLayers'); } //Add custom controls // - Fullscreen if(options.fullscreen){ this.map.controls[google.maps.ControlPosition.TOP_RIGHT].push(new FullscreenControl(this.map)); } }; this.setup = function () { var showEarth = $.inArray('earth', options.types) !== -1 || options.type == 'earth'; // If there are any non-Google KML/KMZ layers, load the geoxml library and use it to add these layers. if (showEarth) { this.removeEarthType(); $.getScript( 'https://www.google.com/jsapi', function (data, textStatus) { google.load('earth', '1', { callback:function () { mw.loader.using('ext.maps.gm3.earth', function () { _this.initializeMap(); if (google.earth.isSupported()) { _this.ge = new GoogleEarth(_this.map); } }); } }); } ); }else{ this.initializeMap(); } }; function FullscreenControl(map) { var controlDiv = document.createElement('div'); controlDiv.style.padding = '5px'; controlDiv.index = 1; var controlUI = document.createElement('div'); controlUI.style.backgroundColor = 'white'; controlUI.style.borderStyle = 'solid'; controlUI.style.borderColor = 'rgba(0, 0, 0, 0.14902)'; controlUI.style.borderWidth = '1px'; controlUI.style.borderRadius = '2px'; controlUI.style.cursor = 'pointer'; controlUI.style.textAlign = 'center'; controlUI.style.boxShadow = 'rgba(0, 0, 0, 0.298039) 0px 1px 4px -1px'; controlUI.style.backgroundClip = 'padding-box'; controlUI.title = mw.msg('maps-fullscreen-button-tooltip'); controlDiv.appendChild(controlUI); var controlText = document.createElement('div'); controlText.style.fontFamily = 'Roboto, Arial, sans-serif'; controlText.style.fontSize = '11px'; controlText.style.fontWeight = '400'; controlText.style.color = 'rgb(86, 86, 86)'; controlText.style.padding = '1px 6px'; controlText.innerHTML = mw.msg('maps-fullscreen-button'); controlUI.appendChild(controlText); google.maps.event.addDomListener(controlUI, 'click', function() { var mapDiv = $(map.getDiv()); if(mapDiv.data('preFullscreenCss') != null){ mapDiv.css(mapDiv.data('preFullscreenCss')); mapDiv.removeData('preFullscreenCss'); }else{ var fullscreenCss = { position:'fixed', top: 0, left:0, width:'100%', height:'100%', zIndex:10000 }; var oldState = {}; for(var cssProp in fullscreenCss){ oldState[cssProp] = mapDiv.css(cssProp); } mapDiv.data('preFullscreenCss',oldState); mapDiv.css(fullscreenCss); } google.maps.event.trigger(map, "resize"); }); return controlDiv; } function openBubbleOrLink(markerData, event, obj) { if (markerData.link) { window.location.href = markerData.link; } else if (markerData.text.trim() !== '') { openBubble.call(this, markerData, event, obj); } } function openBubble( markerData, event, obj ) { if ( this.openWindow !== undefined ) { this.openWindow.close(); } this.openWindow = new google.maps.InfoWindow(); this.openWindow.setContent( markerData.text ); if ( event.latLng !== undefined ) { this.openWindow.setPosition( event.latLng ); } this.openWindow.closeclick = function () { obj.openWindow = undefined; }; if ( event.latLng === undefined ) { this.openWindow.open( _this.map, this ); } else { this.openWindow.open( _this.map ); } } function addCopyCoordsOnRightClick(object) { if(object instanceof Array){ for (var x = 0; x < object.length; x++) { addCopyCoordsOnRightClick(object[x]); } }else{ google.maps.event.addListener(object, 'rightclick', function (event) { prompt(mw.msg('maps-copycoords-prompt'), event.latLng.lat() + ',' + event.latLng.lng()); }); } } //Complete path to OpenLayers WMS layer if (!options.cluster) { this.setup(); } else { mw.loader.using( 'ext.maps.gm3.markercluster', function() { _this.setup(); } ); } return this; }; })(jQuery, window.mediaWiki);