///////////////////////////////////////////////////////////
// M2I JSON Request Framework
// Copyright (c) 2007 Maptuit Corporation, all rights reserved.
// Last update: July 15, 2009
// Global Settings

// Define the Maptuit environment to use - "beta", "bravo", or "service"
var m2i_env = "service";

// Define the ClientID (configuration name) - provided by Maptuit.
var m2i_clientid = "MAP.affinity";

// Define the ClientID (configuration name) used for RDI truck routing. 
var m2i_clientid_rdi = "xml.fleetnav.affinity";

// Define the parameters of the ActiveLayer used to store route traces - namely, the name of the layer and the class value to use.
var m2i_layername = "affinity.rdiTraces";

// If password (shared secret) authentication is used, provide the password - otherwise leave it as an EMPTY STRING
// If password authentication is used, then the md5.js library must also be loaded.
var m2i_password = "";

// The name of the AM3 instance that this library should interact with.
var m2i_am3name = "MYMAP";

// Default country for geocoding.  Recommended values are "US" or "CA"
var m2i_defaultcountry = "US";

// END OF GLOBAL SETTINGS

// global var where JSON script tags get created and where hotspot canvas gets created
var JSON_scripttag_rdi, JSON_scripttag_poise, JSON_scripttag_geo, m2i_hs, m2i_route_hs, m2i_gsc_hs;
var JSON_scripttag_poi = new Object();

///////////////////////////////////////////////////////////
// Geocoding Functions
///////////////////////////////////////////////////////////

// send a geocode
// req: one or more objects containing the following elements:
//	reverse geocodes: "lon" and "lat" (both required)
//	forward geocodes: country, postal, region, city, street, place
// if more than one geocode is needed per request, pass an array of objects.
function m2i_geocode(req, callback)
{
	var num = 0;
	var jsonreq = m2i_makeJSONURL(callback);
	//jsonreq += "&service=geocode&cmd=geocodeaddresses";
	jsonreq += "&service=location::geocode";
	if (req.constructor == Object)
	{
		if (("lon" in req) && ("lat" in req))
		{
			jsonreq += m2i_geocodeJSONrg(req,num);
		}
		else if (("country" in req) || ("postal" in req) || ("region" in req) || ("city" in req))
		{
			jsonreq += m2i_geocodeJSONfg(req,num);
		}
	}
	else if (req.constructor == Array)
	{
		for (num = 0; num < req.length; num++)
		{
			if (("lon" in req[num]) && ("lat" in req[num]))
			{
				jsonreq += m2i_geocodeJSONrg(req[num],num);
			}
			else if (("country" in req[num]) || ("postal" in req[num]) || ("region" in req[num]) || ("city" in req[num]))
			{
				jsonreq += m2i_geocodeJSONfg(req[num],num);
			}
		}
	}

	JSON_scripttag_geo = new JSONscriptRequest(jsonreq);
	JSON_scripttag_geo.buildScriptTag();
	JSON_scripttag_geo.addScriptTag();
}

///////////////////////////////////////////////////////////
// Geocode Callback Functions
// take the first candidate from the first result and plot it on the map with the proper scale.
// the result should be a city or region-level geocode...
function m2i_geocodeJSONAM3point(data)
{
	var lon,lat,scale;
	var candidate = data.addr[0].cand[0];
	var level = data.addr[0].level;
	var map = eval(m2i_am3name);
	var S = new Object();
	var factor = map.mapHeight;
	if (map.mapWidth < factor) { factor = map.mapWidth; }
	S.Scale = ((candidate.radius*2)*1000) / (factor);
	if (S.Scale < 178) { S.Scale = 178;}
	S.Lon = candidate.lon;
	S.Lat = candidate.lat;
	map.setState(S);
	JSON_scripttag_geo.removeScriptTag();
}


//////////////////////////////////////////////////////////////////////
// POI Functions
//////////////////////////////////////////////////////////////////////

