//a position within the world
function Position(latitude, longitude) {
	this.latitude = latitude;
	this.longitude = longitude;
	//is this position the same as another?
	this.equals = function(position) {
		return ((this.latitude == position.latitude) && (this.longitude == position.longitude));
	}
}

//a person's username and (optional) name
function User(username, name) {
	this.username = username;
	if (!name) {
		this.name = username;
	} else {
		this.name = name;
	}
}

//a city in the world
function City(id, name, country, position) {
	this.id = id;
	this.name = name;
	this.country = country;
	this.position = position;
}

//an icon that can be drawn to the screen
function MapIcon(user, city, focus) { 
	//the names of the people the icon represents
	this.user = user;
	//whether the icon should have the focus highlight
	this.focus = focus;
	//the location of the icon
	this.city = city;
}

//create the path from a list of positions. Each position should be specified as another argument
function Path() {
	this.positions = Path.arguments;
}

//the map class
function AnywhereverMap(container) {
	//compiled icons to display
	var compiledIcons = new Array();
	//raw icons to display
	var rawIcons = new Array(); 
	//paths to display
	var paths = new Array();
	//create the zoom control
	var zoom = new ZoomControl(5);
	//various components of the map interface
	var container = setupContainer(document.getElementById(container));
	//width and height of the container
	var grid = new MapGrid(container);
	var mapCaption = new MapCaption();
	//pixel offsets for the map position
	var offset = new Point(0, 0);
	//add the map controls
	var buttonsLeft = 20;
	var buttonsTop = container.offsetHeight - 109;
	addButton('left', buttonsLeft + 1, buttonsTop + 33, 20, 23, scrollLeft);
	addButton('right', buttonsLeft + 46, buttonsTop + 33, 20, 23, scrollRight);
	addButton('up', buttonsLeft + 22, buttonsTop + 12, 23, 20, scrollUp);
	addButton('down', buttonsLeft + 22, buttonsTop + 57, 23, 20, scrollDown);
	addButton('center', buttonsLeft + 23, buttonsTop + 34, 21, 21, zoomToIcons);
	addButton('in', buttonsLeft, buttonsTop - 1, 21, 35, zoomIn);
	addButton('out', buttonsLeft, buttonsTop + 55, 21, 35, zoomOut);
	grid.loadMapData();
	
	//add a button to the map, with the given image source, coordinates and events
	function addButton(src, _left, _top, _width, _height, onClick) {
		//filenames of inactive and active buttons
		var defaultSource = 'maps/controls/default/' + src + '.png';
		var overSource = 'maps/controls/over/' + src + '.png';
		var pressSource = 'maps/controls/pressed/' + src + '.png';
		var button = document.createElement('img');
		with (button.style) {
			position = 'absolute';
			left = _left + 'px';
			top = _top + 'px';
			width = _width + 'px';
			height = _height + 'px';
			zIndex = 10000000;
			backgroundRepeat = 'no-repeat';
			cursor = 'pointer';
		}
		button.src = 'maps/mask.gif';
		setPngBackground(button, defaultSource, false);
		//preload the other images
		var preloadOver = document.createElement('img');
		preloadOver.src = overSource;
		var preloadPress = document.createElement('img');
		preloadOver.src = overSource;
		//setup event handlers
		button.onmouseover = function() {
			setPngBackground(button, overSource, false);
		}
		button.onmouseout = function() {
			setPngBackground(button, defaultSource, false);
		}
		button.onmousedown = function() {
			setPngBackground(button, pressSource, false);
		}
		button.onmouseup = function() {
			setPngBackground(button, overSource, false);
		}
		button.onclick = onClick;
		container.appendChild(button);
	}
	
	//a grid of map data
	function MapGrid(container) {
		//some constants
		var GRID_SIZE = 135;
		//create the div to hold the grid
		var mapDiv = document.createElement('div');
		//set up the map div
		with (mapDiv.style) {
			position = 'absolute';
			top = '0px';
			left = '0px';
			right = '0px';
			bottom = '0px';
			backgroundImage = 'url(maps/mask.gif)'
			zIndex = 0;
		}
		//a small map to show the current area of view
		var miniMap = document.createElement('div');
		with (miniMap.style) {
			position = 'absolute';
			width = '150px';
			height = '75px';
			right = '10px';
			bottom = '10px';
			border = '1px black solid';
			zIndex = 10000000;
			overflow = 'hidden';
		}
		setPngBackground(miniMap, 'maps/miniMap.png', false);
		container.appendChild(miniMap);
		//used to highlight the current area on the miniMap
		var currentArea = new Array();
		for (n = 0; n < 2; n++) {
			currentArea[n] = document.createElement('div');
			with (currentArea[n].style) {
				position = 'absolute';
				border = '1px black solid';
				zIndex = 10000000;
				lineHeight = '0px';
			}
			setPngBackground(currentArea[n], 'maps/select.png', true);
			miniMap.appendChild(currentArea[n]);
		}
		container.appendChild(mapDiv);
		//number of tiles needed
		var gridWidth;
		var gridHeight;
		ensureCapacity();
		//make the map stretchy
		window.onresize = function() {
			ensureCapacity();
			setInteractionLayerBounds();
			grid.loadMapData();
		}
		function ensureCapacity() {
			//the grid extends one grid square to either side of the visible window to allow for buffering
			gridWidth = Math.ceil(container.offsetWidth / GRID_SIZE) + 2;
			gridHeight = Math.ceil(container.offsetHeight / GRID_SIZE) + 2;	 
			//iterate through all the grids, creating them
			var gridsRequired = gridWidth * gridHeight - mapDiv.childNodes.length;
			for(n = 0; n < gridsRequired; n++) {
				var image = document.createElement('div');
				image.style.position = 'absolute';
				image.style.width = GRID_SIZE + 'px';
				image.style.height = GRID_SIZE + 'px';
				image.setSource = function(left, top) {
					//don't fetch invalid urls
					if ((top < 0) || (top >= zoom.mapHeight)) {
						this.style.backgroundImage = '';
						return;
					}
					//wrap the map along the x axis. After all, the world is a sphere...
					if ((left + GRID_SIZE) <= 0) left += zoom.mapWidth;
					if (left >= zoom.mapWidth) {
						left -= zoom.mapWidth;
					}
					//this is just plain wierd... i have to do the check twice in order for it to work.  A bug in the Firefox JavaScript engine I feel...
					if (left == zoom.mapWidth) {
						left -= zoom.mapWidth;
					}
					var url = 'url(maps/world'+ zoom.ppd + '/' + Math.round(left) + '&' + Math.round(top) + '.jpg)';
					if (this.style.backgroundImage != url) {
						this.style.backgroundImage = url;
					}
				}
				image.style.zIndex = 0;
				image.style.backgroundRepeat = 'no-repeat';
				mapDiv.appendChild(image);
			}
		}
		//the layer that protects the map elements from accidental drag n' drops
		var interactionLayer = document.createElement('div');
		with (interactionLayer.style) {
			position = 'absolute';
			backgroundImage = 'url(maps/mask.gif)';
		}
		setInteractionLayerBounds();
		function setInteractionLayerBounds() {
			with (interactionLayer.style) {
				left = '0px'
				top = '0px';
				width = container.offsetWidth + 'px';
				height = container.offsetHeight + 'px';
			}
		}
		//used for drag n' drop
		var drag = false;
		var mouseX = 0;
		var mouseY = 0;
		interactionLayer.onmousedown = function(event) {
			if (!event) event = window.event;
			drag = true;
			interactionLayer.style.cursor = 'move';
			//save the mouse coordinates
			mouseX = event.screenX;
			mouseY = event.screenY;
		}
		interactionLayer.onmousemove = function(event) {
			if (drag) {
				if (!event) event = window.event;
				offset.translate(mouseX - event.screenX, mouseY - event.screenY);
				mouseX = event.screenX;
				mouseY = event.screenY;
				grid.loadMapData();
			}
		}
		interactionLayer.onmouseup = function() {
			drag = false;
			interactionLayer.style.cursor = 'default';
		}
		if (interactionLayer.captureEvents) interactionLayer.captureEvents(Event.MOUSEOUT);
		interactionLayer.onmouseout = interactionLayer.onmouseup;
		container.appendChild(interactionLayer);
		//load all the map data into the image grid
		this.loadMapData = function() {
			//this is the pixel coordinates from which to start reading map data from the server
			var gridStart = new Point(
				(Math.floor(offset.x / GRID_SIZE) * GRID_SIZE) - GRID_SIZE,
				(Math.floor(offset.y / GRID_SIZE) * GRID_SIZE) - GRID_SIZE
			)
			//this is the fine adjustment to do smooth scrolling
			var miniOffset = new Point(
				-offset.x + gridStart.x + GRID_SIZE,
				-offset.y + gridStart.y + GRID_SIZE
			);
			var n = 0;
			for (x = 0; x < gridWidth; x++) {
				for (y = 0; y < gridHeight; y++) {
					var image = mapDiv.childNodes[n];
					image.style.left = ((x-1) * GRID_SIZE) + miniOffset.x + 'px';
					image.style.top = ((y-1) * GRID_SIZE) + miniOffset.y + 'px';
					image.setSource((x * GRID_SIZE) + gridStart.x, (y * GRID_SIZE) + gridStart.y);
					n++;
				}
			}
			//draw all the icons
			for (index in compiledIcons) {
				compiledIcons[index].draw();
			}			
			//draw all the paths
			for (index in paths) {
				 paths[index].draw();
			}			
			//draw the miniMap if required
			if (zoom.zoom != zoom.levels - 1) {
				miniMap.style.visibility = 'visible';
				var currentPosition = offset.toPosition();
				var newLeft = Math.round((currentPosition.longitude + 180) * 0.4166);
				var newTop = Math.round((90 - currentPosition.latitude) * 0.4166) + 'px';
				var newWidth = Math.round(zoom.windowWidth * 0.4166) + 'px';
				var newHeight = Math.round(zoom.windowHeight * 0.4166) + 'px';
				with (currentArea[0].style) {
					left = newLeft + 'px';
					top = newTop;
					width = newWidth;
					height = newHeight;
				}
				with (currentArea[1].style) {
					left = newLeft - 150 + 'px';
					top = newTop;
					width = newWidth;
					height = newHeight;
				}
			} else {
				miniMap.style.visibility = 'hidden';
			}
		}
	}
	
	//a collection of lines creates from an array of positions
	function CompiledPath(path) {
		var positions = path.positions;
		//use the top left corner of the div as an anchor
		var pathAnchor = positions[0];
		var path = new Array();
		//create the divs to hold the lines
		for (n = 0; n < 2; n++) {
			path[n] = document.createElement('div');
			with (path[n].style) {
				position = 'absolute';
				width = '0px';
				height = '0px';
				zIndex = 14;
				overflow = 'visible';
			}
			container.appendChild(path[n])
			//translate the positions into pixels
			var points = new Array();
			var pathOffset = zoom.convert(pathAnchor);
			for (m = 0; m < positions.length; m++) {
				var tempPoint = zoom.convert(positions[m]);
				points.push(new Point(tempPoint.x - pathOffset.x, tempPoint.y - pathOffset.y));
			}
			//create the lines
			for (m = 0; m < points.length - 1; m++) {
				drawLine(points[m], points[m+1], path[n]);
			}
		}
		//draw all the contained lines
		this.draw = function() {
			var coords = zoom.convert(pathAnchor);
			coords.translate(-offset.x, -offset.y);
			path[0].style.left = coords.x + 'px';
			path[0].style.top = coords.y + 'px';
			path[1].style.left = (coords.x - zoom.mapWidth) + 'px';
			path[1].style.top = coords.y + 'px';
		}
		//remove myself
		this.remove = function() {
			container.removeChild(path[0]);
			container.removeChild(path[1]);
		}
		//update myself due to a zoom change by spreading the path points around
		this.update = function() {
			for (p = 0; p < 2; p++) {
				for (n = 0; n < path[p].childNodes.length; n++) {
					with (path[p].childNodes[n]) {
						style.left = defaultLeft * zoom.magnification + 'px';
						style.top = defaultTop * zoom.magnification + 'px';
						style.borderTop = zoom.pathWidth + 'px #FFFF99 solid';
						style.width = zoom.pathWidth + 'px';
					}
				}
			}
		}
		this.update();
		//specify start and end as Points, and it will draw a line between the two on the given path div
		function drawLine(start, end, div) {
			//collect the pixel bounds of the canvas
			var _lineWidth = end.x - start.x;
			var _lineHeight = end.y - start.y;
			//draw the line
			var aspect = _lineWidth / _lineHeight;
			//which is the smallest increment?
			if (Math.abs(_lineWidth) > Math.abs(_lineHeight)) {
				var change = _lineWidth / Math.abs(_lineWidth);
				for (x = 0; x < (_lineWidth * change); x += 0.5) {
					var xc = (x * change);
					var y = xc / aspect;
					setPixel(xc + start.x, y + start.y, div);
				}
			} else {
				var change = _lineHeight / Math.abs(_lineHeight);
				for (y = 0; y < (_lineHeight * change); y += 0.5) {
					var yc = (y * change);
					var x = aspect * yc;
					setPixel(x + start.x, yc + start.y, div);
				}
			}
			//add a colored pixel to the specified div
			function setPixel(x, y, on) {
				var pixel = document.createElement('div');
				with (pixel.style) {
					position = 'absolute';
					zIndex = 14;
				}
				pixel.defaultLeft = x;
				pixel.defaultTop = y;
				on.appendChild(pixel);
			}
		}
	}
	
	//setup the conainer div
	function setupContainer(container) {
		if (!container) {
			alert('Error: You must supply a valid container div');
			return;
		}
		with (container.style) {
			overflow = 'hidden';
			if (position != 'absolute') position = 'relative';
			//sort out sizing	 
			zIndex = 0;
		}
		return container;
	}
	
	//a caption for the map
	function MapCaption() {
		var mapCaption = document.createElement('div');
		with (mapCaption.style) {
			position = 'absolute';
			left = '7px';
			top = '8px';
			cursor = 'pointer';
			fontFamily = 'sans-serif';
			border = '1px black solid';
			padding = '5px 5px 5px 5px';
			visibility = 'hidden';
			width = '180px';
			zIndex = 10000000;
		}
		//another fix for stupid internet explorer...
		var captionBack = document.createElement('div');
		with (captionBack.style) {
			position = 'absolute';
			visibility = 'hidden';
			left = '7px';
			top = '8px';
			zIndex = 9999990;
		}
		setPngBackground(captionBack, 'maps/captionBack.png', true);
		container.appendChild(captionBack);
		//create a header for the map caption
		function createHeader(title) {
			var header = document.createElement('h2');
			with (header.style) {
				color = 'white';
				display = 'block';
				fontSize = '13px';
				fontWeight = 'normal';
				margin = '0px';
				background = 'none';
				padding = '0px';
			}
			header.appendChild(document.createTextNode(title));
			return header;
		}
		//create a paragraph element to hold data
		function createParagraph() {
			var paragraph = document.createElement('p');
			with (paragraph.style) {
				color = '#FFFF99';
				fontSize = '13px';
				margin = '0px 0px 5px 0px';
				textAlign = 'left';
			}
			return paragraph;
		}
		//create a hyperlink
		function createLink(href, message) {
			var link = document.createElement('a');
			with (link.style) {
				color = '#FFFF99';
				textDecoration = 'none';
			}
			link.onmouseover = function() {
				this.style.color = '#66CC99';
			}
			link.onmouseout = function() {
				this.style.color = '#FFFF99';
			}
			link.href = href;
			link.appendChild(document.createTextNode(message));
			return link;
		}
		//when the caption is clicked, it dissappears again
		mapCaption.onclick = function() {
			mapCaption.style.visibility = 'hidden';
			captionBack.style.visibility = 'hidden';
		}
		container.appendChild(mapCaption);
		//set the caption data
		this.setCaption = function(city, country, users) {
			while (mapCaption.firstChild) {
				mapCaption.removeChild(mapCaption.firstChild);
			}
			mapCaption.appendChild(createHeader('City:'));
			var cityName = mapCaption.appendChild(createParagraph());
			mapCaption.appendChild(createHeader('Country:'));
			var countryName = mapCaption.appendChild(createParagraph());
			countryName.appendChild(document.createTextNode(''));
			//cityName.appendChild(createLink('?page=cityReview', city));
			cityName.appendChild(document.createTextNode(city)); //just for now, while the location page is not ready
			//set the country name
			countryName.firstChild.data = country;
			//add some new contents, if any are present
			if (users.length > 0) {
				mapCaption.appendChild(createHeader('People:'));
				var people = mapCaption.appendChild(createParagraph());
				for (index in users) {
					people.appendChild(createLink('myHomepage.php?user=' + users[index].username, users[index].name));
					people.appendChild(document.createTextNode(', '));
				}
				people.removeChild(people.lastChild);
			}
			//make myself visible
			captionBack.style.height = mapCaption.offsetHeight + 'px';
			captionBack.style.width = mapCaption.offsetWidth + 'px';
			mapCaption.style.visibility = 'visible';
			captionBack.style.visibility = 'visible';
		}
	}
	
	//a class that calculates the various parameters needed for scaling the map image
	function ZoomControl(zoom) {
		var ppdValues = [0.016667, 0.033333, 0.066667, 0.133333, 0.266667, 0.533333];
		this.levels = ppdValues.length;
		//set the zoom level manually, returning true on success
		this.setZoom = function(zoom) {
			if ((zoom >= 0) && (zoom < ppdValues.length)) {
				this.zoom = Math.min(Math.max(zoom, 0), ppdValues.length - 1);
				this.ppd = ppdValues[this.zoom];
				this.mapWidth = Math.round(360 / this.ppd);
				this.mapHeight = Math.round(this.mapWidth / 2);
				this.windowWidth = this.ppd * container.offsetWidth;
				this.windowHeight = this.ppd * container.offsetHeight;
				//used for updating paths
				this.magnification = Math.pow(2, ppdValues.length - this.zoom - 1);
				this.pathWidth = Math.round(((ppdValues.length - 1) - this.zoom) / (ppdValues.length - 1)) + 1;
				//update the CompiledPaths
				for (index in paths) {
					paths[index].update();
				}
				return true;
			} else {
				return false;
			}
		}
		//create a new pixel coordinate from a position
		this.convert = function(position) {
			return new Point(
				(position.longitude + 180) / this.ppd,
				(90 - position.latitude) / this.ppd
			);
		}
		//set the zoom to accomodate the given height and width in degreed
		this.zoomToFit = function(width, height) {
			//calculate exact required degrees per pixel.
			var exactPpd = Math.max(width / container.offsetWidth, 
									-height / container.offsetHeight);
			//calculate the most appropriate map resolution.
			for (index in ppdValues) {
				var ppdIndex = index;
				if (exactPpd < ppdValues[index]) {
					break;
				}
			}
			this.setZoom(ppdIndex);
		}
		this.setZoom(zoom);
	}
	
	//a pixel coordinate on the map
	function Point(x, y) {
		this.x = Math.round(x);
		this.y = Math.round(y);
		//translate the point by a certain number of pixels, wrapping to the x axis as required
		this.translate = function(x, y) {
			this.x += Math.round(x);
			this.y += Math.round(y);
			if (this.x <= 0) this.x += zoom.mapWidth;
			if (this.x >= zoom.mapWidth) this.x -= zoom.mapWidth;
		}
		//use the given ZoomControl to translate the pixel coordinates back into a latitude longitude reference
		this.toPosition = function() {
			return new Position (
				-(this.y * zoom.ppd) + 90,
				(this.x * zoom.ppd) - 180
			)
		}
		//used for debugge
		this.toString = function() {
			return 'Point: x=' + this.x + ' y=' + this.y;
		}
	}
	
	//an icon that shall be drawn to the screen
	function CompiledIcon(icon) {
		//the names of the people the icon represents
		var users = new Array();
		if (icon.user != null) {
			users.push(icon.user);
		}
		//the city the icon is on
		var city = icon.city;
		this.position = city.position;
		//whether the icon represents a group of people
		var multiple = false;
		//whether the icon should have the focus highlight
		var focus = icon.focus;
		//create the image of the icon
		var image = createImage(iconSource(), 20, 34);
		image.style.zIndex = -Math.round(city.position.latitude * 10000 ) + 1100000;
		image.useMap = '#iconMask' + city.id;
		var shadow = createImage('maps/shadow.png', 26, 20);
		shadow.style.zIndex = 15;
		container.appendChild(image);
		container.appendChild(shadow);
		var map = setupMask();
		//draw myself to the map
		this.draw = function() {
			var coords = zoom.convert(city.position);
			coords.translate(-offset.x, -offset.y);
			image.style.left = coords.x - 10 + 'px';
			image.style.top = coords.y - 34 + 'px';
			shadow.style.left = coords.x + 'px';
			shadow.style.top = coords.y - 20 + 'px';
		}
		//if the given mapIcon coincides with this icon, merge it in and return true
		this.attemptMerge = function(mapIcon) {
			if (city.position.equals(mapIcon.city.position)) {
				if (mapIcon.user != null) {
					users.push(mapIcon.user);
					multiple = true;
				}
				if (mapIcon.focus) {
					focus = true;
				}
				setPngBackground(image, iconSource(), false);
				return true;
			} else {
				return false;
			}
		}
		//remove myself from the map
		this.remove = function() {
			container.removeChild(image);
			container.removeChild(shadow);
			container.removeChild(map);
		}
		//a function to create a reliable, cross-browser transparent PNG
		function createImage(source, width, height) {
			var image;	
			image = document.createElement('img');
			setPngBackground(image, source, false);
			image.style.position = 'absolute';
			image.src = 'maps/mask.gif';
			image.width = width;
			image.height = height;
			image.style.border = 'none';
			return image;
		}
		//get the filename for the appropriate image icon
		function iconSource() {
			return 'maps/' + multiple + '&' + focus + '.png';
		}
		//set up the image map mask for detecting icon clicks
		function setupMask() {
			var map = document.createElement('map');
			with (map) {
				name = 'iconMask' + city.id;
				id = 'iconMask' + city.id;
			}
			var area = document.createElement('area');
			with (area) {
				shape = 'circle';
				coords = '10, 10, 10';
				href = 'javascript:void(0)';
			}
			area.onclick = function() {
				mapCaption.setCaption(city.name, city.country, users);
			}
			map.appendChild(area);
			container.appendChild(map);
		}
		return map;
	}
	//sets the background image of an element to a translucent png
	function setPngBackground(element, pngSource, stretch) {
		if (element.style.filter == null) {
			element.style.background = "url('" + pngSource + "')";
		} else {
			element.style.filter = 'progid:DXImageTransform.Microsoft.AlphaImageLoader(src="' + pngSource + (stretch? '",sizingMethod=scale)' : '",sizingMethod=crop)');
		}
	}
	
	
	//set the top left of the viewport to a specific position
	function setViewport(position) {
		offset = zoom.convert(position);
		grid.loadMapData();
	}
	//center the viewport on a position
	function centerViewport(position) {
		var point = zoom.convert(position);
		point.translate(-container.offsetWidth / 2, -container.offsetHeight / 2);
		offset = point;
	}
	//set the zoom level
	function setZoom(zoomLevel) {
		var temp = new Point(offset.x, offset.y);
		temp.translate(container.offsetWidth / 2, container.offsetHeight / 2);
		var currentPosition = temp.toPosition();
		if (zoom.setZoom(zoomLevel)) {
			centerViewport(currentPosition);
			return true;
		} else {
			return false;
		}
	}
	//add an icon to the map
	function addIcon(icon) {
		rawIcons.push(icon);
		//search for other icons at the coordinate
		for (index in compiledIcons) {
			if (compiledIcons[index].attemptMerge(icon)) return;
		}
		//if no other icons were found at the coordinate, then compile a new one
		var compiledIcon = new CompiledIcon(icon)
		compiledIcons.push(compiledIcon);
	}
	//remove an icon from the map, specified by username
	function removeIcon(username) {
		//wipe all compiled icons
		for (index in compiledIcons) {
			compiledIcons[index].remove();
		}
		compiledIcons = new Array();
		//recompile icons
		var oldIcons = rawIcons;
		rawIcons = new Array();
		for (index in oldIcons) {
			if (oldIcons[index].user.username != username) {
				addIcon(oldIcons[index]);
			}
		}
	}
	
	//here are the public methods, nice and pretty like...
	this.setViewport = function(position) {
		offset = zoom.convert(position);
		grid.loadMapData();
	}
	this.centerViewport = function(position) {
		centerViewport();
		grid.loadMapData();
	}
	this.setZoom = function(level) {
		if (setZoom(level)) grid.loadMapData();
	}	 
	this.addIcon = function(icon) {
		addIcon(icon);
		grid.loadMapData();
	}
	this.removeIcon = function(icon) {
		removeIcon(icon);
		grid.loadMapData();
	}
	this.addPath = function(path) {
		paths.push(new CompiledPath(path));
		grid.loadMapData();
	}
	this.zoomToIcons = zoomToIcons;
	
	//control functions used by built-in controls
	function translateViewport(changeX, changeY) {
		offset.translate(changeX, changeY);
		grid.loadMapData();
	}
	function scrollRight () {
		translateViewport(container.offsetWidth / 8, 0);
	}
	function scrollLeft() {
		translateViewport(-container.offsetWidth / 8, 0);
	}
	function scrollUp() {
		translateViewport(0, -container.offsetHeight / 8);
	}
	function scrollDown() {
		translateViewport(0, container.offsetHeight / 8);
	}
	function zoomIn() {
		if (setZoom(zoom.zoom - 1)) grid.loadMapData();
	}
	function zoomOut() {
		if (setZoom(zoom.zoom + 1)) grid.loadMapData();
	}
	function zoomToIcons() {
		if (compiledIcons.length > 0) {
			var latitudes = new Array();
			var longitudes = new Array();
			for (index in compiledIcons) {
				longitudes.push(compiledIcons[index].position.longitude);
				latitudes.push(compiledIcons[index].position.latitude);
			}
			//a function sorting an array into ascending order
			function sortNumber(a, b) {
				return a - b;
			}
			//sort into ascending order
			latitudes.sort(sortNumber);
			longitudes.sort(sortNumber);
			//extract min and max values
			var bottom = latitudes[0];
			var top = latitudes[latitudes.length - 1];
			var left = longitudes[0];
			var right = longitudes[longitudes.length - 1];
			zoom.zoomToFit(right - left, bottom - top);
			top += (20 * zoom.ppd);
			zoom.zoomToFit(right - left, bottom - top);
			top -= ((top - bottom) - zoom.windowHeight) / 2;
			left += ((right - left) - zoom.windowWidth) / 2;
			setViewport(new Position(top, left));
		} else {
			setZoom(zoom.levels - 1);
			offset = new Point(0, 0);
		}
		grid.loadMapData();
	}
}