menu menu

Visites Virtuals Interactives

Desenvolupament d'una aplicació web per a la creació de Visites Virtuals Interactives

Treball de Recerca - Marc Pujol Gualdo

TREBALL

ANNEXOS

CODI

            
/* FRAGMENT DE CODI DE MOSTRA */

angular.module('VisitesVirtualsApp').controller('PujarImatgesCtrl', ['$rootScope', '$scope', 'settings', 'FileUploader', 'usuari', '$firebaseArray', '$firebaseObject', '$http', '$stateParams', '$state', function ($rootScope, $scope, settings, FileUploader, usuari, $firebaseArray, $firebaseObject, $http, $stateParams, $state) {

    usuari.usuari().then(function (valor) {
        $scope.usuari = valor; //les dades de l'usuari són les que torna la promesa de la propietat "usuari" de la factoria "usuari"
        var id_visita = $stateParams.visitaid; //obtenim l'id de la visita de l'URL (/#/visita/idvisita)
        $scope.id_visita = id_visita;

        var ref = firebase.database().ref("visites/" + id_visita + ""), //creem una referència a la base de dades que apunti a la visita en qüestió
            panosRef = firebase.database().ref("visites/" + id_visita + "/scenes"); //creem una referència a la base de dades que apunti a les escenes de la visita en qüestió
        $scope.ref = $firebaseObject(ref); //llegim les dades de la visita de la base de dades
        $scope.panos = $firebaseArray(panosRef); //llegim les dades de la visita de la base de dades
        var storageRef = firebase.storage().ref(); //creem una referència a Firebase Storage

        $scope.avancar = function () {
            $state.go("crearid.ubicacio", {
                visitaid: id_visita
            });
        };

        //funció que es crida quan es vol seleccionar zones del plànol d'una visita i associar-les a escenes per permetre'n la posterior navegació.
        crearMap = function () {
            setTimeout(function () {
                //primer elimina les possibles conexions a mitjes que hi pogués haver
                $('#planol_img').imgAreaSelect({
                    remove: true
                });
                //fa possible que es seleccionin zones al plànol (imatge amb id "planol_img")
                $('#planol_img').imgAreaSelect({
                    handles: true,
                    onSelectEnd: function (img, selection) {
                        //quan s'acaba de determinar els límits de la selecció, es guarden les coordenades (punts que delimiten la zona) a l'$scope.
                        $scope.coordinades = selection.x1 + ',' + selection.y1 + ',' + selection.x2 + ',' + selection.y2;
                        //s'obra el modal amb id "modal_planol". (per connectar la zona que s'acaba de delimitar amb alguna de les escenes)
                        $("#modal_planol").modal();
                    }
                });
            }, 750);
        };

        //si ens trobem al tercer pas (plànol) s'executa el següent codi
        if ($state.is('crearid.planol')) {
            $("#planol_img").load(function () {
                //quan el plànol s'ha carregat, executem la funció crearMap que acabem de definir.
                $("#ample").css("width", $("#planol_img").css("width"));
                $("img[usemap]").maphilight();
                crearMap();
            });
        };

        //funció que es crida quan en el modal (#modal_planol) es selecciona la imatge a la que es vol associar la selecció en qüestió
        afegirPlanol = function (pano) {
            //ocultem els modals actius
            $(".modal").modal('hide');
            //executem la funció crearMap
            crearMap();
            //a la base de dades, afegim una nova entrada (dins de visita/idvisita/planol/coordinades) on determinem el panorama al que fa referència i les coordinades de la zona (guardades en una variable de l'$scope)
            ref.child("planol/coordinades").push({
                coordinades: $scope.coordinades,
                pano: pano
            });
        };

        //funció que es crida si es vol eliminar una de les zones afegides al plànol.
        $scope.eliminarMap = function (key) {
            //en reb l'id com a paràmetre, i l'utilitza per eliminar-la de la base de dades
            ref.child("planol/coordinades/" + key).remove();
        };

        //funció necessària per reduir la mida de la imatge (en cas que sigui necessàri)
        dataURLtoBlob = function (b64Data) {
            sliceSize = 512;
            var byteCharacters = atob(b64Data.split(',')[1]);
            var byteArrays = [];

            for (var offset = 0; offset < byteCharacters.length; offset += sliceSize) {
                var slice = byteCharacters.slice(offset, offset + sliceSize);

                var byteNumbers = new Array(slice.length);
                for (var i = 0; i < slice.length; i++) {
                    byteNumbers[i] = slice.charCodeAt(i);
                }

                var byteArray = new Uint8Array(byteNumbers);

                byteArrays.push(byteArray);
            }

            var blob = new Blob(byteArrays, {
                type: "image/jpg"
            });
            return blob;
        }

        //funció cridada quan es seleccionen o s'arrosseguen imatges per ser utilitzades com a plànol
        pujarImatges = function (evt) {
            var files = evt.files;
            $scope.files = files; //els arxius que ha seleccionat

            var contador = 0,
                contador_max = files.length, //número de fotos que s'ha seleccionat
                i = 0;

            //executem un bucle tantes vegades com fotos hi ha per penjar.
            for (i; i < contador_max; i++) {
                /*	encapsulem tot el procès de penjar cada foto en una funció on passem el valor de i com a parametre, ja que la funció de penjar és asincrònica i agafaria els valors variables que hi han en el moment en que s'ha penjat (diferent dels que hi havia quan va començar a penjar-se)	*/
                (function (num_invariable) {
                    //num_invariable té el valor de i. Anirà incrementant fins arribar a contador_max
                    var file = files[num_invariable], //seleccionem una de les fotos a penjar
                        filename = file.name,
                        metadata = {
                            'contentType': file.type
                        },
                        id_escena = num_invariable + "escena" + Math.random().toString(36).substr(2, 9), //creem un id únic per aquesta escena
                        //renderitzem la imatge seleccionada al navegador per obtenir-ne les mides
                        _URL = window.URL || window.webkitURL;
                    if (file.type.indexOf("image") == -1) {
                        //si l'arxiu seleccionat no és una imatge
                        usuari.mostrarError("Error", "L\'arxiu " + file.name + " no és una imatge vàlida.
És un arxiu de tipus " + file.type); $scope.files = []; //buidem els arxius que estaven seleccionats $("#file").val(''); } var img = new Image(); img.src = _URL.createObjectURL(file); img.onload = function () { var alcada_perfecta = this.width / 2; //per ser un panorama complet l'alçada (180º) hauria de ser la metitat que l'amplada (360º). if (!((this.height) < (alcada_perfecta + alcada_perfecta * 0.1) && (this.height) > (alcada_perfecta - alcada_perfecta * 0.1))) { //si la imatge no manté una relacio 2:1 (deixant cert marge d'acceptació) usuari.mostrarError("Error", "La imatge ha se ser esfèrica.
És a dir, l\'amplada ha de ser el doble de l\'alçada"); $scope.files = []; //buidem els arxius que estaven seleccionats $("#file").val(''); } else { //en cas que no presenti cap dels errors anteriors $("#file").attr("disabled", true); //impedim que es seleccionin més fotos fins que no s'hagi acabat amb aquestes if (file.size > 5000000) { //si la imatge pesa més de 5MB (5000000 Bytes) usuari.mostrarError("Error", "L\'arxiu " + file.name + " és massa gran.
La màxima mida permesa són 5 MB i la vostra imatge pesa " + (file.size / 1000000).toFixed(2) + " MB"); $scope.files = []; //buidem els arxius que estaven seleccionats $("#file").val(''); return false; } var MAX_WIDTH = 4096; //amplada màxima per ser compatible amb el màxim de dispositius var width = img.width; //amplada real de la imatge if (width > MAX_WIDTH) { //si l'amplada és més gran que la màxima recomanada. //no és cap error, tan sols una notificació (per tant es penjarà la imatge) usuari.mostrarError("Error", "És possible que aquest panorama no es pugui visualitzar des d'iPhones i iPads a causa de les seves dimensions.
Fa " + width + "px d'amplada mentre que el màxim recomanat per raons de compatibilitat és de " + MAX_WIDTH + "px."); } $scope.files[num_invariable].preview = img.src; //l'adreça necessària per previsualitzar la imatge mentre s'està penjant var PenjarFoto = storageRef.child('visites').child(id_visita).child(id_escena).put(file, metadata); //penjem la foto a Firebase Hosting, a l'adreça /visites/idvisita/nomescena PenjarFoto.on('state_changed', function (snapshot) { //calculem el progrés dividint els bytes penjats entre els bytes toals i multiplicant per 100 var progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100; $scope.files[num_invariable].progress = progress; //reflectim aquest progrés en una barra de progrés al DOM }, function (error) { //en cas d'error degut a Firebase Hosting usuari.mostrarError("Error", "No s'ha pogut penjar la imatge:
" + error); }, function () { //en cas que la imatge es penji correctament var primera_url = PenjarFoto.snapshot.metadata.downloadURLs[0]; //obtenim l'URL de la imatge que acabem de penjar $scope.files[num_invariable].hide = true; //treiem la foto de la columna de progrés var url = primera_url.substr(77, primera_url.length); //retallem la URL, agafant-ne només la part final. (la 1a part està definida a la base de dades --> /visites/idvisita/default/basePath) //afegim una nova escena de la visita a la base de dades, definint-ne "panorama" amb l'url que acabem d'obtenir de la imatge ref.child("scenes/" + id_escena).set({ panorama: url, preview: url, nom_original: filename, url: Math.random().toString(36).substring(7) //un número aleatori (més tard es necessari) }); ref.child("storage").push("visites/" + id_visita + "/" + id_escena); //afegim la ruta de imatge de Firebase Hosting a la base de dades ref.child("default/firstScene").set(id_escena); //definim firstScene (primera escena) com la imatge que acabem de penjar ref.child("incomunicats/" + id_escena).set(true); //afegim la imatge a la llista d'escenes incomunicades (més endavant aniran eliminant-se a mesura que les connectem les unes amb les altres) contador++; //augmentem el contador per saber que ja hem enllestit amb la imatge actual if (contador === contador_max) { //en cas que el contador sigui igual que el número màxim d'imatges per penjar significa que ja hem acabat. $("#file").removeAttr("disabled"); //tornem a activar la zona per penjar arxius, per si se'n volen afegir més } }); } }; })(i); }; } //funció cridada quan s'apreta el botó d'eliminar un panorama $scope.eliminarEscena = function (key) { storageRef.child("visites/" + id_visita + "/" + key).delete().then(function () { //eliminem la imatge de Firebase Hosting ref.child("scenes/" + key).remove(); //eliminem l'escena de la base de dades if ($scope.ref.incomunicats[key]) { ref.child("incomunicats/" + key).remove(); //eliminem l'escena de la llista d'escenes incomunicades (en cas que hi sigui) }; }); }; //funció cridada quan es selecciona una imatge per penjar com a plànol pujarPlanol = function (evt) { var file = evt.files[0]; $scope.planol = file; //obtenim la imatge seleccionada var metadata = { 'contentType': file.type }; if (file.type.indexOf("image") == -1) { //si l'arxiu penjat no és una imatge usuari.mostrarError("Error", "L\'arxiu " + file.name + " no és una imatge vàlida.
És un arxiu de tipus " + file.type); $scope.files = []; $("#file").val(''); } //renderitzem una imatge per obtenir-ne les mides var _URL = window.URL || window.webkitURL; var img = new Image(); img.src = _URL.createObjectURL(file); img.onload = function () { $("#file").attr("disabled", true); //desactivem la zona per penjar arxius (fins que no enllestim amb aquest) var MAX_WIDTH = 800; //definim una amplada màxima (no és necessària una gran qualitat per el plànol) var width = img.width; //obtenim l'amplada real de la imatge var height = img.height; //obtenim l'alçada real de la imatge if (width > MAX_WIDTH) { //en cas que la'amplada de la imatge superi la màxima, generem una imatge més petita mitjançant un "canvas". var canvas = document.createElement("canvas"); var ctx = canvas.getContext("2d"); ctx.drawImage(img, 0, 0); height = height / (width / MAX_WIDTH); //reduim l'alçada amb les mateixes proporcions amb que s'ha reduit l'amplada width = MAX_WIDTH; //reduim l'amplada a la màxima establerta // i dotem el "canvas" d'aquestes noves mides canvas.width = width; canvas.height = height; var ctx = canvas.getContext("2d"); ctx.drawImage(img, 0, 0, width, height); var dataurl = canvas.toDataURL("image/png"); //obtenim una URL del canvas file = dataURLtoBlob(dataurl) //passem d'una url a un objecte que poguem penjar a Firebase Storage } $scope.planol.preview = img.src; //obtenim una URL per previsualitzar la imatge abans que es pengi var PenjarPlanol = storageRef.child('visites').child(id_visita).child("planol").put(file, metadata); PenjarPlanol.on('state_changed', function (snapshot) { //calculem el progrés dividint els bytes penjats entre els bytes toals i multiplicant per 100 var progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100; $scope.planol.progress = progress; //reflectim aquest progrés en una barra de progrés al DOM }, function (error) { //en cas d'error, el manegem usuari.mostrarError("Error", "Error en penjar el plànol:
" + error); }, function () { var url = PenjarPlanol.snapshot.metadata.downloadURLs[0]; //obtenim l'url de la imatge que acabem de penjar ref.child("planol").child("url").set(url); //afegim la url a la base de dades ref.child("storage").push("visites/" + id_visita + "/planol"); //afegim la ruta de l'arxiu a Firebase Storage $scope.planol.hide = true; }); }; } }); $scope.$on('$viewContentLoaded', function () { // initialize core components App.initAjax(); // set default layout mode $rootScope.settings.layout.pageContentWhite = true; $rootScope.settings.layout.pageBodySolid = false; $rootScope.settings.layout.pageSidebarClosed = false; }); }]);

RESULTAT

CONTACTAR