Last updated by Dan Harrelson on May 27th, 2009

mapZING

Sample of using mapZING on the CNN.com homepage

mapzing-cnn

Visualize the "where" of the web

Thanks for taking a look at the underpinnings of mapZING. If you are looking for more of a introduction to the application, take a look at this blog post.

I created mapZING as an experiment to see what I could do with a mashup of ANY web page, the Yahoo! Placemaker service, some of the Yahoo! UI Library elements and some custom code. In just a couple of days I created something pretty powerful, and had fun doing so. In the development of mapZING I found the learnings of others to be invaluable and in response, I am documenting all of my source code here. It is far from perfect and I'll be improving the code when I get time. Here's a list of what I have in mind. I hope that others find this valuable in some small way.

...Dan

mapZING How to use mapZING
  1. Drag the link to the bookmarks/links toolbar or right-click and save as a bookmark/favorite.
  2. Go to a web page with location information.
  3. Click on the mapZING bookmark to all of that page's places on a map

Create a bookmarklet

A user starts interacting with mapZING via a small bookmarklet. This JavaScript embedded into a bookmark (or favorite if you are in IE) simply injects a new <script> element into the current page. This script's source is a larger collection of JavaScript functions that sets up the mapZING environment. The final bookmarklet of course needs to be condensed into a single line with few spaces.

		javascript:
		var d = document, i = "mapzingjs", j = d.getElementById(i), m = "mapZING", z = d.getElementById(m);
		if (!j) { //only add the setup script if needed
			j = d.createElement("script");
			j.id = i;
			j.src = "http://danharrelson.com/mapzing/startmapzing.js";
			var b = d.body;
			if (b) {
				b.appendChild(j);
			} else {
				var h = d.head;
				if (h) {
					h.appendChild(j);
				}
			}
		} else {
			if (!z)	mzAddMap(); //only inject the window if it's closed
		} 
		void(0);	
	

Setup the mapZING environment

The bookmarklet injected a little JavaScript and that in turns injects a lot more. <script> elements from the core mapZING library and from the Yahoo! Developmer Network are included. The current page's URI is passed through to the mapZING JavaScript library in order to harvest places from the page. <style> elements are injected into the page in order to apply the proper CSS to the mapZING window.

		function addScript(id, src) {
			var newScript = document.createElement("script");
			newScript.src = src;
			newScript.id = id;
			document.body.appendChild(newScript);
		}
		
		function addStyle(id,src) {
			var newStyle = document.createElement("link");
			newStyle.id = id;
			newStyle.rel="stylesheet";
			newStyle.type="text/css";
			newStyle.media="screen, projection";
			newStyle.href=src;
			document.getElementsByTagName("head")[0].appendChild(newStyle);
		}
		
		addScript("yui","http://yui.yahooapis.com/combo?2.7.0/build/yahoo-dom-event/yahoo-dom-event.js&2.7.0/build/animation/animation-min.js&2.7.0/build/connection/connection-min.js&2.7.0/build/dragdrop/dragdrop-min.js");
		addScript("ymap","http://l.yimg.com/d/lib/map/js/api/ymapapi_3_8_2_3.js");
		
		/* pass the current URI for processing */
		addScript("mapzingjs","http://danharrelson.com/mapzing/mapzing.js.php?url="+window.location);
		
		addStyle("mapzingcss","http://danharrelson.com/mapzing/mapzing.css");
		addStyle("yresetcss","http://yui.yahooapis.com/3.0.0pr2/build/cssreset/reset-context-min.css");
	

Styling the mapZING window