// Given a list of POI IDs, retrieve their details from the Maptuit POI Management Server.
// input is an object with one or two arrays, optionally with fallback geocode elements:
// - array "agipoi" of Affinity POI IDs
// - array "ntpoi" of NavTeq POI IDs.
// callback is the name of a callback function that does something with the results.
function m2i_POIDetailsFromID(request, callback, idx)
{
	var makeMyCallback = function(idx,request,callback)
	{	
		if (!idx) {idx = 0;}
		if (("city" in request) && ("region" in request))
		{
			callback = callback + "('" + request.city + "','" + request.region + "'";
			if (("country" in request) && request.country)
			{
				callback = callback + ",'" + request.country + "',idx,%s)";
			}
			else
			{
				callback = callback + ",'" + m2i_defaultcountry + "',idx,%s)";
			}
		}
		else
		{
			callback = callback + "("+idx+",%s)";
		}
		return callback;
	}

	var reqcallback = makeMyCallback(idx,request,callback);
	var svc = "&service=pointsofinterest::getdetails";
	
	jsonreq = m2i_makeJSONURL(reqcallback) + svc;
	
	var poielem;
	var c = 0;
	var i = 0;
	if ('agipoi' in request)
	{
		for (var i = 0; i < request.agipoi.length; i++)
		{
			poielem = "&pointofinterest"+c+"_ownerid=" + request.agipoi[i];
			poielem += "&pointofinterest"+c+"_ownername=affinity";
			if ((jsonreq.length + poielem.length) < 2048)
			{
				jsonreq += poielem;
			}
			else
			{
				// dispatch this request now
				JSON_scripttag_poi[idx] = new JSONscriptRequest(jsonreq);
				JSON_scripttag_poi[idx].buildScriptTag();
				JSON_scripttag_poi[idx].addScriptTag();
				
				idx = idx + c;
				c = 0;
				reqcallback = makeMyCallback(idx,request,callback);
				jsonreq = m2i_makeJSONURL(reqcallback) + svc;
				poielem = "&pointofinterest"+c+"_ownerid=" + request.agipoi[i];
				poielem += "&pointofinterest"+c+"_ownername=affinity";
				jsonreq += poielem;
			}
			
			c++;
		}
	}
	
	if ('ntpoi' in request)
	{
		for (i = 0; i < request.ntpoi.length; i++)
		{
			poielem = "&pointofinterest"+c+"_ownerid=" + request.ntpoi[i];
			poielem += "&pointofinterest"+c+"_ownername=NavTeq";
			if ((jsonreq.length + poielem.length) < 2048)
			{
				jsonreq += poielem;
			}
			else
			{
				// dispatch this request now
				JSON_scripttag_poi[idx] = new JSONscriptRequest(jsonreq);
				JSON_scripttag_poi[idx].buildScriptTag();
				JSON_scripttag_poi[idx].addScriptTag();
				
				idx = idx + c;
				c = 0;
				reqcallback = makeMyCallback(idx,request,callback);
				jsonreq = m2i_makeJSONURL(reqcallback) + svc;
				poielem = "&pointofinterest"+c+"_ownerid=" + request.ntpoi[i];
				poielem += "&pointofinterest"+c+"_ownername=NavTeq";
				jsonreq += poielem;
			}
			c++;
		}
	}
	
	JSON_scripttag_poi[idx] = new JSONscriptRequest(jsonreq);
	JSON_scripttag_poi[idx].buildScriptTag();
	JSON_scripttag_poi[idx].addScriptTag();
}

