Volver al indice
Siguiente paso
Suponiendo que los datos enviados al servidor fueron guardados con éxito en una base de datos, ahora viene la segunda parte de este proceso, recrear todos los overlays y nuestro mapa basados en estos datos, básicamente el proceso será inverso, a esto se le llama : Deserialización.
Los datos almacenados en la base de datos por lo general son obtenidos por medio de un método AJAX o algún servicio web, el cual debe devolver un objeto JSON de preferencia, con todos los datos correspondientes. Para los siguientes ejemplos vamos a suponer que el método AJAX nos ha devuelvo el siguiente objeto basado en los ejemplos anteriores.
{
"overlays":[{"position":"","type":"","id":"","draggable":"","title":"","description":""}],
"map":{"zoom":"","bounds":"","mapType":"","type":"map"}
}
"overlays":[{"position":"","type":"","id":"","draggable":"","title":"","description":""}],
"map":{"zoom":"","bounds":"","mapType":"","type":"map"}
}
Los datos de los overlay se encuentran en la propiedad “overlays”, que es un arreglo de objetos y los datos de nuestro mapa se encuentran en la propiedad “map”. Vamos a crear un método llamado “FakeAjax” el cual nos devolverá este objeto simulando un llamado AJAX al servidor, Para los siguientes ejemplos vamos usar como punto de partida el archivo de práctica número “29” el cual contiene creado el método “FakeAjax”.
Normalmente los objetos JSON que son devueltos como respuesta del servidor, vienen en formato cadena por lo tanto tienes que parsear esa cadena para convertirla a un objeto Javascript por ejemplo usando el método jQuery.parseJSON()
Deserializar Marker
En nuestro archivo de practica numero “29” tenemos el método “FakeAjax”, que nos devuelve un objeto con toda la información que necesitamos recrear. El cual ejecutamos en el evento “idle” de nuestro mapa.
google.maps.event.addListenerOnce(map, 'idle', function() {
var data = FakeAjax();
});
var data = FakeAjax();
});
Ahora necesitamos iterar sobre el arreglo de objetos que contiene la información que necesitamos en la propiedad “overlays”.
google.maps.event.addListenerOnce(map, 'idle', function() {
var data = FakeAjax();
for (var i = 0 ; i < data.overlays.length ; i++)
{
switch(data.overlays[i].type)
{
case "marker":
//marker
break;
case "polyline":
//polyline
break;
case "rectangle":
//rectanlge
break;
case "circle":
//cirlce
break;
case "polygon":
//polygon
break
}
}
});
var data = FakeAjax();
for (var i = 0 ; i < data.overlays.length ; i++)
{
switch(data.overlays[i].type)
{
case "marker":
//marker
break;
case "polyline":
//polyline
break;
case "rectangle":
//rectanlge
break;
case "circle":
//cirlce
break;
case "polygon":
//polygon
break
}
}
});
Usando un ciclo FOR iteramos sobre el arreglo de objetos y para cada elemento del arreglo usamos su propiedad “type”, para saber que tipo de overlay se necesita deserializar usando un switch javascript. Ahora vamos a crear un método llamado “DeserializeMaker”.
google.maps.event.addListenerOnce(map, 'idle', function() {
var data = FakeAjax();
for (var i = 0 ; i < data.overlays.length ; i++)
{
var objectDeserialized;
switch(data.overlays[i].type)
{
case "marker":
objectDeserialized = DeserializeMaker(data.overlays[i]);
break;
case "polyline":
//polyline
break;
var data = FakeAjax();
for (var i = 0 ; i < data.overlays.length ; i++)
{
var objectDeserialized;
switch(data.overlays[i].type)
{
case "marker":
objectDeserialized = DeserializeMaker(data.overlays[i]);
break;
case "polyline":
//polyline
break;
case "rectangle":
//rectanlge
break;
case "circle":
//cirlce
break;
case "polygon":
//polygon
break;
}
objectDeserialized.setMap(map);
}
});
function DeserializeMaker (object) {
var position = object.position.split(',');
var latLng = new google.maps.LatLng(position[0],position[1]);
var marker = new google.maps.Marker({
position: latLng,
draggable:object.draggable
});
return marker;
}
//rectanlge
break;
case "circle":
//cirlce
break;
case "polygon":
//polygon
break;
}
objectDeserialized.setMap(map);
}
});
function DeserializeMaker (object) {
var position = object.position.split(',');
var latLng = new google.maps.LatLng(position[0],position[1]);
var marker = new google.maps.Marker({
position: latLng,
draggable:object.draggable
});
return marker;
}
En nuestro método “DeserializeMarker” estamos recibiendo como parámetro el elemento del arreglo actual sobre el cual se está iterando, este objeto tiene las propiedades que previamente habíamos serializado para recrear el marker, en este caso solo es “position” el cual es una cadena de texto con los valores LatLng de la position del marker separados por coma, usando el método split podemos obtener estos datos como un arreglo. Recuerda que el primer valor es Lat y el segundo es Lng. Este es un ejemplo del objeto que estamos recibiendo:
Object {
position: "-39.198205,-56.162109",
type: "marker",
id: 1,
draggable: true
}
position: "-39.198205,-56.162109",
type: "marker",
id: 1,
draggable: true
}
Después creamos nuestros objetos Latlng y Marker usando las clases de Google Maps, pasando como parámetro los valores obtenidos de la propiedad “position”. Nuestro método devuelve el objeto marker creado, el cual es asignado en la variable “objectDeserialized”, en esta variable asignaremos cada objeto creado por nuestros métodos y al final de cada ciclo FOR usaremos el método “setMap” para asignar el objeto creado al mapa ya que todos los overlays comparten el este metodo.
Deserializar rectangle
Con el overlay rectangle vamos a usar el método “DeserializeRectangle”. A partir de ahora solo haré referencia al código referente al switch para evitar repetir líneas de código continuamente para mostrar los siguientes ejemplos. Ya que el resto de codigo sera el mismo.
switch(data.overlays[i].type)
{
case "marker":
objectDeserialized = DeserializeMaker(data.overlays[i]);
break;
case "polyline":
//polyline
break;
case "rectangle":
objectDeserialized = DeserializeRectangle(data.overlays[i]);
break;
case "circle":
//circle
break;
case "polygon":
//polygon
break;
}
{
case "marker":
objectDeserialized = DeserializeMaker(data.overlays[i]);
break;
case "polyline":
//polyline
break;
case "rectangle":
objectDeserialized = DeserializeRectangle(data.overlays[i]);
break;
case "circle":
//circle
break;
case "polygon":
//polygon
break;
}
function DeserializeRectangle (object) {
var bounds = object.bounds.split(',');
var swLatLng = new google.maps.LatLng(bounds[0],bounds[1]);
var neLatLng = new google.maps.LatLng(bounds[2],bounds[3]);
var rectangleBounds = new google.maps.LatLngBounds(swLatLng,neLatLng);
var rectangle = new google.maps.Rectangle({
bounds: rectangleBounds,
editable:object.editable
});
return rectangle;
}
var bounds = object.bounds.split(',');
var swLatLng = new google.maps.LatLng(bounds[0],bounds[1]);
var neLatLng = new google.maps.LatLng(bounds[2],bounds[3]);
var rectangleBounds = new google.maps.LatLngBounds(swLatLng,neLatLng);
var rectangle = new google.maps.Rectangle({
bounds: rectangleBounds,
editable:object.editable
});
return rectangle;
}
Nuestro método está recibiendo como parámetro el siguiente objeto :
Object {
bounds: "-31.615966,-43.813477,-27.80021,-38.803711",
type: "rectangle",
id: 2,
editable: true
}
bounds: "-31.615966,-43.813477,-27.80021,-38.803711",
type: "rectangle",
id: 2,
editable: true
}
La propiedad “bounds” contiene las coordenadas Latlng en formato cadena necesarios para crear el objeto bounds que necesitamos para recrear nuestro overlay, usando el método split convertimos los datos a un arreglo y los usamos para crear los objetos Latlng. Recuerda que estos puntos vienen en el siguiente orden : "lat_lo, lng_lo, lat_hi ,lng_hi" después de haber usado “toUrlValue” para serializarlo.
Con estos puntos LatLngs creamos nuestro objeto “bounds”, el que es usado para crear nuestro overlay.
Deserializar Polyline
Para el overlay Polyline usaremos el método “DeserializePolyline”
switch(data.overlays[i].type)
{
case "marker":
objectDeserialized = DeserializeMaker(data.overlays[i]);
break;
case "polyline":
objectDeserialized = DeserializePolyline(data.overlays[i]);
break;
case "rectangle":
objectDeserialized = DeserializeRectangle(data.overlays[i]);
break;
case "circle":
//circle
break;
case "polygon":
//polygon
break;
}
{
case "marker":
objectDeserialized = DeserializeMaker(data.overlays[i]);
break;
case "polyline":
objectDeserialized = DeserializePolyline(data.overlays[i]);
break;
case "rectangle":
objectDeserialized = DeserializeRectangle(data.overlays[i]);
break;
case "circle":
//circle
break;
case "polygon":
//polygon
break;
}
Este método recibirá el siguiente objeto como parámetro :
Object {
path: "-39.605688,-49.042969,-39.707187,.....",
type: "polyline",
id: 3,
draggable: true
}
path: "-39.605688,-49.042969,-39.707187,.....",
type: "polyline",
id: 3,
draggable: true
}
La propiedad “path” contiene todos nuestros puntos separados por comas en formato cadena, por lo tanto debemos de crear a partir de ellos un objeto MVCArray par recrear nuestro path.
El método encargado para de esta tarea será “DeserializeMvcArray” :
function DeserializeMvcArray(stringLatlng) {
var arrayPoints = stringLatlng.split(',');
var mvcArray = new google.maps.MVCArray();
for(var i= 0; i < arrayPoints.length; i+=2)
{
var latlng = new google.maps.LatLng(arrayPoints[i],arrayPoints[i+1]);
mvcArray.push(latlng);
}
return mvcArray;
}
var arrayPoints = stringLatlng.split(',');
var mvcArray = new google.maps.MVCArray();
for(var i= 0; i < arrayPoints.length; i+=2)
{
var latlng = new google.maps.LatLng(arrayPoints[i],arrayPoints[i+1]);
mvcArray.push(latlng);
}
return mvcArray;
}
Este método recibe el path en formato cadena, después creamos un arreglo a partir de esta cadena con el método split. Usando la clase MVCArray creamos un objeto, al cual dentro del ciclo FOR, agregamos cada punto Latlng creado, por medio del método push. También nota que nuestro ciclo for itera cada 2 elementos en el arreglo, esto es debido a que el primer elemento es Lat y el siguiente Lng y así sucesivamente, al terminar el ciclo for regresará el objeto MVCArray Creado. Ahora solo nos resta incluir este método en “DeserializePolyline”.
function DeserializePolyline (object) {
var mvcArray = DeserializeMvcArray(object.path);
var polyline = new google.maps.Polyline({
path: mvcArray,
draggable:object.draggable,
editable:true
});
return polyline;
}
var mvcArray = DeserializeMvcArray(object.path);
var polyline = new google.maps.Polyline({
path: mvcArray,
draggable:object.draggable,
editable:true
});
return polyline;
}
En este método sólo nos resta pasar nuestro mvcArray creado a nuestro overlay para crear la polyline.
Deserializar Polygon
Para polygon utilizaremos de nuevo el método “DeserializeMvcArray”, debido a que el path del objeto serializado polygon también es una cadena con los valores separados por comas.
Object {
path: "-36.421282,-50.581055,-37.822802....",
type: "polygon",
id: 5,
draggable: true
}
path: "-36.421282,-50.581055,-37.822802....",
type: "polygon",
id: 5,
draggable: true
}
Crearemos un método llamado “DeserializePolygon” :
switch(data.overlays[i].type)
{
case "marker":
objectDeserialized = DeserializeMaker(data.overlays[i]);
break;
case "polyline":
objectDeserialized = DeserializePolyline(data.overlays[i]);
break;
case "rectangle":
objectDeserialized = DeserializeRectangle(data.overlays[i]);
break;
case "circle":
//circle
break;
case "polygon":
objectDeserialized = DeserializePolygon(data.overlays[i]);
break;
}
function DeserializePolygon (object) {
var mvcArray = DeserializeMvcArray(object.path);
var polygon = new google.maps.Polygon({
paths: mvcArray,
draggable:object.draggable,
editable:true
});
return polygon;
}
{
case "marker":
objectDeserialized = DeserializeMaker(data.overlays[i]);
break;
case "polyline":
objectDeserialized = DeserializePolyline(data.overlays[i]);
break;
case "rectangle":
objectDeserialized = DeserializeRectangle(data.overlays[i]);
break;
case "circle":
//circle
break;
case "polygon":
objectDeserialized = DeserializePolygon(data.overlays[i]);
break;
}
function DeserializePolygon (object) {
var mvcArray = DeserializeMvcArray(object.path);
var polygon = new google.maps.Polygon({
paths: mvcArray,
draggable:object.draggable,
editable:true
});
return polygon;
}
Nuestro método recibe el objeto serializado y crea el MVCArray usando la propiedad path con el método “DeserializeMvcArray”, el que después pasamos como parámetro para crear nuestro overlay.
Deserializar Circle
Para circle usaremos el método llamado “DeserializeCircle” y este es el objeto que recibirá este método :
Object {
center: "-29.688053,-56.513672",
radius: 388514.2326,
type: "circle",
id: 4,
draggable: true
}
center: "-29.688053,-56.513672",
radius: 388514.2326,
type: "circle",
id: 4,
draggable: true
}
Y en nuestro switch
switch(data.overlays[i].type)
{
case "marker":
objectDeserialized = DeserializeMaker(data.overlays[i]);
break;
case "polyline":
objectDeserialized = DeserializePolyline(data.overlays[i]);
break;
case "rectangle":
objectDeserialized = DeserializeRectangle(data.overlays[i]);
break;
case "circle":
objectDeserialized = DeserializeCircle(data.overlays[i]);
break;
case "polygon":
objectDeserialized = DeserializePolygon(data.overlays[i]);
break;
}
{
case "marker":
objectDeserialized = DeserializeMaker(data.overlays[i]);
break;
case "polyline":
objectDeserialized = DeserializePolyline(data.overlays[i]);
break;
case "rectangle":
objectDeserialized = DeserializeRectangle(data.overlays[i]);
break;
case "circle":
objectDeserialized = DeserializeCircle(data.overlays[i]);
break;
case "polygon":
objectDeserialized = DeserializePolygon(data.overlays[i]);
break;
}
function DeserializeCircle (object) {
var center = object.center.split(',');
var centerLatLng = new google.maps.LatLng(center[0],center[1]);
var circle = new google.maps.Circle({
center: centerLatLng,
radius: object.radius,
draggable:object.draggable,
editable:true
});
return circle;
}
var center = object.center.split(',');
var centerLatLng = new google.maps.LatLng(center[0],center[1]);
var circle = new google.maps.Circle({
center: centerLatLng,
radius: object.radius,
draggable:object.draggable,
editable:true
});
return circle;
}
La propiedad “center” es la que contiene nuestro punto Latlng en formato cadena, la cual convertimos a un objeto Latlng Google Maps la cual usamos en las opciones junto con “radius”.
Actualizar Map
Hasta ahora hemos deserializado todos los tipos de overlays, pero todavía nos queda actualizar el mapa con el nivel de zoom , tipo de mapa y el lugar exacto donde estaba el usuario cuando los datos fueron guardados. Usaremos el método llamado “UpdateMap”.
google.maps.event.addListenerOnce(map, 'idle', function() {
var data = FakeAjax();
for (var i=0;i < data.overlays.length;i++)
{
var objectDeserialized;
switch(data.overlays[i].type)
{
case "marker":
objectDeserialized = DeserializeMaker(data.overlays[i]);
break;
case "polyline":
objectDeserialized = DeserializePolyline(data.overlays[i]);
break;
case "rectangle":
objectDeserialized = DeserializeRectangle(data.overlays[i]);
break;
case "circle":
objectDeserialized = DeserializeCircle(data.overlays[i]);
break;
case "polygon":
objectDeserialized = DeserializePolygon(data.overlays[i]);
break;
}
objectDeserialized.setMap(map);
}
UpdateMap(data.map);
});
var data = FakeAjax();
for (var i=0;i < data.overlays.length;i++)
{
var objectDeserialized;
switch(data.overlays[i].type)
{
case "marker":
objectDeserialized = DeserializeMaker(data.overlays[i]);
break;
case "polyline":
objectDeserialized = DeserializePolyline(data.overlays[i]);
break;
case "rectangle":
objectDeserialized = DeserializeRectangle(data.overlays[i]);
break;
case "circle":
objectDeserialized = DeserializeCircle(data.overlays[i]);
break;
case "polygon":
objectDeserialized = DeserializePolygon(data.overlays[i]);
break;
}
objectDeserialized.setMap(map);
}
UpdateMap(data.map);
});
“UpdateMap” es llamado fuera del ciclo FOR debido a que los datos que necesitamos no se encuentran en un arreglo, sino en un objeto simple en la propiedad “map” con las siguientes propiedades:
Object {
zoom: 5,
bounds: "-41.261381,-63.325123,-24.786843,-33.793873",
mapType: "hybrid",
type: "map"
}
zoom: 5,
bounds: "-41.261381,-63.325123,-24.786843,-33.793873",
mapType: "hybrid",
type: "map"
}
Y nuestro método es el siguiente:
function UpdateMap (object){
var bounds = object.bounds.split(',');
var swLatLng = new google.maps.LatLng(bounds[0],bounds[1]);
var neLatLng = new google.maps.LatLng(bounds[2],bounds[3]);
var mapBounds = new google.maps.LatLngBounds(swLatLng,neLatLng);
map.fitBounds(mapBounds);
map.setZoom(object.zoom);
map.setMapTypeId(google.maps.MapTypeId[object.mapType.toUpperCase()]);
}
var bounds = object.bounds.split(',');
var swLatLng = new google.maps.LatLng(bounds[0],bounds[1]);
var neLatLng = new google.maps.LatLng(bounds[2],bounds[3]);
var mapBounds = new google.maps.LatLngBounds(swLatLng,neLatLng);
map.fitBounds(mapBounds);
map.setZoom(object.zoom);
map.setMapTypeId(google.maps.MapTypeId[object.mapType.toUpperCase()]);
}
Como lo hemos hecho anteriormente primero creamos nuestro objeto bounds a partir de la propiedad “bounds” en formato cadena, la cual asignamos con el método “fitBounds” del objeto “map” , seguido asignamos el zoom y el tipo del mapa.
Al asignar el tipo de mapa estamos usando las constantes de mapa de la API que comúnmente son de esta manera :
google.maps.MapType.HYBRID
google.maps.MapType.ROADMAP
google.maps.MapType.SATELLITE
google.maps.MapType.TERRAIN
En nuestro código es un tanto diferente ya que nos basamos en que los objetos en javascript son arreglos asociativos, por lo tanto en nuestra línea de código podemos acceder al valor por medio de su “keyname” y usando el método “toUpperCase” de javascript, nos aseguramos de que sean en letras mayúsculas. Normalmente esto podría hacerse por medio de un switch pero creo que de esta manera nos ahorramos unos lineas de código.
Hasta aquí ha sido todo en este capítulo si quieres ver el ejemplo completo puedes abrir el archivo de práctica en la carpeta número “30”. Adicional a este tambien deberias ver el archivo de ejemplo número “31” el cual contiene una versión modificada con algunos campos adicionales y evento click en la lista de la izquierda para ubicar el overlay.
Una buena option adicional a todo lo visto antes es usar el formato GeoJson , el cual te permite guardar geometrias en una estructura estandar. Recomiendo leer sobre como crear and trabjar con este tipo de objectos por que de esa manera puedes exportar tus datos a muchos lados y muchas base de datos ya soportan este formato.
Para darte una idea de como funciona visita este editor online de GeoJson y revisa como el objecto es armado.
En google tambien han escrito este articulo sobre GeoJson muy bueno sobre como usar este estandar dentro de google maps.
Usar GeoJson es una buena option
Una buena option adicional a todo lo visto antes es usar el formato GeoJson , el cual te permite guardar geometrias en una estructura estandar. Recomiendo leer sobre como crear and trabjar con este tipo de objectos por que de esa manera puedes exportar tus datos a muchos lados y muchas base de datos ya soportan este formato.
Para darte una idea de como funciona visita este editor online de GeoJson y revisa como el objecto es armado.
En google tambien han escrito este articulo sobre GeoJson muy bueno sobre como usar este estandar dentro de google maps.
Una vez que entiendas como crear y manipular GeoJson recomiendo que estudies esta libreria http://turfjs.org/ , con ella podras hacer todo tipo de operaciones que pensabas que eran imposibles.
La consulta anterior solo funcionara con puntos en norteamerica, si quieres un consulta que funcione nivel mundial por favor visitar el siguiente link de stackoverflow
http://stackoverflow.com/questions/4834772/get-all-records-from-mysql-database-that-are-within-google-maps-getbounds/20741219#20741219
Hay muchas respuestas recomiendo que verifiques todas y descubras cual funciona para ti. (Yo probe la del usuario vladimir)
Ejemplos Adicionales
Obtener puntos Latlng contenidos dentro de un bounds
Aunque este libro no cubre lenguajes del lado del servidor, considero que el siguiente caso es muy común entre el desarrollo de aplicaciones de mapas, por tal razón he decidido incluirlo.
Habrá algunas ocasiones donde solo queremos mostrar overlays o markers que solo se encuentran dentro del actual viewport del mapa. Para lograr esto solo tienes que obtener el bounds del mapa el cual contiene un método llamado :
bounds.contains(latLng:LatLng) : El cual devuelve “true” si el Latlng que has pasado como parámetro se encuentra dentro de él.
Usando la API de Google Maps es realmente fácil saber esto, pero esto quiere decir que tienes que cargar todos los puntos Latlng antes en algun arreglo e iterar en cada uno. Lo conveniente seria que solo obtuvieras desde tu base de datos los puntos latlng que se encuentran dentro de este bounds.
Por ejemplo imagina que tienes un en tu base de datos una tabla llamada “markers” y esta tabla contiene 2 campos llamados “Latitude” y “longitud”, donde previamente han sido guardados estos valores para 1000 registros, en lugar de cargar estos mil registros de una sola vez en tu mapa , solo queremos obtener los que se encuentran dentro del bounds del mapa actual.
Para realizar esto tienes que enviar al servidor tu coordenadas del bounds actual del mapa, esto lo puedes hacer por medio del método :
bounds.toUrlValue(precision?:number) : que devuelve una cadena con los puntos en el siguiente orden : "lat_lo,lng_lo,lat_hi,lng_hi" .
Una vez que obtengas estos datos del lado del servidor solo tienes que ejecutar la siguiente consulta SQL, que sólo devolverá los registros con los Latlng que se encuentran dentro de bounds indicado.
SELECT * FROM markers
WHERE
Latitude < "lat_hi" AND
Latitude > "lat_lo" AND
Longitude < "lng_hi" AND
Longitude > "lng_lo";
La consulta anterior solo funcionara con puntos en norteamerica, si quieres un consulta que funcione nivel mundial por favor visitar el siguiente link de stackoverflow
http://stackoverflow.com/questions/4834772/get-all-records-from-mysql-database-that-are-within-google-maps-getbounds/20741219#20741219
Hay muchas respuestas recomiendo que verifiques todas y descubras cual funciona para ti. (Yo probe la del usuario vladimir)
Cálculo de áreas y distancias
Google Maps pone a nuestra disposición la librería “Geometry Library” con la cual podremos realizar cálculos de datos geométricos sobre la superficie de la Tierra. Similar a otras librerías con las que hemos trabajado anteriormente, tenemos que incluirla en nuestro proyecto de la siguiente manera:
<script type="text/javascript" src="http://maps.googleapis.com/maps/api/js?sensor=false&libraries=geometry"></script>
El uso de los métodos de esta librería es tan sencillo como solamente llamar al método estático deseado para realizar la operación. He preparado 2 ejemplos de cómo usar esta librería para calcular áreas y distancias entre puntos en el mapa. Animo al lector a revisar el código para su comprender cómo funciona.
En el ejemplo de práctica en la carpeta número “32”, puedes encontrar un ejemplo de cómo se calcula la distancia entre 2 puntos. Al mover los markers sobre el mapa podrás ver como en el panel izquierdo se actualiza la distancia entre estos puntos, la cual es devuelta en metros.
Y en el ejemplo de práctica en la carpeta número “33”, podras encontrar el código de ejemplo de como calcular el área de las overlays en Google Maps, las cuales son expresadas en metros cuadrados. Si dibujas una overlay, podrás ver como se actualiza el panel izquierdo con la información del área. Para las polyline se esta calculando la distancia total en metros.
Hace un tiempo atras cree un jquery plugin que te permite crear un mapa con puntos en el mapa de manera rapida y sencilla, por favor hecha un vistado talvez te pueda ser muy util.
Jquery plugin google maps EasyLocator
EasyLocator
Hace un tiempo atras cree un jquery plugin que te permite crear un mapa con puntos en el mapa de manera rapida y sencilla, por favor hecha un vistado talvez te pueda ser muy util.
Jquery plugin google maps EasyLocator
No hay comentarios:
Publicar un comentario