Μετάβαση στο περιεχόμενο

Πανοράματα με βάση τη θέση: μια δοκιμή στο Pano2VR

Σε αυτή τη δοκιμή του 2021 εξέτασα αν μια περιήγηση Pano2VR μπορούσε να επιλέγει πανόραμα με βάση τη γεωγραφική θέση μιας συσκευής. Πιθανές χρήσεις ήταν το άνοιγμα μιας κοντινής όψης ή μια προτροπή που εξαρτάται από τη θέση μέσα σε μια ευρύτερη παρουσίαση.

Η προσέγγιση

Το script διάβαζε τις συντεταγμένες των πανοραμάτων από τα δεδομένα XML της περιήγησης, τις συνέκρινε με μια θέση και επέλεγε σκηνή. Μπορούσε να αναζητά το κοντινότερο πανόραμα, να στοχεύει ένα πανόραμα που οριζόταν στο URL ή να συγκρίνει τα πανοράματα με δοσμένες συντεταγμένες. Ένα όριο απόστασης καθόριζε αν θα άνοιγε η σκηνή.

Όρια του παραδείγματος

Πρόκειται για παλαιότερο πρωτότυπο, όχι για συντηρούμενο οδηγό εγκατάστασης ή εργαλείο πλοήγησης. Εξαρτάται από τη δομή εξαγωγής της περιήγησης, τα δεδομένα συντεταγμένων και τις παλαιότερες παραδοχές του script. Η απόσταση από ένα σημείο δεν αποδεικνύει ότι υπάρχει βατή διαδρομή ή ασφαλής πρόσβαση.

Ο γεωεντοπισμός στον browser απαιτεί άδεια του επισκέπτη και ασφαλές περιβάλλον σύνδεσης. Δείτε την τεκμηρίωση του Geolocation API. Η ακρίβεια και η συχνότητα ενημέρωσης εξαρτώνται από τη συσκευή και τις συνθήκες.

Αρχικός κώδικας

Το αρχικό παράδειγμα διατηρείται παρακάτω χωρίς να υποστηρίζεται ότι λειτουργεί με τις σημερινές εξαγωγές του Pano2VR. Η συνάρτηση απόστασης επιστρέφει χιλιόμετρα, ενώ η ένδειξη μετατρέπεται σε μέτρα· επομένως το όριο απόστασης χρειάζεται έλεγχο ως προς αυτές τις μονάδες.

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;
  }
}

Για το ευρύτερο έργο, δείτε την παρουσίαση του Ψηλορείτη και την υπηρεσία χαρτών και GIS.

Επιστροφή στην αρχή