// JSON callback function for the above.  Given the ordered array of POIs,
// use CanvasHotspots to draw them on the map and fill in the hover/click balloons.
// Hover balloons will only have the names, click balloons will have full details
// as well as a link to a new page.
function m2i_POIDetailsOnMap()
{
	var data,tempdata,city,region,country,idx;
	// determine the arguments.
	switch(arguments.length)
	{
		case 5:
			city = arguments[0];
			region = arguments[1];
			country = arguments[2];
			idx = arguments[3];
			tempdata = arguments[4];
			break;
		default:
			idx = arguments[0];
			tempdata = arguments[1];
			break;
	}
	var JSONidx = idx;
	data = eval(tempdata);
	var pointarr = new Array();

	// compute map extent, set it
	for (var i = 0; i < data.pointofinterest.length; i++)
	{
		if (("lon" in data.pointofinterest[i]) && (data.pointofinterest[i].lon != "0")) 
		{
			pointarr.push(data.pointofinterest[i].lon + ',' + data.pointofinterest[i].lat);
		}
	}
	
	if (pointarr.length == 0)
	{
		// we didn't find anything.  
		JSON_scripttag_poi[idx].removeScriptTag();
		delete(JSON_scripttag_poi[idx]);
		if (city && region && country)
		{
			m2i_geocode({city:city, region:region, country:country}, 'm2i_geocodeJSONAM3point');
		}
		return;
	}
	
	// invoke Canvas
	var map = eval(m2i_am3name);
	if (!m2i_hs)
	{
		m2i_hs = new HotSpotCanvas('hs_'+m2i_clientid, true);
		map.addCanvas('hs_' + m2i_clientid, m2i_hs, 11);
	}
	var S = new Object();
	var bb = map.computebox(pointarr);
	S.Lon = (bb.left+bb.right)/2;
	S.Lat = (bb.top+bb.bottom)/2;
	S.Scale = map.autoscale(bb);

	map.setState(S);

	// build out our hotspots
	for (i = 0; i < data.pointofinterest.length; i++)
	{
		if (("lon" in data.pointofinterest[i]) && (data.pointofinterest[i].lon != "0"))
		{
			var poi = data.pointofinterest[i];
			var url = m2i_fixurl(poi.url);
			var hoverhtml = '<b>' + poi.name + '</b>';
			var clickhtml = m2i_poiDetailsToHTML(poi);
	
			m2i_hs.CreateBoth(poi.ownername+poi.ownerid, poi.lon, poi.lat, (poi.logourl ? m2i_fixurl(poi.logourl) : 'http://www.trailerlifedirectory.com/images/icons/mapping/camper.gif'), 20, 20, 200, 70,clickhtml,'',100,40,hoverhtml,'', true);
		}
		idx++;
	}
	
	m2i_hs.Redraw();			
	JSON_scripttag_poi[JSONidx].removeScriptTag();
	delete(JSON_scripttag_poi[JSONidx]);
}

////////////////////////////////////////////////////////////////////
// POI Corridor Search
////////////////////////////////////////////////////////////////////
// Search a given ActiveLayer as a corridor against a set of POIs.
// featurename: the name of the feature
// keywords: an array of boolean keyword queries to use
// callback: the name of the callback function to process the output.


function m2i_POICorridorSearch(featurename, radius, keywords, callback)
{
	var jsonreq = m2i_makeJSONURL(callback);
	//jsonreq += "&service=poise&cmd=search";
	jsonreq += "&service=pointsofinterest::search";
	jsonreq += "&detaillevel=full&layernamereadfilter=affinity*";
	jsonreq += "&spinelayer=" + m2i_layername;
	jsonreq += "&spinename=" + featurename;
	jsonreq += "&radius=" + radius;
	jsonreq += "&tolerance=5&scale=0";

	for (var i = 0; i < keywords.length; i++)
	{
		jsonreq += "&query"+i+"_keywordquery=" + keywords[i];
	}

	JSON_scripttag_poise = new JSONscriptRequest(jsonreq);
	JSON_scripttag_poise.buildScriptTag();
	JSON_scripttag_poise.addScriptTag();
}