Yeah, there's CSS. Boring, but for completeness here it is...

		#mapZING { z-index: 10000; width: 100%; height: 1px; position: absolute; left: 0px; top: 10px; padding: 0; margin: 0; }
	
		#mzInner { 
					border: 1px #646464 solid; 
					margin-left: auto; 
					margin-right: 10px; 
					font-family: Helvetica, Arial, sans-serif; 
					width: 408px; height: 333px; 
					background-color: #e8e8e8; 
					text-align: left;
		}
		
		#mzInner #mzHeader { margin-right: 5px; margin-left: 5px; font-size: 10pt; padding-top: 5px; height:15px; }
		#mzInner #mzHeader a { padding:5px 5px 5px 10px; color: #8b8b8b; text-decoration: none; margin-right: 15px; font-size: 8pt; }
		#mzInner #mzHeader .mzbuttons { float:left; }
		#mzInner #mzHeader .mzCloseBtn { background: no-repeat url(./img/cross.png) 0 5px; }
		#mzInner #mzHeader .mzSizeBtn { padding-left: 18px; background: no-repeat url(./img/zoom.png) 0 5px; }
		#mzInner #mzHeader .mzTitle { padding-right: 5px; float: right; font-weight: bold; text-align: right; }
		
		#mzInner #mzYMap { margin:auto; margin-top: 10px; width: 400px; height: 300px; }
	

Get the page's places

