function relationshipMap(){
	return {
		optionDefaults: {
			'predefinedLocations': {},
			'updateVisibleMarkersCallback': null
		},
		
		map: null,	//reference to Google Map
		options: null,	//options
		itemsArray: {}, //object contaning all map items using id as key, used as quick reference for relationships
		cluster: null,	//reference to clustering object
		polylines: [],	//array of polylines
		_tmpMarkersArray: [], //temporary array to hold markers before adding them to the map
		ajaxRequest: null,
		activeInfoWindow: null,

		onMapMarkerMouseOut: function(mapItem, itemData) {
			mapItem.infoWindow.remove();
			mapItem.infoWindow = null;
			this.resetMap();
			this.updateVisiblityStatuses(true);
			$('#map-details').fadeOut();
		},

		getPopupDescription: function( itemData ) {

			var objectGroupType = {};
			var itemDataCount = itemData.length;
			for(i=0; i < itemDataCount; i++){
				var objectData = itemData[i];
				if(!objectGroupType[objectData['type']]){
					objectGroupType[objectData['type']] = 1;
				}else{
					objectGroupType[objectData['type']]++;
				}
			}
		

			var tooltipStrs = {};
			for(var type in objectGroupType){
				var friendlyTypeStr = '';
				switch(type){
					case 'programme':
						friendlyTypeStr = 'Project';
						break;
					case 'cast':
						friendlyTypeStr = 'Playwright';
						break;
					case 'play':
						friendlyTypeStr = 'Play';
						break;
				}
				var count = objectGroupType[type];
				friendlyTypeStr = count + ' ' + friendlyTypeStr;
				if(count != 1){
					friendlyTypeStr = friendlyTypeStr + 's';
				}
				tooltipStrs[type] = friendlyTypeStr;
			}
			var orderedTooltipStrs = new Array();

			if(tooltipStrs['play']){
				orderedTooltipStrs.push(tooltipStrs['play']);
			}
			if(tooltipStrs['programme']){
				orderedTooltipStrs.push(tooltipStrs['programme']);
			}
			if(tooltipStrs['cast']){
				orderedTooltipStrs.push(tooltipStrs['cast']);
			}
			
			return orderedTooltipStrs.join(', ') + "...";

		},

		showPopup: function(mapItem, itemData, itemLocation){

			//if itemData isn't an array make it one as
			//our popup code expects one
			if(!(itemData instanceof Array)){
				itemData = new Array(itemData);
			}

			var popupHtml = '<div id="relationship-popup">';
			
			if(itemLocation != undefined){
				var location = itemLocation+':';
			} else {
				var location = '';
			}
			
			popupHtml = popupHtml + '<div class="header">'+location+'<select><option>'+this.getPopupDescription(itemData)+'</option>';
			var oldType = '';
			var friendlyTypes = {play:'Plays', programme:'Projects',cast:'Playwrights'};
			var friendlyItemData = {};
			
			var itemDataCount = itemData.length;
			for(var i = 0; i < itemDataCount; i++){
				var key = itemData[i]['type'];
				if(!friendlyItemData[key] || !friendlyItemData[key].length){
					friendlyItemData[key] = [];
				}
				friendlyItemData[key].push(itemData[i]);
			}
			
			for(var key in friendlyTypes){
				if(friendlyItemData[key]){
					popupHtml = popupHtml + '<optgroup label="'+friendlyTypes[key]+'">';
				
					for(var i = friendlyItemData[key].length - 1; i>= 0; i--){
						var data = friendlyItemData[key][i];
						popupHtml = popupHtml + '<option value="'+data['id']+'">' + data['title'] + '</option>';
					}
					popupHtml = popupHtml + '</optgroup>';
				}
			}
			popupHtml = popupHtml + '</select></div><div class="content"></div>';
			popupHtml = popupHtml + '</div>';
			
			mapItem.openExtInfoWindow(this.map, "relationship-popup-window", popupHtml, {beakOffset: 2});
			
			var thisObj = this;
			var infoWindow = this.map.getExtInfoWindow();

			$('#relationship-popup select').change(function() {
				this.ajaxRequest && this.ajaxRequest.abort();
				thisObj.showPopupInformation($(this).val(),infoWindow);
			});
			mapItem.infoWindow = infoWindow;

			$('#relationship-popup-window li').hover(
				function(){ //over
					thisObj.resetMap();
					$(this).addClass('active'); //add hover class for IE
					var id = $(this).attr('id').replace('map-item-', '');
					var mapObject = thisObj.itemsArray[id];
					thisObj.activateItem(mapObject);
				}
			)
			
			if(data['link']){
				$('#relationship-popup-window li').click(function(){
					window.location = data['link'];
				})
			}
			this.map.getExtInfoWindow().resize();
		},
		
		showPopupInformation: function( value, infoWindow ) {
			var thisObj = this;
			var itemId = value;
			thisObj.resetMap();
			this.ajaxRequest = $.get('/map/details/', {id: value}, function(data) { thisObj.onPopupInformationLoaded(data,itemId) } );
		},
		
		onPopupInformationLoaded: function( data, itemId ) {
			$('#map-details').html(data).fadeIn();
			var mapObject = this.itemsArray[itemId];
			this.activateItem(mapObject);

			this.map.getExtInfoWindow().resize();
		},
		
		updateVisiblityStatuses: function(){
			this.resetMap();
			if(this.options.updateVisibleMarkersCallback){
				for(var i in this.itemsArray){
					if(!this.options.updateVisibleMarkersCallback(this.itemsArray[i])){
						//hide marker
						this.itemsArray[i]['_marker'].hide();
						this.itemsArray[i]['_marker']._toggledOff = true;
					}
				}
				this.cluster.refresh(true);
			}
		},
		
		panMapTo: function(lat, lng, zoom){
			this.map.setCenter(new GLatLng(lat, lng), zoom);
		},
		
		resetMap: function(){
			this.removePolylines();
			this.showAllMarkers();
			$('#relationship-popup-window li').removeClass('active');
			this.ajaxRequest && this.ajaxRequest.abort();
		},
		
		showAllMarkers: function(){
			for(var i in this.itemsArray){
				this.itemsArray[i]['_marker'].show();
				this.itemsArray[i]['_marker']._toggledOff = false;
			}
			this.cluster.refresh(true);
		},
		
		removePolylines: function(){
			if(this.polylines && this.polylines.length){
				for(var i in this.polylines){
					this.map.removeOverlay(this.polylines[i]);
				}
			}
			this.polylines = null;
		},
		
		activateItem: function(mapObject){
			
			//start by getting the other objects related to this one
			var relatedItems = mapObject['relationships'];
			if(relatedItems.length){
				//hide anything not related to this one
				for(var i in this.itemsArray){
					var item = this.itemsArray[i];
					if(item['id'] != mapObject['id']){
						//hide object
						item['_marker'].hide();
						item['_marker']._toggledOff = true;
					}
				}
				
				
				//draw polylines to other related objects
				this.polylines = new Array();
				for(var i in relatedItems){
					var relatedItem = this.itemsArray[relatedItems[i]];
					if(relatedItem){
						var polyline = new GPolyline([
							new GLatLng(mapObject['latitude'], mapObject['longitude']),
							new GLatLng(relatedItem['latitude'], relatedItem['longitude'])
						], '#ff0000', 5);
						this.map.addOverlay(polyline);
						this.polylines.push(polyline);
						relatedItem['_marker'].show();
						relatedItem['_marker']._toggledOff = false;
					}
				}
				this.cluster.refresh(true);
			}
		},
	
		selectMapItem: function(mapItem, location){
		//	this.map.panTo(mapItem.getLatLng());
			this.showPopup(mapItem, mapItem.objectData, location);
		},
	
		addMapItem: function(obj){
			var icon = new GIcon();
			icon.iconSize = new GSize(30, 36);
			switch(obj['type']){
				case 'play':
					icon.image = "/mmlib/images/royal-court/pin-production.png";
					break;
				case 'programme':
					icon.image = "/mmlib/images/royal-court/pin-project.png";
					break;
				case 'cast':
					icon.image = "/mmlib/images/royal-court/pin-people.png";
					break;
			}
			
			icon.iconAnchor = new GPoint(15, 36);
			icon.infoWindowAnchor=new GPoint(16, 36);
			var point = new GLatLng(obj['latitude'], obj['longitude']);
			var marker = new GMarker(point, {icon:icon});
			
			if(obj['location']){
				var tooltip = new Tooltip(marker, obj['location'], 4);
				marker.tooltip = tooltip;
				this.map.addOverlay(tooltip);
				
				GEvent.addListener(marker, 'mouseover', function(){
					this.tooltip.show();
				});
				GEvent.addListener(marker, 'mouseout', function(){
					this.tooltip.hide();
				});
				GEvent.addListener(marker, 'click', function(){
					this.tooltip.hide();
				});
			}

			marker.objectData = obj;
			marker._toggledOff = false;
			this._tmpMarkersArray.push(marker);
			obj['_marker'] = marker;
			this.itemsArray[obj['id']] = obj;
			
			var thisObj = this;
			GEvent.addListener(marker, 'click', function(){
				thisObj.selectMapItem(marker, obj['location']);
			});
		},
		
		commitMapItems: function(){
			var multipleIcon = new GIcon();
			multipleIcon.image = "/mmlib/images/royal-court/pin-multiple.png";
			multipleIcon.iconSize = new GSize(30, 36);
			multipleIcon.iconAnchor=new GPoint(15, 36);
			multipleIcon.infoWindowAnchor=new GPoint(16, 36);
			multipleIcon.shadow='http://www.google.com/intl/en_us/mapfiles/arrowshadow.png';
			multipleIcon.shadowSize=new GSize(30, 36);
			
			var thisObj = this;
			/*clusterMarkerTitle: ' ',*/
			this.cluster = new ClusterMarker(this.map, { 
				markers:this._tmpMarkersArray,
				clusterMarkerIcon: multipleIcon,
				clusterMarkerClick: function(args){
					var mapItemDataArray = new Array();
					for(var i = args.clusteredMarkers.length - 1; i>= 0; i-- ){
						mapItemDataArray.push(args.clusteredMarkers[i].objectData);
					}
					thisObj.showPopup(args.clusterMarker, mapItemDataArray, args.clusterLocation);
				}
			});
			this.cluster.fitMapToMarkers();
		},
	
		init: function(map, options){
			this.map = map;
			this.options = $.extend({}, this.optionDefaults, options);
			var thisObj = this;
			
			$('.close').live('click', function(){
				thisObj.map.closeExtInfoWindow();
				return false;
			})
			
			
			GEvent.addListener(this.map, 'extinfowindowclose', 
				function(){ 
					thisObj.resetMap();
					thisObj.updateVisiblityStatuses(true);
					$('#map-details').fadeOut().html('');
				}
			);
		}
	}
}