// search a radius around a given point for POIs specified by keywords.
// point: a string containing "lon,lat"
// radius: the radius around the point to search (in KM;  km = mi * 0.621371192)
// keywords: an array of boolean keyword queries
// callback: the name of the callback function to process the output.
function m2i_POIRadiusSearch(point, radius, keywords, callback)
{
	var jsonreq = m2i_makeJSONURL(callback);
	//jsonreq += "&service=poise&cmd=search";
	jsonreq += "&service=pointsofinterest::search";
	jsonreq += "&detaillevel=full";
	jsonreq += "&radius=" + radius;
	jsonreq += "&tolerance=5&scale=0";
	var ll = point.split(",");
	jsonreq += "&centerlon=" + ll[0];
	jsonreq += "&centerlat=" + ll[1];

	for (var i = 0; i < keywords.length; i++)
	{
		jsonreq += "&query"+i+"_keywordquery=" + keywords[i];
	}

	JSON_scripttag_poise = new JSONscriptRequest(jsonreq);
	JSON_scripttag_poise.buildScriptTag();
	JSON_scripttag_poise.addScriptTag();	
}

////////////////////////////////////////////////////////////////////
// Routing Functions
////////////////////////////////////////////////////////////////////
// Puts route features on AM3 from server.
// tracename is the name of the trace feature stored in ActiveLayers by RDI.
// stops is an ordered array of "lon,lat" points denoting the location of the stops.
function m2i_RouteOnMap(tracename, stops, tracecolor)
{
	var logourl,logow;
	var logoh = 16;
	var pointarr = new Array();
	var map = eval(m2i_am3name);

	for (var i = 0; i < stops.length; i++)
	{
		pointarr.push(stops[i]);
	}
	var S = new Object();
	var bb = map.computebox(pointarr);
	S.Lon = (bb.left+bb.right)/2;
	S.Lat = (bb.top+bb.bottom)/2;
	S.Scale = map.autoscale(bb);
	map.setState(S);

	if (tracecolor)
	{
		CanvasActiveLayer.defineStyle(m2i_layername,'*','*','strokestyle',tracecolor);
	}
	var cal = new CanvasActiveLayer('rtrace', m2i_layername, "", 0, "", tracename, true);
//	var cal = new CanvasActiveLayer('rtrace',m2i_layername,";" + tracename + ";;;;", 0);
	
	map.addCanvas("trace"+m2i_clientid, cal, 9);
	m2i_route_hs = new HotSpotCanvas("stops", true);
	map.addCanvas("stops"+m2i_clientid, m2i_route_hs, 10);

	for (var i = 0; i < stops.length; i++)
	{
		var ll = stops[i].split(",");
		if (i == 0)
		{
			logow = 34;
			logourl = "";
		}
		else if (i == stops.length-1)
		{
			logow = 34;
			logourl = "";
		}
		else
		{
			logow = 16;
			logourl = ""+i+".gif";
		}
		m2i_route_hs.CreateHover("stop"+i, ll[0],ll[1],logourl,34,16,0,0,'','foo');
	}
}

// given an array of address objects, run an RDI request with them, return the
// Direction results, and call m2i_RouteOnMap to draw the markup.
function m2i_RDI(stops, tracename, rthandler,stoptype, stopvalue, tracecolor, optimize)
{
	if (!optimize)
	{
		optimize = false;
	}
	if (!stoptype)
	{
		stoptype = '';
		stopvalue = '';
	}
	
	if (!tracecolor)
	{
		tracecolor = '';
	}
	else
	{
		tracecolor = encodeURIComponent(tracecolor);
	}
	
	var jsonreq = m2i_makeJSONURL("m2i_RDIcallback('" + tracename + "','" + rthandler + "',%s,'" + stoptype + "','" + stopvalue + "','" + tracecolor + "')", true);
	    //jsonreq+= "&service=rdi&cmd=GenerateRDI&units=mi&debug=Detailed&modeoftravel=Truck&clientid=" + m2i_clientid_rdi;
	    jsonreq+= "&service=route::getroute&units=mi&debug=Detailed&modeoftravel=Truck&clientid=" + m2i_clientid_rdi;
	    jsonreq+= "&PointListFeatureName=" + tracename;
	    jsonreq+= "&PointListLayerName=" + m2i_layername;
	    jsonreq+= "&OptimizeStopOrder=" + (optimize ? "Enable" : "Disable");
 
	for (var i = 0; i < stops.length; i++)
	{
		jsonreq += m2i_geocodeJSONfg(stops[i], i, true);
	}
	
	JSON_scripttag_rdi = new JSONscriptRequest(jsonreq);
	JSON_scripttag_rdi.buildScriptTag();
	JSON_scripttag_rdi.addScriptTag();	
}