Using PHP, mapZING takes in a URL parameter, POSTs that URL and some other configuration to the Yahoo! Placemaker service and expects an XML response. The XML data is parsed as PHP objects looking for valid "places". Each place has a name, a unique identifier (WOEID) and a location defined by latitude and longitude. This data is encoded into JSON for use later by mapZING's JavaScript.

		<?php
			$key = 'YOUR KEY HERE';
			$apiendpoint = 'http://wherein.yahooapis.com/v1/document';
			$url = $_GET["url"]; //the current location is passed via a GET parameter
			$inputType = 'text/html';
			$outputType = 'xml';
			$post = 'appid='.$key.'&documentURL='.$url.'&documentType='.$inputType.'&outputType='.$outputType;
			$ch = curl_init($apiendpoint);
			curl_setopt($ch, CURLOPT_POST, 1);
			curl_setopt($ch, CURLOPT_POSTFIELDS, $post);
			curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
			$results = curl_exec($ch);
			
			/*
			Read in the XML data as an object
			Iterate over the data object looking for found places
			Create an array of all places
			Encode the array as JSON for use in the JavaScript that follows
			*/
			$xml = simplexml_load_string($results, 'SimpleXMLElement', LIBXML_NOCDATA);
			if($xml->document->placeDetails) {
				$places = array();
				$placeList = "";
				foreach ($xml->document->placeDetails as $placeDetails) {
					$place = $placeDetails->place;
					$places[] = json_encode($place);
					$placeList .= '
  • '.$place->name.'
  • '; } $xmlToJson = '['.join(',',$places).']'; } else { echo "noPlacesFound = true;"; //no places found, so set the JavaScript boolean value to false } ?>

    Draw the map

    mapZING uses the Yahoo! Maps AJAX API. The JSON data created earlier via PHP is used to plot places on the map. Each marker has a mouseOver that displays the name of the place. A complete array of the places is passes to the maps API in order to calculate the idea zoom level and center point so that all of the places fit on the map.

    		var placeData = eval( <? echo $xmlToJson ?> );
    	
    		var YMAPPID = "YOUR KEY HERE";
    		
    		/*
    		load a new Y! map and call function to plot markers with the JSON data
    		*/
    		var mzYMap;
    		function mzStartMap(){
    			mzYMap = new YMap(document.getElementById('mzYMap'));
    			mzYMap.addTypeControl();	//map, satellite, hybrid
    			mzYMap.addZoomLong();		//zoom control
    			mzPlotOnMap(placeData);
    		}
    		
    		/*
    		iterates through the list of places and creates corresponding markers on the Y! Map
    		expects one argument: JSON data evaluated as placeData
    		*/
    		function mzPlotOnMap (places) {
    			if (places.length > 0) {
    				var geopoints = [];		//collect an array of points to determine the best center and zoom level
    				for(var i = 0; i < places.length; i++) {
    					var point = new YGeoPoint(places[i].centroid.latitude,places[i].centroid.longitude);
    					geopoints.push(point);
    					var newMarker = new YMarker(point);
    				    newMarker.addAutoExpand(places[i].name);	//on hover display the name of the marker
    				    mzYMap.addOverlay(newMarker);
    				}
    				var zac = mzYMap.getBestZoomAndCenter(geopoints);
    				mzYMap.drawZoomAndCenter(zac.YGeoPoint,16);		//set zoom to min as results are inconsistent
    			}
    		}
    		
    		setTimeout("mzAddMap()", 750);
    	

    Create the mapZING window

    If the previous PHP script determines that there are no places found then an alert to this effect is displayed to the user. If places are found, then the mapZING window is a injected into the current page's DOM, the Yahoo! map is rendered within the window and a drag-n-drop handler is attached to the window. Resizing is handled via a simple function that changes the size of the window and the map. The window is closed by removing the corresponding elements from the DOM.

    		/*
    		if the error condition variable !false then an alert is raised, else
    		creates the mapZING structure of nested DIVs
    		injects the final structure into the DOM at the end of the body element
    		*/
    		function mzAddMap() {
    			if (noPlacesFound) {
    				alert("The Yahoo! Placemaker service couldn't find any places on this page. 
    				Likely reasons are:\n\n* There really is no location info on this page\n* 
    				The page is too long\n* Some other type of error (sorry)\n\n")
    			} else {
    				var mapZingOuterElement = document.createElement("div");
    					mapZingOuterElement.id = "mapZING";
    					mapZingOuterElement.setAttribute("class","yui-reset");
    					
    				var mzInnerElement = document.createElement("div");
    					mzInnerElement.id = "mzInner";
    					mzInnerElement.setAttribute("class","yui-reset");
    					
    				var mzHeaderElement = document.createElement("div");
    					mzHeaderElement.id = "mzHeader";
    					mzHeaderElement.setAttribute("class","yui-reset");
    					mzHeaderElement.innerHTML = ".larger mapmapZING";
    					mzInnerElement.appendChild(mzHeaderElement);
    					
    				var mzYMapElement = document.createElement("div");
    					mzYMapElement.id = "mzYMap";
    					mzYMapElement.setAttribute("class","yui-reset");		
    					mzInnerElement.appendChild(mzYMapElement);
    					
    					mapZingOuterElement.appendChild(mzInnerElement);
    					
    				document.body.appendChild(mapZingOuterElement);
    				mzStartMap();								//use the Y! Maps API to load a map
    				mzDragDrop = new YAHOO.util.DD("mzInner");	//use YUI to make the elements draggable
    			}
    		}
    		
    		/* 
    		removes the primary DIV and all of it's children from the DOM
    		*/
    		function mzRemoveMap() {
    			var mapZingDiv = document.getElementById("mapZING");
    			mapZingDiv.parentNode.removeChild(mapZingDiv);
    		}
    		
    		var mzMapSize = "small";
    		
    		/* 
    		toggles the window between a small and large size
    		*/
    		function mzResizeMap() {
    			var mzInnerElement = document.getElementById("mzInner");
    			var mzYMapElement = document.getElementById("mzYMap");
    			var mzHeaderElement = document.getElementById("mzHeader");
    			
    			var mzMapLarge = new YSize(800,600);
    			var mzMapSmall = new YSize(400,300);
    			
    			if (mzMapSize == "small") { 		//map is small, so go big
    				mzInnerElement.style.width = "808px";
    				mzInnerElement.style.height = "633px";
    						
    				mzYMap.resizeTo(mzMapLarge);	//native Yahoo! Maps API function
    				mzHeaderElement.getElementsByTagName("a")[1].innerHTML = "smaller map";
    				mzMapSize = "large";
    				
    			} else { 							//map is big, so go small
    				mzInnerElement.style.width = "408px";
    				mzInnerElement.style.height = "333px";
    					
    				mzYMap.resizeTo(mzMapSmall);
    				mzHeaderElement.getElementsByTagName("a")[1].innerHTML = "larger map";
    				mzMapSize = "small";
    				
    			}
    		}