jueves, 21 de enero de 2016

Libro Google maps Javascript - Capítulo 5 : Guardar y cargar tu overlays



Volver al indice


Capítulo 5 : Guardar y cargar tu overlays


Hasta este punto hemos aprendido, como cargar un mapa, como crear Markers y overlays, localizar al usuario entre otras cosas. El siguiente paso en cualquier proyecto web basado en mapas, es guardar toda la información creada por el usuario. Esta información por lo general tiene que ser almacenada en una base de datos del servidor, en este capítulo no abordaremos los temas de como se guarda la información en el lado del servidor,  en su lugar veremos cómo preparar la información para enviarla al servidor por medio de AJAX.

Al proceso de preparar la información antes de enviarla al servidor para que sea guardada se le conoce como  “serialización”.

Para comenzar este capítulo necesitamos un punto de partida, para esto vamos a usar el archivo de práctica de la carpeta número “26”, donde podemos ejecutar el archivo “index.html”. Al cargar nuestro ejemplo vemos que tenemos un mapa sencillo, al lado izquierdo tenemos un panel vacio, tambien tenemos cargada la “Drawing Library” con la cual podemos dibujar todos los tipos de overlays que queramos, los cuales tienes habilitado su modo edición en las opciones de nuestra “Drawing Library”. Este será nuestro punto de partida. La idea es que el usuario pueda dibujar cualquier overlay en cualquier lugar del mapa y nuestro trabajo será guardar toda esa información.

El primer paso a seguir es identificar qué tipo de overlay el usuario ha dibujado en el mapa, en dependencia del tipo, podremos saber cómo guardar la información. Para esto crearemos un evento “overlaycomplete” para el objeto “Drawing Library” el cual se ejecuta cada vez que un overlay es dibujado.

// when the map is ready
google.maps.event.addListenerOnce(map, 'idle', function() {   
       var drawingManager = new google.maps.drawing.DrawingManager({
        drawingControlOptions: {
            position: google.maps.ControlPosition.TOP_CENTER
        },
        circleOptions: {editable: true },
        polygonOptions: {editable: true },
        polylineOptions: {editable: true },
        rectangleOptions: {editable: true },
        markerOptions : {draggable:true},
        map: map
    });
    google.maps.event.addListener(drawingManager, 'overlaycomplete', function(event) {
   
    });       
});

En este evento podemos escribir nuestro código para saber que tipo de overlay se ha dibujado, a partir de este momento nos centraremos en este método por lo que no haré referencia al código encargado de crear la “Drawing overlay”.

Cuando el evento “overlaycomplete” es ejecutado, pasa como parámetro a nuestra callback un objeto llamado “event”, el cual contiene 2 propiedades “type” y “overlay”. “type” nos indica el nombre del overlay creado y “overlay” es el objeto overlay creado. Sabiendo esto podemos añadir la siguiente línea de código:

google.maps.event.addListener(drawingManager, 'overlaycomplete', function(event) {
    switch(event.type)
    {
        case "marker":
            alert('I am a marker');
        break;
        case "polyline":
            alert('I am a polyline');
        break;
        case "rectangle":
            alert('I am a rectangle');
        break;
        case "circle":
            alert('I am a circle');
        break;
        case "polygon":
            alert('I am a polygon');
        break;
    }
});

Con este swicht y usando la propiedad “event.type”, podremos saber exactamente cual overlay fue dibujado por el usuario. Primero vamos a serializar el overlay del tipo Marker.

Serializar Marker


Serializar es el proceso de organizar la información que necesitamos guardar antes de enviarla al servidor, por lo tanto esta sección vamos a serializar un marker. Primero tenemos que hacernos la siguiente pregunta :

¿ Qué información necesitamos para recrear un marker ?

El Latlng para saber donde ubicarlo y sus opciones, en este caso para nuestro ejemplo solo sera la propiedad “editable”.

Con esta respuesta en mente ahora vamos a crear nuestro método para serializar un maker.

google.maps.event.addListener(drawingManager, 'overlaycomplete', function(event) {
    switch(event.type)
    {
        case "marker":
            SerializeMarker(event.overlay);
        break;
        case "polyline":
            alert('I am a polyline');
        break;
        case "rectangle":
            alert('I am a rectangle');
        break;
        case "circle":
            alert('I am a circle');
        break;
        case "polygon":
            alert('I am a polygon');
        break;
    }
});

El método que usaremos será “serializeMarker”, al cual le pasaremos como parámetro el overlay que ha sido creado por el usuario que nos proporciona la propiedad “event.overlay”, ahora definiremos nuestro método, el cual podremos ubicar fuera del evento ready de Jquery.

function SerializeMarker(marker) {
    var object = {};
    object.position = marker.getPosition().toUrlValue();
    object.type = "marker";
    object.draggable = marker.getDraggable();
    return object;
}