// JSON callback function that handles the RDI response.
// if RDI request failed, it calls rthandler function with a single object argument with two properties:
// status and errormessage
// if RDI request failed, status == 0 and errormessage == a description of the error.
// Otherwise, it will display the route on the map properly
function m2i_RDIcallback(tracename, rthandler, data, stoptype, stopvalue,tracecolor)
{
	if ((data.status == 0) || (!data.dir))
	{
		// error.  geocoding or route failed.
		data.status = 0;
		if (!data.dir)
		{
			data.errormessage += " [no directions returned.]";
		}
		eval(rthandler+'({status: ' + data.status + ', errormessage:"' + data.errormessage + '"})');
		return;
	}
	
	var map = eval(m2i_am3name);
	// Route trace points
	var pointarr = new Array();
	var tmparr = new Array();
	
	// index of stopids to route trace point index
	var stopid_ptidx = new Array();
	
	// stops and stop information
	//var stops = new Array();
	//var stopinfo = new Array();
	var stopinfo = "";
	
	data.stops = new Array();
	
	var stopid = 0;
	var distcount = 0;
	var stopcount = 1;
	stopvalue = parseInt(stopvalue);
	
	m2i_route_hs = new HotSpotCanvas('hsr_' + m2i_clientid);
	map.addCanvas("hsr_" + m2i_clientid, m2i_route_hs,10);
	
	// read the directions
	for (var i = 0; i < data.dir.length; i++)
	{
		if (data.dir[i].type == "Leg")
		{
			tmparr = data.dir[i].pointlist.split(' ');
			if (pointarr.length == 0)
			{
				stoppoint = tmparr[0].split(',');
				data.stops.push({lon: stoppoint[0], lat: stoppoint[1]});
				stopid_ptidx.push(0);
				
				//stops.push(tmparr[0]);
			}
			stoppoint = tmparr[tmparr.length-1].split(',');
			data.stops.push({lon: stoppoint[0], lat: stoppoint[1]});
			
			//stops.push(tmparr[tmparr.length-1]);
			pointarr = pointarr.concat(tmparr);
			stopid_ptidx.push(pointarr.length-1);
		}
		else if (('fromstopid' in data.dir[i]) || ('tostopid' in data.dir[i]))
		{
			if ('fromstopid' in data.dir[i]) stopid = data.dir[i].fromstopid;
			else stopid = data.dir[i].tostopid;
			
			//stoppoint = stops[stopid].split(',');
			//stopinfo[stopid] = data.dir[i].street + ', ' + data.dir[i].city + ', ' + data.dir[i].region + ', ' + data.dir[i].country;
			
			if (!data.stops[stopid]) data.stops[stopid] = new Object();
			data.stops[stopid].stopid = stopid;
			data.stops[stopid].street = data.dir[i].street;
			data.stops[stopid].city = data.dir[i].city;
			data.stops[stopid].region = data.dir[i].region;
			data.stops[stopid].country = data.dir[i].country;
			data.stops[stopid].type = "route";
		}
	}
	
	// draw route stop markers on the map
	for (i = 0; i < data.stops.length; i++)
	{
		//var ll = stops[i].split(",");
		var stopinfo = data.stops[i].street + ', ' + data.stops[i].city + ', ' + data.stops[i].region + ', ' + data.stops.country;
		m2i_route_hs.CreateBoth('hs'+i,data.stops[i].lon,data.stops[i].lat,
			function(){return m2i_route_hs.CreateIndexedBlob('m2i_hs_square_green')},
			15, 15, 110,30, '<b>' + stopinfo + '</b>', '');
	}
	
	// insert suggested stops into 
	if (stoptype == "m")
	{
		var tdist_mi = data.totaldistance * 0.621371192;
		var step = Math.round((pointarr.length / tdist_mi) * stopvalue);
		for (i = step; i < pointarr.length; i+=step)
		{
			// add to the reverse geocode request
			var ll = pointarr[i].split(",");
			var idx = m2i_findIdx(stopid_ptidx, i);
			data.stops.splice(idx, 0, {lon:ll[0], lat:ll[1], type:"suggest"} );
			stopid_ptidx.splice(idx, 0, i);
			//data.stops.push({lon:ll[0], lat:ll[1]});
		}
	}
	
	var S = new Object();
	var bb = map.computebox(pointarr);
	S.Lon = (bb.left+bb.right)/2;
	S.Lat = (bb.top+bb.bottom)/2;
	S.Scale = map.autoscale(bb);
	map.setState(S);
	
	if (tracecolor != '')
	{
		CanvasActiveLayer.defineStyle(m2i_layername,'*','*','strokestyle',tracecolor);
	}
	
//	var cal = new CanvasActiveLayer('rtrace',m2i_layername,";" + tracename + ";;;;", 0);
	var cal = new CanvasActiveLayer('rtrace', m2i_layername, "", 0, "", tracename,  true);
	map.addCanvas("trace"+m2i_clientid, cal, 9);
	
	var rthandlerfx = eval(rthandler);
	rthandlerfx(data);
	JSON_scripttag_rdi.removeScriptTag();
}

