Google Maps API v.3.0 y ASP.NET MVC 3 con Razor

Gracias a todas las APIs que nos facilita Google, podemos hacer uso prácticamente de todos los servicios que ofrece la compañía a día de hoy. En este post en concreto me gustaría hablaros de cómo utilizar el API de Google Maps en su versión 3.0 para generar mapas dinámicos con una enorme cantidad de funciones. Además, para rizar más el rizo utilizaré ASP.NET MVC 3 con Razor para crear una llamada síncrona y otra asíncrona con la ayuda de JQuery.

Referencias

Para utilizar el API de Google Maps son necesarias las siguientes referencias:

<script src="@Url.Content("~/Scripts/jquery-1.4.4.min.js")" type="text/javascript"></script>
<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script>

Nota: Cuando utilizas Google Maps siempre debe especificarse si vas a utilizar un sensor, por ejemplo un GPS, para determinar la ubicación del usuario (importante para los dispositivos móviles). En este caso estableceremos el valor del parámetro sensor a false ya que no entra este escenario dentro del post 😉

¿Por dónde empiezo?

Lo primero que debemos tener en cuenta es que podemos ubicar marcas de dos formas distintas: Indicando las coordenadas del sitio o recuperando las coordenadas a través de una dirección postal. Sin embargo, en el segundo caso tenemos una limitación por parte de la API, la cual sólo nos permite solicitar unas 10 direcciones por consulta, lo cual limita nuestro campo de acción. En este post utilizaremos coordenadas para poder agregar tantas marcas como sea necesario 😀 Para ello, he creado un objeto de prueba que almacenará todas las marcas y un repositorio,  también de prueba, que nos devolverá tres objetos dentro de una lista para la demo.

Objeto de prueba

namespace GoogleMapsASPNETMVC3.Models
{
    public class GoogleMarker
    {
        public string SiteName { get; set; }
        public double Latitude { get; set; }
        public double Longitude { get; set; }
        public string InfoWindow { get; set; }
    }
}

Repositorio de prueba


using System.Collections.Generic;

namespace GoogleMapsASPNETMVC3.Models
{
    public class MarkerRepository
    {

        public IList<GoogleMarker> GetMarkers()
        {
            var googleMarkers = new List<GoogleMarker>
                                    {
                                        new GoogleMarker
                                            {
                                                SiteName = "Jardines de Sabatini",
                                                Latitude = 40.421749,
                                                Longitude = -3.713994,
                                                InfoWindow = "InfoWindow de los Jardines de Sabatini"
                                            },
                                        new GoogleMarker
                                            {
                                                SiteName = "Campo del Moro",
                                                Latitude = 40.419658,
                                                Longitude = -3.718801,
                                                InfoWindow = "InfoWindow del Campo del Moro"
                                            },
                                        new GoogleMarker
                                            {
                                                SiteName = "Parque de la Cornisa",
                                                Latitude = 40.413254,
                                                Longitude = -3.716483,
                                                InfoWindow = "InfoWindow del Parque de la Cornisa"
                                            }
                                    };

            return googleMarkers;
        }
    }
}

Llamada síncrona

Para la llamada síncrona lo que vamos a hacer es solicitar la lista de marcas y pasarla a la vista para poder asociarla a la propiedad Model.

[HttpGet]
public ActionResult Sync()
{
     return View(_markerRepository.GetMarkers());
}

Una vez en la vista agregaremos el siguiente código:

@model IEnumerable<GoogleMapsASPNETMVC3.Models.GoogleMarker>
@using System.Threading;
@using System.Globalization;
@{
    ViewBag.Title = "Sync";
    Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");
}
<script language="javascript" type="text/javascript">
    $(document).ready(function () {
    var bounds = new google.maps.LatLngBounds();
    var options = {
        zoom : 14,
        mapTypeId: google.maps.MapTypeId.TERRAIN
    };
    var googleMap = new google.maps.Map($("#map")[0],options);
    var infoWindow = new google.maps.InfoWindow({ content: "Cargando..." });
    @foreach (var marker in Model)
    {
    <text>
        var point = new google.maps.LatLng(@marker.Latitude, @marker.Longitude);
        bounds.extend(point);

        var marker = new google.maps.Marker({
                position: point,
                map: googleMap,
                icon:'/Content/images/cloud_marker.png',
                html: '@marker.InfoWindow'
            });

        google.maps.event.addListener(marker, "click", function () {
                infoWindow.setContent(this.html);
                infoWindow.open(googleMap, this);
            });
    </text>
    }
    googleMap.fitBounds(bounds);
    });