Ahora nuestro método nos devolverá un objeto, con las propiedades principales para recrear el overlay marker que son “position”, si es “draggable” y que tipo de overlay es con “type”.


Hemos usado cada uno de los métodos del overlay tipo marker para obtener los datos que necesitamos, por ejemplo “getPosition” que nos devuelve el Latlng de la posición actual del marker. Una vez tenemos el Latlng del marker podemos encadenar el método “toUrlValue” del objeto Latlng para convertir este dato a una cadena, el cual sera mas facil de almacenar.

Después usamos “getDraggable” que nos devuelve true ó false, en este caso será true para saber si el marker será editable o no. Nuestro método devolverá un objeto similar a este :

Object {
    position: "52.05249,-106.259766",
    type: "marker",
    editable: true
}

Una vez que nuestro método “SerializeMarker”, nos devuelve el objeto producto de nuestra serialización, tenemos que guardarlo en algun lugar. Para esto vamos usar un arreglo global llamado “overlayArray” el cual vamos a definir al inicio de nuestro archivo “googlemaps.js”, una vez hecho esto solo tenemos que usar el método push javascript para guardar nuestro objeto en este arreglo :

google.maps.event.addListener(drawingManager, 'overlaycomplete', function(event) {
    switch(event.type)
    {
        case "marker":
            overlayArray.push(SerializeMarker(event.overlay));
        break;
        case "polyline":
            alert('I am a polyline');
        break;
        case "rectangle":
            alert('I am a rectangle');
        break;
        case "circle":
            alert('I am a circle');
        break;
        case "polygon":
            alert('I am a polygon');
        break;
    }
});

Ahora supongamos que dibujamos 3 overlays marker en nuestro mapa y consultamos la variable “overlayArray” en el modo consola de Google Chrome, obtendremos algo como esto:



Cada elemento del arreglo “overlayArray”, será un objeto serializado por nosotros. Lo siguiente que necesitamos es una manera de identificar cada elemento del arreglo de forma individual, esto lo podemos conseguir usando un ID para identificar cada overlay. Para esto por ejemplo podemos crear otra variable global llamada “idCounter” la cual debemos inicializar en “0” y será nuestro contador que aumentaremos en 1 cada vez que un overlay sea creado.

google.maps.event.addListener(drawingManager, 'overlaycomplete', function(event) {
    idCounter = idCounter + 1;   
    switch(event.type)
    {
        case "marker":
            overlayArray.push(SerializeMarker(event.overlay,idCounter));
        break;
        case "polyline":
            alert('I am a polyline');
        break;
        case "rectangle":
            alert('I am a rectangle');
        break;
        case "circle":
            alert('I am a circle');
        break;
        case "polygon":
            alert('I am a polygon');
        break;
    }
});
function SerializeMarker(marker,id) {
    var object = {};
    object.position = marker.getPosition().toUrlValue();
    object.type = "marker";
    object.id = id;
    object.draggable = marker.getDraggable();
    return object;
}


Como puedes ver, he actualizado el método “SerializeMarker” para que reciba un segundo parámetro que será nuestro ID que identificara nuestro objeto, en la siguiente imagen podemos ver un ejemplo en modo consola de Google Chrome del objeto overlayArray .
 

Ahora cada elemento del arreglo “overlayArray”, puede ser identificado por la propiedad ID. Y con esto seria todo para serializar nuestro marker.



Serializar rectangle


Ahora vamos a serializar el overlay rectangle, para poder recrear este overlay hacemos la misma pregunta.

¿ Qué información necesitamos para recrear un rectangle ?

Este overlay es muy sencillo, ya que solo necesitamos saber el “bounds” del overlay del tipo rectangle para poderlo recrearlo y sus opciones.

google.maps.event.addListener(drawingManager, 'overlaycomplete', function(event) {
    idCounter = idCounter + 1;   
    switch(event.type)
    {
        case "marker":
            overlayArray.push(SerializeMarker(event.overlay,idCounter));
        break;
        case "polyline":
            alert('I am a polyline');
        break;
        case "rectangle":
            overlayArray.push(SerializeRectangle(event.overlay,idCounter));
        break;
        case "circle":
            alert('I am a circle');
        break;
        case "polygon":
            alert('I am a polygon');
        break;
    }
});

function SerializeRectangle(rectangle,id) {
    var object = {};
    object.bounds = rectangle.getBounds().toUrlValue();
    object.type = "rectangle";
    object.id = id;
    object.editable = rectangle.getEditable();
    return object;
}

Esta es la misma dinámica en como trabaja nuestro método anterior “serializeMarker”, la diferencia es en los métodos usados para crear nuestro objeto serializado. El método “getBounds” del overlay tipo “rectangle” nos devuelve un “LatlngBounds” y cuando usamos el método “toUrlValue” nos devuelve una cadena con los valores de ese bounds en el siguiente orden :  "lat_lo , lng_lo , lat_hi , lng_hi " donde la terminación “lo” se refiere al suroeste y “hi” al noreste. En modo consola nuestro objeto serializado sería similar a este este ejemplo:


Serializar polyline


Los overlay del tipo polyline deben ser serializados con un paso adicional, debido a que su trazado es definido por un arreglo de puntos Latlng, los cuales tenemos que serializar por separado. Para comenzar vamos definir nuestro método para serializar una polyline.


google.maps.event.addListener(drawingManager, 'overlaycomplete', function(event) {
    idCounter = idCounter + 1;   
    switch(event.type)
    {
        case "marker":
            overlayArray.push(SerializeMarker(event.overlay,idCounter));
        break;
        case "polyline":
            overlayArray.push(SerializePolyline(event.overlay,idCounter));
        break;
        case "rectangle":
            overlayArray.push(SerializeRectangle(event.overlay,idCounter));
        break;
        case "circle":
            alert('I am a circle');
        break;
        case "polygon":
            alert('I am a polygon');
        break;
    }
});
function SerializePolyline(polyline,id) {
    var object = {};   
    object.path =  '';
    object.type = "polyline";
    object.id = id;
    object.draggable = polyline.getEditable();
    return object;
}

Hemos definido nuestro método llamado “SerializePolyline” y dentro de él  tenemos los mismos pasos anteriores con (id , type y draggable), lo único adicional aquí es la propiedad llamada “path”, la cual usaremos para guardar el path ó trazado de la polyline. Debemos recordar que el path de una polyline es un arreglo de puntos LatLng, por lo tanto tenemos que serializarlo por separado, crearemos un método adicional que se encargue de esta operación  por nosotros a continuación.

function SerializeMvcArray(mvcArray) {
    var path = [];
    for(var i= 0; i < mvcArray.getLength(); i++)
    {
        path.push(mvcArray.getAt(i).toUrlValue());
    }   
    return path.toString();
}
Este será nuestro método para serializar un  MVCArray . ¿Por que un MVCArray?  recuerda que para obtener el path de una polyline debemos utilizar el método “getPath” del objeto polyline, el cual nos devuelve un MVCArray.

En este método vamos a recorrer cada elemento del MVCArray por medio de un ciclo FOR, con el método “getLength” del objeto MVCArray obtenemos cuántos elementos existen en el arreglo. Y para obtener cada elemento individual del  MVCArray usamos el método “getAt”, que recibe sólo el índice del elemento, el cual nos devolverá un objeto Latlng, el que seguidamente convertimos a cadena usando “toUrlValue”.

En cada iteración del ciclo FOR guardamos nuestro valor de cadena en un arreglo llamado “path” y cuando el ciclo FOR a terminado el método devolverá esos valores en formato cadena usando el método “toString”. Con nuestro método creado solo nos resta hacer el llamado desde nuestro metodo  “SerializePolyline”.

function SerializePolyline(polyline,id) {
    var object = {};   
    object.path = SerializeMvcArray(polyline.getPath());
    object.type = "polyline";
    object.id = id;
    object.draggable = polyline.getEditable();
    return object;
}
function SerializeMvcArray(mvcArray) {
    var path = [];
    for(var i= 0; i < mvcArray.getLength(); i++)
    {
        path.push(mvcArray.getAt(i).toUrlValue());
    }   
    return path.toString();
}

Ahora la propiedad “path” de nuestro objeto serializado contendrá el trazado ó path de la polyline en formato de texto. Este es un ejemplo del objeto serializado:





Serializar polygon


Serializar un overlay del tipo polygon es muy parecido al método usado para  polyline, la única diferencia es que la polyline es una series de puntos Latlng que determinan un trayecto abierto, mientras que un polygon es una serie de puntos Latlng que definen una región cerrada. La API de Google Maps automáticamente unirá el punto final con el inicial para cerrar el polygon. Con esto en mente podemos definir nuestro método:

google.maps.event.addListener(drawingManager, 'overlaycomplete', function(event) {
    idCounter = idCounter + 1;   
    switch(event.type)
    {
        case "marker":
            overlayArray.push(SerializeMarker(event.overlay,idCounter));
        break;
        case "polyline":
            overlayArray.push(SerializePolyline(event.overlay,idCounter));
        break;
        case "rectangle":
            overlayArray.push(SerializeRectangle(event.overlay,idCounter));
        break;
        case "circle":
            alert('I am a circle');
        break;
        case "polygon":
            overlayArray.push(SerializePolygon(event.overlay,idCounter));
        break;
    }
});
function SerializePolygon(polygon,id) {
    var object = {};   
    object.path = SerializeMvcArray(polygon.getPath());
    object.type = "polygon";
    object.id = id;
    object.draggable = polygon.getEditable();
    return object;
}