function m2i_clearPOICanvas()
{
	if (m2i_hs)
	{
		var map = eval(m2i_am3name);
		map.deleteCanvas(m2i_hs.id);
		m2i_hs = false;
	}
}

// HotSpot Canvas extensions for returning all hotspots of a particular type
// under the mouse position as returned by AM3;
//
//  colltype: string containing the type of hotspots to look for.  One of
//            'Click','Hover', or 'Both'
//  x: mouse X position as returned by AM3
//  y: mouse Y position as returned by AM3
HotSpotCanvas.prototype.searchPosition = function(colltype,x,y)
{
    var tp = this.MyMap.xy_tray(x, y);
    if (colltype == 'Click')
    {   return this._find_all_hotspots(this.HotSpotClick,tp.x,tp.y);  }
    if (colltype == 'Hover')
    {   return this._find_all_hotspots(this.HotSpotHover,tp.x,tp.y);  }
    if (colltype == 'Both')
    {   return this._find_all_hotspots(this.HotSpotBoth,tp.x,tp.y);  }
};

HotSpotCanvas.prototype._find_all_hotspots = function(collection, x, y) {
    var xc,yc,idxx,idxy;
    var ret_arr = [];
    for (xc in collection)
    {
        idxx = Math.abs(parseInt(xc) - x);
        if (idxx < 12)
        {
            for (yc in collection[xc])
            {
                idxy = Math.abs(parseInt(yc)-y);
                if (idxy < 12)
                {
                    // copy members from collection[xc][yc] into ret_arr.
                    for (var i = 0; i < collection[xc][yc].length; i++)
                    {
                        ret_arr.push(collection[xc][yc][i]);
                    }
                }
            }
        }
    }

    return ret_arr;
};

