Skip to content Skip to sidebar Skip to footer

For a long time I wanted to test to see if I could check for the distance of the user to a panorama of a virtual tour. The panorama can be either the closest one, or one that is dictated by a parameter (e.g. a url parameter). This idea can open up some possibilities for better user experience and also can aid to implement a game of treasure hunting explicitly or partially build in the virtual tour. Since I learnt about geolocation API and had a glance of what I could accomplish with it, I never really dive into it to see if there was actually a chance for me to accomplish anything useful, since I did not even know where to start.

Nowadays I use for the virtual tours the Pano2VR software and export a HTML5 output. You have the ability to make your own template and your own skin and use them to export the tours you build in the virtual tour builder. In order to implement the code below I had to add a custom.js file and a reference to a jquery library to the ggt template and add two buttons in the skin editor. One button to fire the periodic checks for the distances by looping all the panoramas and one to switch it off.

This functionality gives the ability to track distance and bearing to one panorama and to accomplish one of the below:

  1. Check the distance of all panoramas of the virtual tour and automatically open the closest one. If the closest is already open then do not reopen it. If a new panorama becomes the closest one since the user is moving towards it, then the closest panorama will automatically change. This is done independently of the distance to the user.
  2. The above functionality, however open the closest panorama only if the user is close enough. The distance and bearing that is displayed belongs to the closest panorama. This can be set from a variable. E.g. if this variable is set to 100, then only if the user comes 100 meters away from the panorama it will change. If there are multiple panoramas less than 100 meter away, then the closest one will open.
  3. Check the distance of a given panorama (e.g. by passing a url parameter panoramaid) and open it independently of the distance to the user.
  4. Check the distance of a given panorama and open it only if the user comes close enough, like the case 2.
  5. Check the distance of given coordinates (e.g. by passing two url parameters givenlon and givenlat) and open it independently of the distance to the user.
  6. Check the distance of the given coordinates and open it only if the distance is smaller than a given threshold.

The piece of code below can be used to serve as an example of how to accomplish the mentioned cases and is included in the basic.js file. All the distance checks can be done periodically by setting the checkTimer variable in milliseconds, so when the user changes position the result changes as well.

var checkTimer;
const urlParams = new URLSearchParams(window.location.search);
const panoParam = urlParams.get('panoramaid');
const panolon = urlParams.get('givenlon');
const panolat = urlParams.get('givenlat');
var smallerdistance = urlParams.get('threshold');


function readXML(position) {
	var xhttp = new XMLHttpRequest();
	xhttp.onreadystatechange = function() {
	   if (this.readyState == 4 && this.status == 200) {
		   initXML(this, position);
	   }
	};
	xhttp.open("GET", "pano.xml", true);
	xhttp.send();
};

