In this 2021 experiment I tested whether a Pano2VR tour could choose a panorama according to a device’s geographic position. Possible uses included opening a nearby view or providing a location-based prompt within a wider presentation.
The approach
The script read the panorama coordinates from the tour’s XML data, compared them with a position and selected a scene. It could look for the nearest panorama, target a panorama identified in the URL, or compare the panoramas with supplied coordinates. A distance threshold controlled whether a scene would open.
Limits of the example
This is an archived prototype, not a maintained installation guide or navigation tool. It depends on the exported tour structure, its coordinate data and older script assumptions. Distance to a point also does not establish a walkable route or safe access.
Browser location requires the visitor’s permission and a secure context. See the Geolocation API documentation. Accuracy and update frequency depend on the device and conditions.
Original code
The original example is retained below without a claim that it works with current Pano2VR exports. The distance function returns kilometres, while the displayed result is converted to metres; a threshold must therefore be checked against those units.
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;
}
}
For the wider work, see the Psiloritis project and the maps and GIS service.

