// ==UserScript==
// @name          Geograph Images
// @version       2.2
// @description   (v2.2) Adds nearby Geograph images to a cache page. New version for Geocaching.com June 2010 update. 
// @namespace     http://benchmarks.org.uk/geographimages/
// @copyright     2010+, Gary Player <dev@benchmarks.org.uk>
// @license       Released under the GPL http://www.gnu.org/copyleft/gpl.html
// @attribution   Automatic Update code and cross browser LD_* functions kindly supplied by Lil Devil http://www.lildevil.org/greasemonkey/
// @include       http://*.geocaching.com/seek/cache*
// @include       https://*.geocaching.com/seek/cache*
// ==/UserScript==

// This is a Greasemonkey user script, see http://greasemonkey.mozdev.org/

(function()
{
// script details for version check
var SCRIPT_NAME = 'Geographimages';
var SCRIPT_VERSION = '2.2';

// items set by menu options - opera users will need to manually change these
var imagesPerRow = 3;
var defaultRowsToShow = 1;

// constants
var mapId = 'ctl00_ContentBody_uxlrgMap';
var lnkConvId = 'ctl00_ContentBody_lnkConversions';
var fancyboxRelId = "fancybox[geothumbs]"; // allows images to be treated as part of geothumbs script
var geographDivTableId = 'geographDivTableId';
var closedImg = '../images/arrow_close.gif';
var openedImg = '../images/arrow_open.gif';
var searchDist = 2; // 2km search distance
var searchMaxImages = 30; // max number of images to return
var fancyBoxAnchorClass = 'geothumbs_fbac'; //anchor class to use for all anchors to images - fancybox is applied to these
var style = '#geographImagesTable { width:100%; border-collapse: collapse; border:1px solid #D7D7D7; table-layout: auto; }' +
			'.geographImageCell { width:120px; border: 0px none; padding: 8px; }' +
			'.geographImage { display: block; margin-left: auto; margin-right: auto; }' +
			'.geographTitleCell { width:1400px; padding-right:8px; }' +
			'.geographTitleLink { text-decoration: none; }' +
			'.geographDescription { font-size: smaller; }' +
			'.geographDescriptionLink { color: #666666; }' +
			'#geographHeadingLinkId { text-decoration: none; font-weight: bold; color: black !important; }' +
			'#geographMoreId { font-size: smaller; position: relative; bottom:0.5pt;}' +
			'#geographHeadingRightId { float:right; position: relative; bottom:0px;}' +
			'#geographIconLinkId { float:left; } ' +
			'#geographLicenseId { color: #666666; }' +
			'#caption #description { font-weight: normal; }' +
			'#imageData #imageDetails { width: 80%; }' +
			'.geothumbs_fbac { position:relative; }' +
			'.geothumbs_fbac #title { width:80%; text-align:left; }'+
			'.geothumbs_fbac #links { float:right; height:2em; margin-left:auto; padding-left:2em; }'+
			'.geothumbs_fbac #imageCount { position:absolute; right:0px; bottom:0px; padding-left:4em; }'+
			'.geothumbs_fbac #description { font-weight: normal; }';
var backgroundDarkness = 25; // Fancybox background darkness (in percent)
var backgroundColour = '#000'; // Fancybox background colour


// variables
var extWindow; // window access
var numDescriptionCharsPerImage = 0; // calculated number of description chars to display
var geographLink = "";
var geographHomePage = "";
var geographPhotoPageUrl = "";
var geographGridrefPageUrl = "";
var geographApiUrl = "";
var lat = 0;
var lon = 0;
var geographData; // holds data returned by geograph
var unshownIndex = 0; // the first unshown geograph image 
var collapsed = false; // image table initially shown


// Lil Devil's cross browser compatible Get and Set functions
function LD_setCookie(key, val, life) {
	if (!key) { return; }
	if (!life) { life = 31536000; }
	var expires = new Date().getTime() + (1000 * life);
	document.cookie = escape(key) + '=' + escape(val) +
		';expires=' + new Date(expires).toGMTString() + ';path=/';
}

function LD_getCookie(key) {
	var cookieJar = document.cookie.split('; ');
	for (var i=0, len=cookieJar.length; i<len; i++ ) {
		var oneCookie = cookieJar[i].split('=');
		if (oneCookie[0] == escape(key)) {
			return unescape(oneCookie[1]);
		}
	}
	return null;
}

function LD_setValue(key, val, username) {
	if (username !== undefined) {
		if (username) {		// if username is supplied, it must not be blank (i.e. not logged in)
			key += '_' + encodeName(username);
		} else {
			return;
		}
	}
	if ((typeof(GM_setValue) != 'undefined') &&
		(GM_setValue.toString().indexOf('not supported') < 0) &&
		!window.opera) {	// don't use Opera compatibility script because it probably uses cookies
			GM_setValue(key, val);
			return;
	}
	val = (typeof(val)).toString().substring(0,1) + val;
	try {
		localStorage.setItem(key, val);
	} catch (err) {
		// if we get here, either localStorage doesn't exist, or we got a Security Error using it
		LD_setCookie(key, val);
	}
}

function LD_getValue(key, defVal, username) {
	if (username !== undefined) {
		if (username) {		// if username is supplied, it must not be blank (i.e. not logged in)
			key += '_' + encodeName(username);
		} else {
			return;
		}
	}
	if ((typeof(GM_setValue) != 'undefined') &&
		(GM_setValue.toString().indexOf('not supported') < 0) &&
		!window.opera) {	// don't use Opera compatibility script because it probably uses cookies
			return GM_getValue(key, defVal);
	}
	var val;
	try {
		val = localStorage.getItem(key);
	} catch (err) {
		val = LD_getCookie(key);
	}
	if (typeof(val) != 'string' || val.length === 0) {
		return defVal;
	}
	var type = val.substr(0,1);
		 val = val.substr(1);
	switch (type) {
		case 'b':
			return (val == 'true');
		case 'n':
			return Number(val);
		default:
			return val;
	}
}

// Lil Devils cross browser logger
function LD_log(str) {
	if ((typeof(GM_log) != 'undefined') && !window.opera) {
		GM_log(str);
		return;
	}
	try {
		console.log(str);
	} catch (err) {}
}

// Lil Devils cross browser compatible xmlhttpRequest
function LD_xmlhttpRequest(request) {
	if ((typeof(GM_xmlhttpRequest) != 'undefined') && !window.opera) {
		GM_xmlhttpRequest(request);
	} else {
		var xmlhttp = new XMLHttpRequest();
		xmlhttp.onreadystatechange = function() {
			var responseState = {	responseXML		: '',
									responseText	: '',
									readyState		: xmlhttp.readyState,
									responseHeaders	: '',
									status			: 0,
									statusText		: ''
								};
			if (xmlhttp.readyState == 4) {
				responseState = {	responseXML		: xmlhttp.responseXML,
									responseText	: xmlhttp.responseText,
									readyState		: xmlhttp.readyState,
									responseHeaders	: xmlhttp.getAllResponseHeaders(),
									status			: xmlhttp.status,
									statusText		: xmlhttp.statusText
								};
			}

			if (request['onreadystatechange']) {
				request['onreadystatechange'](responseState);
			}
			if (xmlhttp.readyState == 4) {
				if (request['onload'] && xmlhttp.status >= 200 && xmlhttp.status < 300) {
					request['onload'](responseState);
				}
				if (request['onerror'] && (xmlhttp.status < 200 || xmlhttp.status >= 300)) {
					request['onerror'](responseState);
				}
			}
		};
		try {
			//cannot do cross domain
			xmlhttp.open(request.method, request.url);
		} catch(e) {
			if(request['onerror']) {
				//simulate a real error
				request['onerror']({responseXML		: '',
									responseText	: '',
									readyState		: 4,
									responseHeaders	: '',
									status			: 403,
									statusText		: 'Forbidden'
									});
			}
			return;
		}
		if (request.headers) {
			for (var prop in request.headers) {
				xmlhttp.setRequestHeader(prop, request.headers[prop]);
			}
		}
		xmlhttp.send((typeof(request.data) != 'undefined') ? request.data : null);
	}
}

// Lil Devils Script update checker
function CheckForUpdate(scriptName, scriptVersion)
{
	var VERSION_LIST_XDR = 'http://benchmarks.org.uk/greasemonkey/versions.txt';
	var VERSION_LIST_NO_XDR = 'http://www.geocaching.com/seek/log.aspx?LUID=026bbac0-130d-49bb-9eee-626b3f873e96';

	try {
		var forceCheck = false;
		var checkURL = VERSION_LIST_XDR;
		if (window.opera) {
			// Opera doesn't support cross-domain xmlhttpRequests so use a URL on geocaching.com
			checkURL = VERSION_LIST_NO_XDR;
		}

		// avoid a flood of dialogs e.g. when opening a browser with multiple tabs open
		var now = new Date().getTime();
		var DOSpreventionTime = 2 * 60 * 1000;	// two minutes
		var abbrev = scriptName.replace(/[^A-Z]/g, '');
		var lastStart = LD_getValue(abbrev + '_Update_Start', null);
		LD_setValue(abbrev + '_Update_Start', now.toString());
		if (!forceCheck && lastStart && (now - lastStart) < DOSpreventionTime) { return; }

		// time to check yet?
		var oneDay = 24 * 60 * 60 * 1000;
		var lastChecked = LD_getValue(abbrev + '_Update_Last', null);
		var checkDays = LD_getValue(abbrev + '_Update_Days', 1);
		if (!forceCheck && lastChecked && (now - lastChecked) < (oneDay * checkDays)) {	return;	}

		LD_xmlhttpRequest({
			method: 'GET',
			url: checkURL,
			headers: { 'User-Agent' : scriptName + ' v' + scriptVersion + ' auto updater' },
			onload: function(result) {
				var matches,
					regex = new RegExp('[\\s\\>]' + scriptName +
										'\\s+v([\\d\\.]+)\\s+(\\d+)\\s+(.+?)[\\<\\s]', 'i');
				if (!(matches = regex.exec(result.responseText))) {
					LD_log(scriptName + ': Updater: response unrecognized');
					return;
				}

				var theOtherVersion = matches[1];
				LD_setValue(abbrev + '_Update_Days', +matches[2]);
				var theOtherURL = matches[3];

				if (theOtherVersion.replace(/\./g, '') <= scriptVersion.replace(/\./g, '')) { return; } // no updates or older version
				if (theOtherURL.indexOf('http') !== 0) { theOtherURL = 'http://' + theOtherURL; }

				if (window.confirm(	'The Greasemonkey script "' + scriptName +
									'" has been updated.\n\n' +
									'The new version is ' + theOtherVersion +
									'\nYou are currently using version ' + scriptVersion +
									'\n\nClick OK for instructions on how to upgrade.')) {
					document.location = theOtherURL;	// open in same window to avoid popup blockers
				}
			}
		});
		LD_setValue(abbrev + '_Update_Last', new Date().getTime().toString());
	}
	catch (err) { LD_log(scriptName + ': ' + err);}
}

// sets the geograph urls for this lat lon 
function setGeographUrls()
{
	if (inBritainAndIreland())
	{
		geographLink = 'geograph.org.uk';
		geographHomePage = 'http://www.geograph.org.uk/';
		geographApiUrl = 'http://api.geograph.org.uk/api';
		geographPhotoPageUrl = 'http://www.geograph.org.uk/photo/';
		geographGridrefPageUrl = 'http://www.geograph.org.uk/gridref/';
	}
	else if (inChannelIslands())
	{
		geographLink = 'channel-islands.geographs.org';
		geographHomePage = 'http://channel-islands.geographs.org/';
		geographApiUrl = 'http://channel-islands.geographs.org/api';
		geographPhotoPageUrl = 'http://channel-islands.geographs.org/photo/';
		geographGridrefPageUrl = 'http://channel-islands.geographs.org/gridref/';
	}
	else if (inGermany())
	{
		geographLink = 'geo-en.hlipp.de';
		geographHomePage = 'http://geo-en.hlipp.de/';
		geographApiUrl = 'http://geo-en.hlipp.de/restapi.php/api';
		geographPhotoPageUrl = 'http://geo-en.hlipp.de/photo/';
		geographGridrefPageUrl = 'http://geo-en.hlipp.de/gridref/';
	}
}

// add css styles
function addGlobalStyle(css) {
    var head = document.getElementsByTagName('head')[0];
    if (head)
    {
		var style = document.createElement('style');
		style.type = 'text/css';
		style.innerHTML = css;
		head.appendChild(style);
    }
}

// insert newNode after referenceNode
function insertAfter(referenceNode, newNode)
{
	referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling);
}

// returns a string shortened to the nearest word boundary less than the specified length
function shortenString(str,n)
{
	  if(str.length > n)
	  {
		  var s = str.substr(0, n);
		  var ls = s.lastIndexOf(' ');
		  str = s.substr(0,ls);
	  }
	  return str;
}

// convert awkward html entities
function convertHtmlEntity(h)
{
	if (h)
	{
		// convert &#8470; into No
		h = h.replace(/&#8470;/g,'No ');
	}
	return h;
}

// tidies a geograph description by:
// 1. removing newlines
// 2. converting geograph links to html links
// 3. converting http strings to html links
function tidyDescription(d)
{
	if (d)
	{
		// convert /r /n to space
		d = d.replace(/(\\[rn])+/g,' ');
		
		// convert http strings to an html link
		d = d.replace(/(https?:\/\/?[a-z0-9_$!&%?,#@'\/.*+;:=~-]+)/gi,'<a class="geographDescriptionLink" href="$1" target="_blank">link</a>');
		
		// convert geograph format photo links to html links
		d = d.replace(/\[+(\d+)\]+/g,'<a class="geographDescriptionLink" href="'+geographPhotoPageUrl+'$1" target="_blank">link</a>');

		// convert geograph format gridref links to html links
		d = d.replace(/\[+([a-z]{1,3}\d+)\]+/gi,'<a class="geographDescriptionLink" href="'+geographGridrefPageUrl+'$1" target="_blank">link</a>');
		
		// ensure trailing '.'
		d = d.replace(/\.\s*$/, '') + '.';
	}
	return d;
}

// cycles number of images per row between 2 and 4
function imagesPerRowMenuAction()
{
	imagesPerRow++;
	if (imagesPerRow > 4)
		imagesPerRow = 2;
	GM_setValue('imagesPerRow', imagesPerRow);
	document.location.reload();
}

// cycles default number of rows to show between 1 and 4
function defaultRowsToShowMenuAction()
{
	defaultRowsToShow++;
	if (defaultRowsToShow > 4)
		defaultRowsToShow = 1;
	GM_setValue('defaultRowsToShow', defaultRowsToShow);
	document.location.reload();
}

// set up user defined settings
function getUserDefinedSettings()
{
	imagesPerRow = GM_getValue('imagesPerRow', imagesPerRow);
	defaultRowsToShow = GM_getValue('defaultRowsToShow', defaultRowsToShow);
}

// register menus
function registerMenus()
{
	GM_registerMenuCommand('Geograph Images - Images per row : ' + imagesPerRow, imagesPerRowMenuAction);
	GM_registerMenuCommand('Geograph Images - Default rows to show : ' + defaultRowsToShow, defaultRowsToShowMenuAction);
}

// get lat lon from page
function getLatLon()
{
	// get lat lon coordinates
	var lnkConv = document.getElementById(lnkConvId);
	if (lnkConv)
	{
		var ll = lnkConv.href.match(/-?\d+\.?\d*/g);
		lat = ll[0];
		lon = ll[1];
	}
}

// returns true if lat lon is in britain or ireland
function inBritainAndIreland()
{
	if (lat > 49.7 && lat < 61 && lon > -12.2 && lon < 2)
		return true;
	else
		return false;
}

// returns true if lat lon is in the channel islands
function inChannelIslands()
{
	if (lat > 49.1 && lat < 49.8 && lon > -2.8 && lon < 1.8)
	{
		return true;
	}
	else
		return false;
}

//returns true if lat lon is in germany
function inGermany()
{
	if (lat > 47.25 && lat < 55.1 && lon > 5.8 && lon < 15.05)
		return true;
	else
		return false;
}

// returns true if geograph covers the lat lon
function isCoveredByGeograph()
{
	if (inBritainAndIreland() || inChannelIslands() || inGermany())
		return true;
	else
		return false;
}

// get geograph api url - distance in km
function getApiUrl(distance, maxImages)
{
	return geographApiUrl+'/Latlong/'+distance+'km/'+lat+','+lon+'/geocache?output=json&perpage='+maxImages+'&callback=?';
}

// title formatter for fancybox
function formatFancyBoxTitle(title, currentArray, currentIndex, currentOpts) {
	return '<div class="'+fancyBoxAnchorClass+'">' + (title && title.length ? '<b>' + title + '</b>' : '' ) + 
	       '<div id="imageCount">Image ' + (currentIndex + 1) + ' of ' + currentArray.length + '</div>';
}

// creates title for anchor tag
function createAnchorTagTitle(title, description, owner, geographPage, takenDate, largePictureLink)
{
	var descPart;
	var lbStyleViewLogLink;
	var taken;
	var title;
	
	if (description == '')
		descPart = '&nbsp;';
	else
		descPart = description;

	if (geographPage == '')
	{
		lbStyleViewLogLink = '';
		taken = '';
	}
	else
	{
		lbStyleViewLogLink = '<a href="' + geographPage + '" target="_blank">Geograph Page</a>&nbsp;';
		taken = ' on ' + takenDate;
	}
	
	// lightbox style
	title = '<div id="links">' +
				lbStyleViewLogLink +
				"<a href=\"javascript:pp(\'" + largePictureLink + "\');\">Print Picture</a>" +
			'</div>' +
	
			'<div id="title">' +
				'"' + title + '"' +
				' by ' + owner + taken + '<br>' +
				'<span id="description">' + descPart + '</span>' + 
			'</div>';
	return title;
}

// add a geograph image to the specified cell
function addImage(cell, item)
{
	var thumbnail = item.thumbnail;
	var image = item.image;
	var title = item.title;
 	var description = item.comment;
	var author = item.realname;
	var gridimage_id = item.gridimage_id;
	var imagetaken = item.imagetaken;
	var distance = item.dist;
	
	// create anchor and set the fancybox rel id
	var a = document.createElement('a');
	a.rel = fancyboxRelId;
	
	// format the distance info
	var distanceStr = Math.floor(distance) + 'm away';

	// create the author license
	var licence='&copy; ' + author + ' and licensed for reuse under this ' +
				'<a id="geographLicenseId" href="http://creativecommons.org/licenses/by-sa/2.0/" target="_blank">Licence</a>.';

	// initialise fancybox and image title tag descriptions 
	var fancyboxDescription = '<span id="description">';
	var imageTitleDescription = '';

	// add description if there is one
	if (description && description != '')
	{
		// create a tidied description for the fancybox
		var tDesc = tidyDescription(description);

		// add distance to tidied description
		fancyboxDescription = fancyboxDescription + tDesc + '<br>';
		
		// remove any trailing '. ' from image pop-up description
		imageTitleDescription = ' - ' + description.replace(/\.\s*$/,'');
	}
	
	// add distance and licence (only for fancybox)
	fancyboxDescription = fancyboxDescription + licence +  '<br>' + distanceStr + '</span>';
	imageTitleDescription = imageTitleDescription + ' - ' + distanceStr;
	
	// link to geograph page
	var geographPage = geographPhotoPageUrl + gridimage_id;

	// create anchor title - this is used by fancybox
	a.title = createAnchorTagTitle(title, fancyboxDescription, author, geographPage, imagetaken, image);
	a.href = image;

	// create thumbnail image
	var thumb = new Image();
	thumb.src = thumbnail;
	thumb.className = 'geographImage';
	
	// create the image tooltip
	thumb.title = title + ' by ' + author + imageTitleDescription;
	thumb.alt = title;	
	
	// add thumbnail to anchor
	a.appendChild(thumb);
	
	// add anchor to cell
	cell.appendChild(a);

	// set same anchor class so we can apply fancybox to all image anchors
	a.className = fancyBoxAnchorClass;

	// add our lightbox format to all anchors/images
	unsafeWindow.$('.'+fancyBoxAnchorClass).fancybox({
		'titlePosition' 	: 'inside',
		'overlayOpacity'	: (backgroundDarkness / 100),
		'overlayColor'		: backgroundColour,
		'titleFormat'		: formatFancyBoxTitle
	});

}

// add a geograph title to the specified cell
function addTitle(cell, item)
{
	var title = item.title;
	var description = item.comment;
	var geographPageUrl = geographPhotoPageUrl + item.gridimage_id;
	
	// create new anchor for text link to geograph page
	var textAnchor = document.createElement('a');
	textAnchor.className = 'geographTitleLink';
	textAnchor.href = geographPageUrl;
	textAnchor.target = '_blank';
	textAnchor.title = title; // sets tooltip
	textAnchor.textContent = title;
	
	// add anchor to cell
	cell.appendChild(textAnchor);// add description if required

	// add description if one exists
	if (description && (description != ""))
	{
		// shorten description length to allowed amount
		var cDesc = shortenString(description, numDescriptionCharsPerImage);
		
		// add continuation string if shortened
		if (cDesc.length != description.length)
		{
			cDesc = cDesc + " ...";
		}
		
		// tidy description
		var tDesc = tidyDescription(cDesc);

		// add description to title
		cell.appendChild(document.createElement('br'));
		var span = document.createElement('span');

		span.className = 'geographDescription';
		cell.appendChild(span);
		span.innerHTML = tDesc;
	}
}

// add geograph details (image and title) to the specified row
function addGeographImageDetails(r, item)
{
	// add new image cell
	var cell = r.insertCell(-1);
	cell.className = 'geographImageCell';
	addImage(cell, item);
	
	// add a new title cell
	cell = r.insertCell(-1);
	cell.className = 'geographTitleCell';
	addTitle(cell, item);
}

// add row of images to table
function addRowOfImages()
{
	var t = document.getElementById('geographImagesTable');
	
	// add table row
	var r = t.insertRow(-1);
	r.className = 'geographImagesRow';
	
	// set number of images to load
	var toShow = geographData.length - unshownIndex;

	if (toShow > imagesPerRow)
		numToLoad = imagesPerRow;
	else
		numToLoad = toShow;
	
	var i = unshownIndex;
	var endIndex = i + numToLoad;
	while ( i < endIndex )
	{
		// get geograph metadata
		var item = geographData[i];

		// and add the details to the current row
		addGeographImageDetails(r, item);
		i++;
	}
	
	unshownIndex = i;

	// hide 'more' link when all images are shown
	if (unshownIndex >= geographData.length)
	{
		var gmid = document.getElementById('geographMoreId');
		gmid.style.visibility = 'hidden';
	}
}

// sort function to sort by distance field
function distanceSortFn(a, b)
{
	return (a.dist - b.dist);
}

// constants for distance calculation
var DEG_TO_RAD = 0.017453292519943295769236907684886;
var RADIUS = 6371000;

// returns distance in metres between two coordinates
function distanceBetweenCoords(lat1, lon1, lat2, lon2)
{
	var dLat = (lat2-lat1) * DEG_TO_RAD;
	var dLon = (lon2-lon1) * DEG_TO_RAD; 
	var a = Math.sin(dLat/2) * Math.sin(dLat/2) +
	        Math.cos(lat1 * DEG_TO_RAD) * Math.cos(lat2 * DEG_TO_RAD) * 
	        Math.sin(dLon/2) * Math.sin(dLon/2); 
	var d = RADIUS * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); 
	return d;
}

// processes the data returned by geograph
function processGeographResponse(d)
{
	// set local attribute for access by all
	geographData = d;

	// process results (if there are any)
	var i = geographData.length;
	if (i)
	{
		while (--i >= 0)
		{
			// add distance field to data
			var item = geographData[i];
			var llat = item.wgs84_lat;
			var llon = item.wgs84_long;
			item.dist = distanceBetweenCoords(lat, lon, llat, llon);

			// change awkward html entities
			item.title = convertHtmlEntity(item.title);
			item.comment = convertHtmlEntity(item.comment);
			
			// convert dates to dd-mm-yyyy
			item.imagetaken = item.imagetaken.replace(/(\d+)\-(\d+)\-(\d+)/,"$3-$2-$1");
		}

		// sort results by distance from origin
		geographData.sort(distanceSortFn);

		// create geograph div section
		var gDiv = document.createElement('div');
		gDiv.id = 'geographDiv';

		// create header span
		var hdrSpan = document.createElement('span');
		hdrSpan.id = 'geographHeader';
		hdrSpan.innerHTML = '<br>' +
		'<a id="geographHeadingLinkId" href="' + geographHomePage + '" title="Go to ' + geographLink + '" target="_blank">Geograph Images</a> ' +
		'<span id="geographMoreId"> (<a href="#" onClick="addRowOfImages();return false;">more</a>)</span>' +
		'<span id="geographHeadingRightId">' +
			'<a id="geographIconLinkId" href="' + geographHomePage + '" title="Go to ' + geographLink + '" target="_blank"><img src="' + geographHomePage + 'favicon.ico"></a>' +
		'</span>';		
		
		
		// add header span to geograph div
		gDiv.appendChild(hdrSpan);
		
		// add new table div id and sub div (required by scriptaculous Effect) for collapsible section
		var tDiv = document.createElement('div');
		tDiv.id = geographDivTableId;
		gDiv.appendChild(tDiv);
		var div = document.createElement('div');
		tDiv.appendChild(div);
		
		// create images table
		var t = document.createElement('table');
		t.id = 'geographImagesTable';
		var tb = document.createElement('tbody');
		t.appendChild(tb);

		// add table to sub div
		div.appendChild(t);

		// position geograph div before map (this should exist as we're only called if we're logged in and have coordinates)
		var mapDiv = document.getElementById(mapId);
		if (mapDiv)
		{
			// this is run after the page javascript has already run
			// and enclosed the div in an anchor.  move mapDiv up to this
			// anchor so we insert before the anchor
			if (mapDiv.parentNode.nodeName == 'A')
			{
				mapDiv = mapDiv.parentNode;
			}

			mapDiv.parentNode.insertBefore(gDiv, mapDiv);

			// show the initial rows of images
			var i = defaultRowsToShow;
			while (i-- > 0)
			{
				addRowOfImages();
			}
		}
	}
}

// determines how many description characters can be displayed
function calcNumDescChars()
{
	var w = window.innerWidth; // page width
	var i = imagesPerRow;
	var r = (w/1024);
	var c = Math.floor(120 * (-2*r*r+9*r-6) * (0.2*i*i-1.6*i+3.4));
	if (c > 450)
	{
		c = 450;
	}
	else if (c < 10)
	{
		c = 10;
	}
		
	return c;
}

///////////////////////////// main /////////////////////////////////

//check for new version
CheckForUpdate(SCRIPT_NAME, SCRIPT_VERSION);

// get cache location
getLatLon();

if (isCoveredByGeograph())
{
	if (window.navigator.userAgent.match('Firefox'))
	{
		// for firefox
		extWindow = unsafeWindow;
		
		// get settings and register menus - only available for firefox
		getUserDefinedSettings();
		registerMenus();
	}
	else
	{
		// for opera
		extWindow = window;
	}

	// calculate max size of description
	numDescriptionCharsPerImage = calcNumDescChars();
	
	// set the required server and paths
	setGeographUrls();
	
	// set styles
	addGlobalStyle(style);

	// allow calls from web page to our greasemonkey script
	extWindow.addRowOfImages = function(){addRowOfImages();};

	// create link for geograph request
	var apiLink = getApiUrl(searchDist, searchMaxImages);

	// get geograph json data
	extWindow.jQuery.getJSON(apiLink, function(d){processGeographResponse(d);});
}

})();
