function BlinkMap() {

	this.canvasId;
	this.journeyType;
	this.map = null;
	this.showControls = false;
	this.legs = new Array();
	this.legColours = new Array("#0076be", "#00843e", "#462a88", "#d85620", "#96004f");
	this.markers;
	this.bounds;

	this.icon = new GIcon(G_DEFAULT_ICON);
	this.icon.image = "/site/modules/blink-now/media/img/map-icon-blink.png";
	
	/**
	 * Init a map into a DOM element. Pre-instance leg object array for 
	 * expected numbers of legs.
	 */
 	this.init = function(canvasId, numLegs, journeyType) {
 		this.canvasId = canvasId;
		for (var i = 0; i < numLegs; i++) {
			this.legs.push({ });
		}
		this.journeyType = journeyType; 
 	}
 	
 	/**
 	 * Renders this map based on current Leg configuration
 	 */
 	this.render = function() {

 		if (this.map == null) {
 			this.map = new GMap2(document.getElementById(this.canvasId));
 			if (this.showControls) {
 				this.map.setUIToDefault();
 			}
 		}
 		
 		this.map.clearOverlays();
 		this.markers = new Array();
 		
 		// extend bounds to fit legs
 		this.bounds = new GLatLngBounds();
	    for (var i = 0; i < this.legs.length; i++) {
	    	if (this.legs[i].from instanceof GLatLng) { 
	    		this.bounds.extend(this.legs[i].from);
	    	}
	    	
	    	if (this.legs[i].to instanceof GLatLng) {
	    		this.bounds.extend(this.legs[i].to);
	    	}
        } 

	    // center on the computed bounds
	    var zoomOutBy = 1;
	    if (! this.isCompleteLeg(this.legs[0])) {
	    	zoomOutBy = 10;
	    }
	    
	    this.map.setCenter(this.bounds.getCenter(), 
			this.map.getBoundsZoomLevel(this.bounds) - zoomOutBy);

	    // add lettered markers and polyline to join per-leg endpoints
	    
	    var overlays = new Array();
	    
	    var isReturn = this.journeyType == 'Return Trip';
	    var letter = 0; var maxLetter = 0; 
	    var pins = {};
	    
        for (var i = 0; i < this.legs.length; i++) {
        	
        	// do not go above 1 leg for returns
        	if (i > 0 && isReturn) {
        		break;
        	}
        	
        	if (this.legs[i].from instanceof GLatLng) {
        		
        		if (pins[this.legs[i].from_id] === undefined) {
        			pins[this.legs[i].from_id] = maxLetter++;
        		}
        		letter = pins[this.legs[i].from_id];
        		
        		this.addMarker(letter, this.legs[i].from, this.legs[i].from_type);
        	}
        	
        	if (this.legs[i].to instanceof GLatLng) {
        		
        		if (pins[this.legs[i].to_id] === undefined) { 
        			pins[this.legs[i].to_id] = maxLetter++;
        		} 
        		letter = pins[this.legs[i].to_id];
        		
        		this.addMarker(letter, this.legs[i].to, this.legs[i].to_type);
        	}
        	
        	if (this.legs[i].from instanceof GLatLng && 
        		this.legs[i].to instanceof GLatLng) {
        		//if (! $.browser.msie) {
	        		var polyline = new GPolyline([this.legs[i].from, this.legs[i].to], this.legColours[i], 3, 1, { clickable: false, geodesic: true });
	        		this.map.addOverlay(polyline);
        		//}
        	}
        }
 	}
 	
 	/**
 	 * Handy shortcut to render, more semantically correct sometimes
 	 */
 	this.refresh = function() {
 		this.render();
 	}
 	
 	/**
 	 * Sets a full Leg JSON structure at a given index
 	 * @param index The Leg index, e.g. 0
 	 * @param leg Full JSON structure { from: airport_name_id, to: airport_name_id }
 	 */
 	this.setLeg = function(index, leg) {		
 		this.setLegEndpoint(index, "From", leg);
 		this.setLegEndpoint(index, "To", leg);
 		
 	}
 	
 	/**
 	 * Sets a Leg endpoint (airport). The endpoint is set inside the passed JSON
 	 * leg as an airport_name_id. This is turned into a lat/lng by a service call
 	 * @param index The Leg index, e.g. 0
 	 * @param endpoint From or To
 	 * @param leg Partial JSON structure { from: airport_name_id, to: airport_name_id }
 	 */
 	this.setLegEndpoint = function(index, endpoint, leg) { 
 		
 		var instance = this;
 		this.legs[index] = leg;

 		$.getJSON("/blink-now/book/getAirportByNameId", { q: endpoint == "From" ? leg.from : leg.to }, function (response) {
 			if (response.Airport) {
 				var ll = new GLatLng(parseFloat(response.Airport.airport_latitude), parseFloat(response.Airport.airport_longitude));
	 			if (endpoint == "From") { 
	 				leg.from_id = leg.from;
	 				leg.from = ll;
	 				leg.from_type = response.Airport.airport_type;
	 			} else {
	 				leg.to_id = leg.to;
	 				leg.to = ll;
	 				leg.to_type = response.Airport.airport_type;
	 			}	
	 			
	 			instance.refresh();
 			}
 		});
 	}
 	
 	/**
 	 * Return a specific Leg
 	 * @param index 0-based Leg index
 	 * @return the Leg JSON structure 
 	 */
 	this.getLeg = function(index) {
 		if (this.legs[index]) {
 			return this.legs[index];
 		} else {
 			return { from: '', to: '' }
 		}
 	}

 	this.setShowControls = function(b) {
 	 	this.showControls = b;
 	}
 	
 	/**
 	 * Validate if a Leg structure is complete, i.e. from and to are both present
 	 */
 	this.isCompleteLeg = function(leg) {
 		return leg.from && leg.to;
 	}

 	/**
 	 * Does this map have a marker for the geocoord
 	 */
	this.hasMarker = function(latlng) {
		for (var i = 0; i < this.markers.length; i++) { 
			var marker = this.markers[i];
			if (marker.getLatLng().lat() == latlng.lat() && 
				marker.getLatLng().lng() == latlng.lng()) {
				return true;
			}
		}
    	return false;
	}

	/**
	 * Add a lettered icon marker at a geocoord
	 */
	this.addMarker = function(lettOffset, latlng, type) { 
		if (this.hasMarker(latlng)) {
			return false;
		}
		var letter = String.fromCharCode("A".charCodeAt(0) + lettOffset);
		//alert(airport_type);
		var marker = new GMarker(latlng, this.getIcon(letter, type));   
		this.map.addOverlay(marker);
		this.markers.push(marker);
    	return true;
	}

	/**
	 * Return a configured letter icon marker
	 */
	this.getIcon = function(letter, hub) { 
		
			if (letter) { 
				var iconlet = new GIcon(G_DEFAULT_ICON);
				
				//alert(hub);
				
				if (hub != "HUB"){
					iconlet.image = "/site/modules/blink-now/media/img/map-icon-blink-" + letter.toLowerCase() + ".png";
				} else {
					iconlet.image = "/site/modules/blink-now/media/img/map-icon-blink-hub-" + letter.toLowerCase() + ".png";
				}
				return iconlet;
			}
		
		return icon;
	}
	
}