</script>
<h2>
    Sync</h2>
<div id="map" style="width: 800px; height: 500px;"></div>

Lo primero que hacemos en la parte javascript es la inicialización de unas variables bases como son bounds donde vamos a insertar cada uno de los puntos de coordenadas, googleMap que asignará el espacio dedicado al mapa a un div con id “map” situado abajo del todo e infoWindow que nos servirá para asociar un pop up a cada marca del mapa.

Posteriormente utilizamos la @ para inicializar un foreach, ya que estamos utilizando el motor de vistas Razor, y recorremos cada una de las marcas ubicadas en el Model del tipo GoogleMarker. Como vamos a mezclar código javascript con variables de servidor, debemos hacer el uso de <text></text> para que no interprete todo el código dentro del foreach como código de servidor.

Los pasos que vamos a realizar en cada iteración es la creación de cada conjunto de coordenadas en el mapa a través de google.maps.LatLng para acto seguido usar el resultado como la posición de la marca en el objeto google.maps.Marker en el mapa asociado. En este último podemos asociar distintos tipos de opciones como por ejemplo el icono que queremos mostrar para dicha marca, sombreados, etcétera. En este caso he modificado la imagen por una nube 🙂

Además podemos asociar un evento a cada marca para mostrar el típico pop up que aparece cuando haces clic sobre una de ellas. Para ello utilizaremos google.maps.event.Listener donde crearemos la asociación con la clase InfoWindow instanciada al inicio del script.

Por otro lado, si os fijáis de nuevo en el código anterior, he cambiado la cultura a en-US. Esto es debido a que es necesario respetar el signo de separación de los decimales en las coordenadas ya que, de lo contrario, la latitud y longitud recuperadas de la propiedad Model se pintarían con comas y no se mostraría correctamente.

Como resultado obtendríamos la siguiente imagen 😀

Llamada asíncrona

Para la llamada asíncrona el escenario cambia:

@{
    ViewBag.Title = "Async";
}
<script language="javascript" type="text/javascript">
    $(document).ready(function () {

        var bounds = new google.maps.LatLngBounds();
        var options = {
            zoom: 14,
            mapTypeId: google.maps.MapTypeId.TERRAIN
        };
        var googleMap = new google.maps.Map($("#map")[0], options);
        var infoWindow = new google.maps.InfoWindow({ content: "Cargando..." });

        $.ajax({
            type: "POST",
            url: "GetMarkersAsync",
            datatype: "json",
            success: function (data) {
                for (var i = 0; i < data.length; i++) {

                    var point = new google.maps.LatLng(data[i].Latitude, data[i].Longitude);

                    bounds.extend(point);

                    var marker = new google.maps.Marker({
                        position: point,
                        map: googleMap,
                        html: data[i].InfoWindow
                    });

                    google.maps.event.addListener(marker, "click", function () {
                        infoWindow.setContent(this.html);
                        infoWindow.open(googleMap, this);
                    });
                }
            }
        });

        googleMap.fitBounds(bounds);
    });
</script>
<h2>
    Async</h2>
<div id="map" style="width: 800px; height: 500px;">
</div>

En este segundo caso estamos utilizando JQuery para la llamada Ajax a una acción llamada GetMarkersAsync, la cual nos devolverá un listado de objetos en JSON con el mismo formato que nuestro objeto de prueba. Para ello, crearemos una acción HttpPost que retorne dicho listado basado en la clase GoogleMarker.

[HttpPost]
public ActionResult GetMarkersAsync()
{
    return Json(_markerRepository.GetMarkers());
}