function initXML(xml, position) {
    var panorama, panoramaid, i, xmlDoc, panolongitude, panolatitude, userlongitude, userlatitude, panodistance=[], message;
	if (!smallerdistance) {
		smallerdistance = 2000;
	}
    xmlDoc = xml.responseXML;
	userlatitude =  position.coords.latitude;
    panolongitude	 = "";
	panolatitude	 = "";

	userlongitude = position.coords.longitude;
    panorama = xmlDoc.getElementsByTagName('panorama');
	// If none of the url parameters are set, then check for the closest panorama
	// Case 1 - This is done by checking all the panoramas, measuring the distance to the user coordinates and sorting them
	
	if (!panoParam && !panolon && !panolat) {
		for (i = 0; i < panorama.length; i++) { 
			userdata = xmlDoc.getElementsByTagName('userdata');
			panoramaid = panorama[i].getAttribute('id');
			panolongitude = parseFloat(userdata[i].getAttribute('longitude'));
			panolatitude = parseFloat(userdata[i].getAttribute('latitude'));
			panodistance.push([panoramaid, distance(panolongitude, panolatitude, userlongitude, userlatitude)]);
		}
		var sortedpanos = panodistance.sort(compareDistances);
		var closestpanoid = sortedpanos[0][0];
		var closestpanodistance = sortedpanos[0][1];
		message = "The closest panorama is ";
	// Case 3 - If the url panorama parameter is set then check the distance of the user to this panorama	
	} else if (panoParam) {
		for (i = 0; i < panorama.length; i++) { 
			userdata = xmlDoc.getElementsByTagName('userdata');
			panoramaid = panorama[i].getAttribute('id');
			if (panoramaid == panoParam) {
				panolongitude = parseFloat(userdata[i].getAttribute('longitude'));
				panolatitude = parseFloat(userdata[i].getAttribute('latitude'));
				panodistance.push([panoramaid, distance(panolongitude, panolatitude, userlongitude, userlatitude)]);
				break;
			}
		}
		//var sortedpanos = panodistance.sort(compareDistances);
		var closestpanoid = panodistance[0][0];
		var closestpanodistance = panodistance[0][1];
		message = "This panorama is ";
	// case 5 - If the coordinates url parameters are given then check the closest panorama to this point
	} else if (panolon && panolat) {
		for (i = 0; i < panorama.length; i++) { 
			userdata = xmlDoc.getElementsByTagName('userdata');
			panoramaid = panorama[i].getAttribute('id');
			panolongitude = parseFloat(userdata[i].getAttribute('longitude'));
			panolatitude = parseFloat(userdata[i].getAttribute('latitude'));
			panodistance.push([panoramaid, distance(panolongitude, panolatitude, parseFloat(panolon), parseFloat(panolat))]);
		}
		var sortedpanos = panodistance.sort(compareDistances);
		var closestpanoid = sortedpanos[0][0];
		var closestpanodistance = sortedpanos[0][1];
		message = "The closest panorama to the given coordinate is ";
	}	
	var bearing = checkBearing(userlongitude, userlatitude, panolongitude, panolatitude);
	
	var currentPano = pano.getCurrentNode();
	if (currentPano !== closestpanoid) {
		// case 2, 4 and 6 (open the panorama if the distance is lower that the smaller distance threshold)
		if (closestpanodistance < smallerdistance) {
			pano.openNext("{" + closestpanoid + "}");
		}
	//alert("This panorama is " + Math.round(closestpanodistance * 100) / 100 + " kilometers away from you."); 
	}
	$(".outputdistance").fadeOut();
	$(".outputdistance").fadeIn();
	$(".outputdistance").html(message + Math.round(closestpanodistance * 1000) + " meters away and the bearing is " + Math.round(bearing));
}


function stopcheck() {
	clearInterval(checkTimer);
	$(".outputdistance").fadeOut();
}

function compareDistances(a, b) {
    if (a[1] === b[1]) {
        return 0;
    }
    else {
        return (a[1] < b[1]) ? -1 : 1;
    }
}

function checkBearing(startLng, startLat, destLng, destLat){
  startLat = toRadians(startLat);
  startLng = toRadians(startLng);
  destLat = toRadians(destLat);
  destLng = toRadians(destLng);

  y = Math.sin(destLng - startLng) * Math.cos(destLat);
  x = Math.cos(startLat) * Math.sin(destLat) -
        Math.sin(startLat) * Math.cos(destLat) * Math.cos(destLng - startLng);
  brng = Math.atan2(y, x);
  brng = toDegrees(brng);
  return (brng + 360) % 360;
}
// Converts from degrees to radians.
function toRadians(degrees) {
  return degrees * Math.PI / 180;
}

function toDegrees(radians) {
  return radians * 180 / Math.PI;
}

if (navigator.geolocation) {    
    // Browser supports it, we're good to go!     
    } else {    
    alert('Sorry your browser doesn\'t support the Geolocation API');    
    }


function execcheck() {
	navigator.geolocation.getCurrentPosition(readXML, errorPosition);
}

function checkdistance() {    
	execcheck();
    checkTimer = setInterval(execcheck, 10000);
}

function errorPosition() {  
	
    alert('Sorry couldn\'t find your location');                 
    pretext.show();         
}

function distance(lon1, lat1, lon2, lat2) {
  var R = 6371; // Radius of the earth in km
  var dLat = (lat2-lat1).toRad();  // Javascript functions in radians
  var dLon = (lon2-lon1).toRad(); 
  var a = Math.sin(dLat/2) * Math.sin(dLat/2) + Math.cos(lat1.toRad()) * Math.cos(lat2.toRad()) * Math.sin(dLon/2) * Math.sin(dLon/2); 
  var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); 
  var d = R * c; // Distance in km
  return d;
}

/** Converts numeric degrees to radians */
if (typeof(Number.prototype.toRad) === "undefined") {
  Number.prototype.toRad = function() {
    return this * Math.PI / 180;
  }
}

Panotours © 2021. All Rights Reserved.