El funcionamiento es idéntico que el método para serializar un polyline, dado que el método para obtener el path de un polygon tiene el mismo nombre en la API y devuelve también un MVCArray, por lo tanto necesitamos hacer uso de “SerializeMvcArray” que habíamos creado anteriormente.

Con esto será suficiente, a continuación un ejemplo de como sería el objeto serializado:



 Un dato importante que debes recordar es que en nuestro ejemplo estamos usando el metodo “getPath” del objeto polygon para obtener su path, por es un polygon simple cerrado.Pero un polygon también puede estar formado por más de una path, por ejemplo para los polygon huecos, en este caso tendrias que usar el método “getPaths” para obtener todas las paths relacionadas.

Serializar circle


El overlay tipo circle solo necesita 2 propiedades para ser creados que son center y radius, los cuales son fáciles de obtener por medio de los métodos del objeto circle.

google.maps.event.addListener(drawingManager, 'overlaycomplete', function(event) {
    idCounter = idCounter + 1;   
    switch(event.type)
    {
        case "marker":
            overlayArray.push(SerializeMarker(event.overlay,idCounter));
        break;
        case "polyline":
            overlayArray.push(SerializePolyline(event.overlay,idCounter));
        break;
        case "rectangle":
            overlayArray.push(SerializeRectangle(event.overlay,idCounter));
        break;
        case "circle":
            overlayArray.push(SerializeCircle(event.overlay,idCounter));
        break;
        case "polygon":
            overlayArray.push(SerializePolygon(event.overlay,idCounter));
        break;
    }
});

function SerializeCircle(circle,id) {
    var object = {};   
    object.center = circle.getCenter().toUrlValue();
    object.radius = circle.getRadius();
    object.type = "circle";
    object.id = id;
    object.draggable = circle.getEditable();
    return object;
}

Con el método “getCenter” obtenemos el centro del overlay que nos devuelve un Latlng y con “getRadius” obtenemos el radio del overlay circle en metros. A continuación un ejemplo del objeto:




Serializar el mapa


Además de los overlay es conveniente serializar el estado actual del mapa, por ejemplo podemos guardar en que zona estaba el usuario,  que nivel de zoom tenia en ese momento y que tipo de mapa tenia seleccionado.

function SerializeMap()  {
    var object = {};
    object.zoom = map.getZoom();
    object.bounds = map.getBounds().toUrlValue();
    object.mapType = map.getMapTypeId();
    object.type = 'map';
    return object;
}

Este es nuestro método para serializar nuestro objeto mapa, no recibe ningún parámetro ya que nuestro mapa esta guardado en la variable global “map”. Como siempre usamos los métodos disponibles del objeto “map” para obtener los datos que necesitamos.

“getZoom” para obtener el zoom , “getBounds” para saber en que zona esta el usuario en ese momento, recuerda el bounds del objeto map se refiere al viewport del mapa y “getMapTypeId” para el tipo de mapa seleccionado.

El mejor lugar para llamar a este método, es un botón que tenemos disponible llamado “save” con el id “save” en un nuestro “index.html”, que creo habrás notado en nuestro ejemplo en el lado izquierdo.

$('#save').on('click',function(){   
    var data = {};
    data.map = SerializeMap();
    data.overlays = overlayArray;
});       

La razón por la que hacemos el llamado a este metodo aqui es por que la accion del boton “save” es la de guardar toda la información en la base de datos, por lo tanto solo necesitamos serializar nuestro objeto mapa una sola vez.

Aquí creamos un nuevo objeto llamado “data” para organizar mejor la información antes de enviarla al servidor, nuestro método “SerializeMap” devuelve el objeto mapa serializado, el cual lo guardamos en la propiedad llamada “map” y nuestro arreglo de overlays los guardaremos en una propiedad llamada “overlays”.

Ahora solo nos basta enviar esta información por medio de AJAX al servidor para ser guardada, una maneras de hacer esto es usando el método POST de Jquery.

$('#save').on('click',function(){   
    var data = {};
    data.map = SerializeMap();
    data.overlays = overlayArray;
    $.post("hereyoururlscriptserverside",data);
});

Como guardar los datos en el lado del servidor esta fuera del ámbito de este libro, pero en resumen el script recibirá los datos por medio del método post, de esta manera puedes procesarlos y guardarlos en tu base de datos.

Bueno hemos terminado de serializar todos los overlays y el mapa. Para ver el ejemplo completo debes abrir el archivo de práctica número “27”. Adicional a este ejemplo podrás encontrar otro en la carpeta número “28”, el cual tiene el mismo ejemplo pero con  ligeras modificaciones para que cada overlay pueda aceptar un título y una descripción escrita por el usuario, te invito que analises el codigo y como practica puedas mejorarlo un poco mas.








No hay comentarios:

Publicar un comentario