En este caso el resultado es el mismo que en la llamada síncrona a excepción de los iconos utilizados en las marcas, ya que en el segundo ejemplo he omitido la propiedad icon para que utilice el icono por defecto de Google.

Adjunto el proyecto por si fuera de utilidad 😀

Más información sobre la Google Map API 3.0

¡Saludos!

34 Comments

  1. Pancho Febrero 9, 2011 at 6:08 pm

    Excelente como siempre, y me encanta la sintaxis del Razor view engine
    saludos

    Reply
    1. Gisela Febrero 11, 2011 at 4:10 pm

      ¡Gracias Pancho! 😀

      ¡Saludos!

      Reply
  2. Sen Febrero 10, 2011 at 12:50 am

    Excelente artículo, como de costumbre.

    Saludos

    Reply
    1. Gisela Febrero 11, 2011 at 4:11 pm

      Muchas gracias Sen 🙂

      ¡Saludos!

      Reply
  3. sebastian Febrero 10, 2011 at 6:08 pm

    excelente gracias!

    Reply
    1. Gisela Febrero 11, 2011 at 4:11 pm

      Muchas gracias sebastian 😀

      ¡Saludos!

      Reply
  4. ggwilly Marzo 11, 2011 at 6:54 am

    Hola Gis
    Mi nombre es Guillermo, estoy tratando de representar la mejor forma con Razor pero no doy en la tecla:

    A esto tengo que llegar

    * Inicio
    * Artículos
    * Stock
    * Proveedores
    * Clientes
    * Facturas
    * Administración

    Mi intento hasta el momento es

    @foreach (var item in Model.MainMenuItems)
    {

    @Html.ActionLink(item.Text, item.Action, item.Controller)

    }

    Me falta poner el y dentro de la etiqueta A y delimitar a item.Text con ellos.

    Gracias por cualquier ayuda !!!
    Saludos desde Argetina

    Guille

    Reply
    1. Gisela Abril 3, 2011 at 10:50 am

      Perdona por mi tardanza ggwilly 🙁

      ¿Conseguiste solucionar tu problema?

      ¡Saludos!

      Reply
  5. MalcolM Marzo 18, 2011 at 3:15 pm

    Aunque!… el servicio de google en una aplicación propia es pago…. lo averigüe
    (((-.-))) y esta dentro de las condiciones de la API… El echo del uso esta todo bien, pero en cuando sacas provecho (algo tan simple como mostrar direcciones) en tu aplicación web se garpa una cuota.–> u$s.

    Reply
    1. Gisela Abril 3, 2011 at 10:54 am

      Hola MalcolM, perdona por mi tardanza 🙁

      Según estuve leyendo en la página de FAQ del API de Google Maps depende de cuál sea el uso de tu sitio web. Cito literalmente:

      ¿Puedo utilizar Google Maps API en un sitio web comercial?

      Puedes utilizar Google Maps API siempre que tu sitio sea accesible desde el punto de vista general a los clientes de forma gratuita. Por ejemplo, si tu sitio web recibe ingresos a través de la publicidad, es probable que esté dentro de las condiciones del servicio del API de Google Maps. Si cobras a aquellas personas que desean incluir información en tu mapa (por ejemplo, anunciar una casa en venta), pero muestras esa información mediante el API de Google Maps en una sección de acceso gratuito de tu sitio, también cumples las condiciones del servicio del API de Google Maps.

      Sin embargo, hay usos comerciales que no están permitidos. Si tu sitio cumple alguno de los siguientes criterios, deberás utilizar el API de Google Maps (edición premier):

      El sitio solo está disponible para clientes de pago.
      Solo se puede acceder al sitio a través de una red intranet o desde dentro de la empresa.

      Recuerda que Google se reserva el derecho de suspender o finalizar el uso del API de Google Maps en cualquier momento, por lo que te recomendamos que leas detenidamente las condiciones del servicio.

      ¡Saludos!

      Reply
  6. Juan Carlos Heredia Marzo 19, 2011 at 11:33 am

    Interesante artículo. Una combinación importante de tecnologías disponibles para proyectos Web que hoy cada vez somos más los que usamos la geolocalización como punto de referencia en las páginas.

    Gracia por el aporte.

    Reply
    1. Gisela Abril 3, 2011 at 10:56 am

      Gracias por tu comentario Juan Carlos 🙂 Me alegra que te sea de utilidad 😀

      ¡Saludos!

      Reply
  7. Idmer Cruz Clemente Septiembre 27, 2011 at 10:10 pm

    Hola amigos quiero mostrar una foto que tengo con google maps es posible y como?? los agradecere

    Reply
  8. Illidan Enero 3, 2012 at 5:03 am

    Thank you. even this is not english blog but the code is english. this post really helps me.

    Gracias!

    Reply
    1. Gisela Enero 8, 2012 at 7:08 pm

      Thanks for your comment, Illidan! I’m glad this is helpful for you 🙂

      Cheers!

      Reply
  9. mcartur Marzo 5, 2012 at 3:47 pm

    Muchas gracias por el artículo y por poner el proyecto. Me ha sido de gran utilidad.

    Reply
    1. Gisela Marzo 6, 2012 at 8:51 am

      Gracias por tu comentario mcartur 🙂

      Me alegra que te haya servido 😀

      ¡Saludos!

      Reply
  10. Omar Gameel Marzo 15, 2012 at 11:45 am

    Thank you so much, great article 🙂

    Reply
    1. Gisela Mayo 21, 2012 at 11:32 am

      Thanks for your comment Omar!

      Cheers! 🙂

      Reply
      1. Djon Enero 28, 2013 at 7:20 pm

        Working on it. Trying to figure out the best way to aggergate ride stats. Have you ever used Google fusion? I need to get to a desktop cause I can’t tell what it is trying to do using my phone.

        Reply
  11. Adrian Mayo 10, 2012 at 3:45 pm

    buen dia
    disculpa es que soy nuevo en este tema y pues quisiera saber como hago para colocar otras cordenadas al inicio es que para el proyecto que estoy desarrollando
    necesito colocar a colombia y no he encontrado en donde colocarlo muchas gracias

    Reply
    1. Gisela Mayo 21, 2012 at 11:32 am

      Hola Adrian, ¿Tienes la coordenadas de dónde quieres posicionar el mapa?

      ¡Saludos!

      Reply
  12. Eder Mayo 21, 2012 at 7:57 am

    Hola, muy weno tu aporte. Tengo una pequenia consulta, cuando corro el ejercicio me abre de ves en cuando, no s a que se pueda deber. Muchas gracias atentamente

    Reply
    1. Gisela Mayo 21, 2012 at 11:32 am

      Hola Eder,

      No entiendo muy bien tu pregunta. ¿Te da algún tipo de error el código?

      ¡Saludos!

      Reply
  13. Leonardo Mayo 23, 2012 at 2:32 am

    Excelente aporte, es claro y conciso. Voy a seguir mirando tu blog porque me pareció interesante. Muchas gracias!

    Reply
    1. Gisela Mayo 23, 2012 at 6:53 am

      Gracias por tu comentario Leonardo, me alegra que te sea de utilidad 😀

      ¡Saludos!

      Reply
  14. Sreekanth Junio 13, 2012 at 8:48 am

    Thank you….very helpful article.

    Reply
    1. Gisela Junio 13, 2012 at 2:20 pm

      Thanks for your comment Sreekanth 🙂

      Reply
  15. marco Diciembre 12, 2012 at 4:58 pm

    me encanta este ejemplo esta excelente, solo una consulta mas, ¿como hago para obtener la longitud y latitud de un punto dando click en cualquier lado del mapa?

    Reply
  16. Ariel Mayo 2, 2013 at 6:59 pm

    Buenas tardes!! excelente artículo.
    Lo que yo quería hacer era mostrar una dirección con un ícono, y las demás con el otro.
    Se puede modificar el for each sin ningún problema?
    Supongamos que en el primer recorrido muestro un lugar con un ícono, mientras que en los siguientes recorridos muestro todos los demás con otro.
    Gracias de antemano!!

    Reply

Deja tu comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.