////////////////////////////////////////////////////////////////////
// Utility Functions - convenience functions used by the code above.
// No user-servicable parts inside.  Consult your owner's manual.
////////////////////////////////////////////////////////////////////
function m2i_findIdx(arr, idx)
{
	for (var i = 0; i < arr.length; i++)
	{
		if ((arr[i] < idx) && (arr[i+1] > idx))
		{
			return i+1;
		}
	}
	// ran off the end of the array
	return i;
}


function m2i_insertStop(stops, newstop)
{
	var closestdist = 99999999999999;
	var closestidx;
	var dist;
	
	if (!("lon" in newstop))
	{
		// reverse geocode it and get back to me
	}
	
	for (var i = 0; i < stops.length; i++)
	{
		if (!("lon" in stops[i]))
		{
			// reverse geocode it and get back to me
		}
		//lon1,lat1,lon2,lat2
		dist = gcircle(stops[i].lon, stops[i].lat, newstop.lon, newstop.lat)
		if (dist < closestdist)
		{
			closestdist = dist;
			closestidx = i;
		}
	}
	
	if (closestidx == 0)
	{
		// edge case - start point
		// put it right after this point
		stops.splice(1,0,newstop);
	}
	else if (closestidx == stops.length-1)
	{
		// edge case - end point
		// put it right before this point
		stops.splice(stops.length-2,0,newstop);
	}
	else
	{
		// compare 
		var d_left = gcircle(stops[closestidx-1].lon, stops[closestidx-1].lat, newstop.lon, newstop.lat);
		var d_right = gcircle(stops[closestidx+1].lon, stops[closestidx+1].lat, newstop.lon, newstop.lat);
		if (d_left < d_right)
		{
			stops.splice(closestidx,0,newstop);
		}
		else
		{
			stops.splice(closestidx+1,0,newstop);
		}
	}

	return stops;
}


function m2i_poiDetailsToHTML(obj)
{
	var html = '<div style="height: 11px; font-size: 11px; overflow: hidden; font-weight: bold; padding: 2px; border-bottom: solid 1px black;">'+ obj.name +'</div><div style="overflow: auto; height: 60px; width: 200px;">';
	if ('displaystreet' in obj)
	{
		html += obj.displaystreet + '<br>';
	}
	else if ('street' in obj)
	{
		html += obj.street + '<br>';
	}
	
	if (('city' in obj) && ('region' in obj))
	{
		html += obj.city + ', ' + obj.region;
		if ('postal' in obj)
		{
			html += ', ' + obj.postal;
		}
		html += '<br>';
	}
	if ('url' in obj)
	{
	    var temp = "www.trailerlifedirectory.com/Travel" + obj.url;
		html += '<a target="_none" href="' + m2i_fixurl(temp) + '">Location Details</a>';
	}
	html += '</div>';
	return html;
}
	
function m2i_geocodeJSONrg(obj, num)
{	return "&addr"+num+"_lon=" + obj.lon + "&addr"+num+"_lat="+obj.lat; }

function m2i_geocodeJSONfg(obj, num, rdi)
{
	var tmp;
	var hdr = "&addr"+num+"_";

	if (!obj.country) { delete(obj.country); }
	if (!obj.postal)  { delete(obj.postal);	}
	if (!obj.region)  { delete(obj.region); }
	if (!obj.city)    { delete(obj.city); }
	if (!obj.street)  { delete(obj.street); }
	if ("country" in obj)	{ tmp = hdr+"country=" + obj.country; }
	else {tmp = hdr+"country=" + m2i_defaultcountry; }
	if ("postal" in obj)	{ tmp += hdr+"postal=" + obj.postal; }
	if ("region" in obj)	{ tmp += hdr+"region=" + obj.region; }
	if ("city" in obj)		{ tmp += hdr+"city=" + obj.city; }
	if ("street" in obj)	{ tmp += hdr+"street=" + obj.street; }
	if ("place" in obj)		{ tmp += hdr+"place=" + obj.place; }
	return tmp;
}
function m2i_fixurl(url)
{
	if (/^http:\/\//.test(url))
	{ return url; }
	else
	{ return "http://" + url; }
}


// make the initial JSON URL with the proper signature, configuration, and callback function.
function m2i_makeJSONURL(callback, rdi)
{

	var tmp = "http://" + m2i_env + "" + (rdi ? m2i_clientid_rdi : m2i_clientid);
	if (m2i_password != "")
	{ tmp += "&signature=" + m2i_getSignature(); }
	if (callback.constructor != String)
	{ callback = callback.toString(); }
	return tmp + "&callback=" + callback + "&error_callback=" + callback;
}

function gcircle (lon1, lat1, lon2, lat2)
{
	var rad = 0.0174532925199;

	lon1 = parseFloat(lon1)*rad; lat1 = parseFloat(lat1)*rad;
	lon2 = parseFloat(lon2)*rad; lat2 = parseFloat(lat2)*rad;

	var theta = lon2 - lon1;
	var dist =  Math.acos(Math.sin(lat1) * Math.sin(lat2) + Math.cos(lat1) * Math.cos(lat2) * Math.cos(theta));
	if (dist < 0) { dist += Math.PI; }
	dist = dist * 6371200;
	return dist;
}


// make a UNIX timestamp
function m2i_makeTimestamp()
{	var d = new Date(); return (Math.floor(d.getTime() / 1000)).toString(); }

// build signature
function m2i_getSignature() 
{
	var d = new Date();
	var da = (Math.floor(d.getTime() / 1000)).toString();
	var t = sprintf("%8x", da);	
	var payload = (m2i_password+t+m2i_clientid).toString();
	var sig = hex_md5(payload);
	return (t + sig);	
}

function sprintf() 
{
	if (!arguments || arguments.length < 1 || !RegExp)	{
	  return;
	}
	
	var str = arguments[0];
	var re = /([^%]*)%('.|0|\x20)?(-)?(\d+)?(\.\d+)?(%|b|c|d|u|f|o|s|x|X)(.*)/;
	var a = b = [], numSubstitutions = 0, numMatches = 0;
	while (a = re.exec(str))
	{
		var leftpart = a[1], pPad = a[2], pJustify = a[3], pMinLength = a[4];
		var pPrecision = a[5], pType = a[6], rightPart = a[7];

		numMatches++;
		if (pType == '%')
		{
			subst = '%';
		}
		else
		{
			numSubstitutions++;
			if (numSubstitutions >= arguments.length)
			{
				// there was an alert here.  But we don't care for it.
			}
			var param = arguments[numSubstitutions];
			var pad = '';
					if (pPad && pPad.substr(0,1) == "'") pad = leftpart.substr(1,1);
				else if (pPad) pad = pPad;
			var justifyRight = true;
					if (pJustify && pJustify === "-") justifyRight = false;
			var minLength = -1;
					if (pMinLength) minLength = parseInt(pMinLength);
			var precision = -1;
					if (pPrecision && pType == 'f') precision = parseInt(pPrecision.substring(1));
			var subst = param;
					if (pType == 'b') subst = parseInt(param).toString(2);
				else if (pType == 'c') subst = String.fromCharCode(parseInt(param));
				else if (pType == 'd') subst = parseInt(param) ? parseInt(param) : 0;
				else if (pType == 'u') subst = Math.abs(param);
				else if (pType == 'f') subst = (precision > -1) ? Math.round(parseFloat(param) * Math.pow(10, precision)) / Math.pow(10, precision): parseFloat(param);
				else if (pType == 'o') subst = parseInt(param).toString(8);
				else if (pType == 's') subst = param;
				else if (pType == 'x') subst = ('' + parseInt(param).toString(16)).toLowerCase();
				else if (pType == 'X') subst = ('' + parseInt(param).toString(16)).toUpperCase();
		}
		str = leftpart + subst + rightPart;
	}
	return str;
}
