jueves, 9 de mayo de 2013

[Dynamic CRM] Actualizar Geo Localización (Latitud/Longitud) con Google Maps

 

Introducción


Si se quiere trabajar con mapas es imprescindible contar con la correcta resolución del posicionamiento de las entidades, la idea es actualizar los campos latitud y longitud de la entidad cuenta

image

Nota:Esta vista que estas observando la cree para poder tener la info de la latitud y longitud listado a simple vista

En este caso la actualización será masiva a un grupo de cuentas, la ejecución se realizara desde un test haciendo uso de una consulta fetchxml para resolver las cuentas que se quieren actualizar

Esta misma técnica se podría aplicar para crear un plug-in que se adjunte a los campos de dirección de la cuenta, ante el cambio de este campo se lanzaría la operación de actualización del posicionamiento global.

 

Obtener información de las cuentas


Haremos uso de las librerías de CRM SDK para poder recuperar las cuentas según la query definida en el fetchxml proporcionado

El primer paso será agregar la referencia a las librerías del SDK

image

En el archivo de configuración se debe definir la url al sitio del CRM que se este utilizando

 

<?xml version="1.0" encoding="utf-8" ?>
<configuration>

  <connectionStrings>
    <add name="CRMServer" connectionString="Url=http://localhost:5555/ContosoHQ;"/>
  </connectionStrings>


  <system.diagnostics>
    <trace autoflush="true" indentsize="4">
      <listeners>
        <remove name="Default" />
        <add name="myListener"  type="System.Diagnostics.TextWriterTraceListener" initializeData="TraceLog.log" />
      </listeners>
    </trace>
  </system.diagnostics>
  
</configuration>

Aquí se define tanto la conexión como la línea que permite definir el trace

En la siguiente imagen se puede observar la utilización la definición de la conexión en la clase que proporciona el SDK

 

SNAGHTML6597704

 

Nota: en este caso desarrolle dentro del propio equipo donde tenia instalado el servidor de CRM por eso utilice localhost, pero es lógico que esto deba cambiarse si se accede de forma remota

El siguiente código permite recuperar la información de las cuentas:

 

public static List<AccountCRM> GetEntityMap(string fetchxml)
{

    List<AccountCRM> entityList = new List<AccountCRM>();

    try
    {
        using (var service = new OrganizationService("CRMServer"))
        {

            EntityCollection entityCol = service.RetrieveMultiple(new FetchExpression(fetchxml));

            foreach (Entity entity in entityCol.Entities)
            {

                AccountCRM account = new AccountCRM()
                {
                    id = (Guid)entity.Attributes["accountid"],
                    razonsocial = entity.Attributes["name"].ToString(),
                    address = entity.Attributes["address1_line1"].ToString(),
                    stateorprovince = entity.Attributes["address1_stateorprovince"].ToString(),
                    country = entity.Attributes["address1_country"].ToString(),
                };

                if (entity.Attributes.ContainsKey("address1_latitude") && entity.Attributes.ContainsKey("address1_longitude"))
                {
                    account.Position = new GeoPosition()
                    {
                        Latitude = Convert.ToDouble(entity.Attributes["address1_latitude"]),
                        Longitude = Convert.ToDouble(entity.Attributes["address1_longitude"]),
                    };
                }


                entityList.Add(account);

            }

            return entityList;
        }
    }
    catch (Exception ex)
    {
        Trace.WriteLine(string.Format("[{0:dd-MM-yyyy HH:mm}] Message:{1}, StackTrace: {2}", DateTime.Now, ex.Message, ex.StackTrace));
        throw;
    }

}

Se utilizo el siguiente fetchxml para recuperar las cuentas que nos interesa actualizar

 

string fetch = @"<fetch version='1.0' count='50' output-format='xml-platform' mapping='logical' distinct='false'>
                  <entity name='account'>
                    <attribute name='accountid' />
                    <attribute name='name' />
                    <attribute name='address1_city' />
                    <attribute name='address1_stateorprovince' />
                    <attribute name='address1_line1' />
                    <attribute name='address1_country' />
                    <attribute name='address1_longitude' />
                    <attribute name='address1_latitude' />
                    <filter type='and'>
                        <condition attribute='statecode' operator='eq' value='0' />
                        <condition attribute='address1_city' value='Buenos Aires' operator='eq'/>
                    </filter>
                  </entity>
                </fetch>";

 

Por supuesto esto es completamente re-definible solo se usa la opción:

SNAGHTML67b89e9

La búsqueda avanzada abre el dialogo que permite definir los filtros que crean la consulta fetchxml

 

Resolver Geo Localización de las cuentas


Para obtener el posicionamiento global de las cuentas según su dirección haremos uso de google maps

 

public static GeoPosition GetGeoPosition(AccountCRM account)
{
    const string _googleUri = "http://maps.googleapis.com/maps/api/geocode/xml?";

    try
    {
        if (string.IsNullOrEmpty(account.address) ||
            string.IsNullOrEmpty(account.stateorprovince) ||
            string.IsNullOrEmpty(account.country))
            return null;

        string url = string.Format("{0}address={1}&components=locality:{2}|country:{3}&sensor=false"
                               , _googleUri
                               , HttpUtility.UrlEncode(account.address)
                               , account.stateorprovince
                               , account.country);

        WebClient client = new WebClient();
        Uri uri = new Uri(url);
        HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);

        XmlDocument doc = new XmlDocument();
        doc.Load(request.GetResponse().GetResponseStream());
        XmlNode root = doc.DocumentElement;
        if (root.SelectSingleNode("/GeocodeResponse/status").InnerText == "OK")
        {
            return new GeoPosition()
            {
                Latitude = Double.Parse(root.SelectSingleNode("/GeocodeResponse/result/geometry/location/lat").InnerText),
                Longitude = Double.Parse(root.SelectSingleNode("/GeocodeResponse/result/geometry/location/lng").InnerText)
            };
        }
        else
            return null;

    }
    catch (Exception ex)
    {
        Trace.WriteLine(string.Format("[{0:dd-MM-yyyy HH:mm}] Message:{1}, StackTrace: {2}", DateTime.Now, ex.Message, ex.StackTrace));
        throw;
    }

}

Solo es cuestión de definir la url de google maps con los valores de dirección, localidad y país, lanzar la ejecución mediante WebClient y procesar la respuesta.

El ultimo paso consiste en actualizar la entidad en CRM

 

public static void UpdatePosition(string fetchxml)
{
    System.Threading.Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("en-US");

    //se obtiene la lista de cuentas
    List<AccountCRM> accountList = GetEntityMap(fetchxml);

    using (var service = new OrganizationService("CRMServer"))
    {
        //se recorre cada cuenta para actualizar el posicionamiento
        foreach (var item in accountList)
        {
            //se recupera las coordenadas de posicionamiento
            GeoPosition position = MapHelper.GetGeoPosition(item);

            //se crea la entidad account 
            //a la cual se le actualizara la info de posicionamiento
            var entity = new Entity("account");
            entity["accountid"] = item.id;

            if (position == null)
            {
                entity["address1_latitude"] = null;
                entity["address1_longitude"] = null;
            }
            else
            {
                entity["address1_latitude"] = position.Latitude;
                entity["address1_longitude"] = position.Longitude;
            }

            service.Update(entity);
        }
    }

}

En la primer línea se define la cultura en en-US para evitar problemas de conversión de tipos cuando se recupera las posiciones de geo localización

Una vez que se recuperan las cuentas se recorren para resolver la posición según al dirección que tengan asignada, según la respuesta de google maps se asignara las coordenadas de localización o en caso de no poder resolver se asigna null.

 

Código


10 comentarios:

  1. Leandro, excelente artículo. Muchas gracias.
    Me encanta ver contenidos de Dynamics CRM en blogs no específicos de CRM!

    Un abrazo!

    ResponderEliminar
  2. Hola. Estoy creando en C# un html para enviar al correo de hotmail p gmail. ENtonces estoy creando una simpre tabla de html :
    img src=\"" + "http://salud_2013.salud.gob.mx/images/mpr.png"

    algo asi como esto, el problema es que cuando lleha el correo no llega la propidad src del img. SAludos

    ResponderEliminar
  3. hola Josengan

    cuando defines el mail asignas la propiedad

    IsBodyHtml

    porque sino no interpretara el body como html

    saludos

    ResponderEliminar
  4. Hola, soy nuevo en esto de visual stufioy c#, me gustaria saber como se consume este ejemplo, desde un proyecto web, desde consola, etc.

    ResponderEliminar
  5. hola Hermes HR

    se consume que cosa ?

    no entendi el planteo que realizas en el contexto del articulo del blog en que lo haces


    saludos

    ResponderEliminar
  6. Lo que pasa es que tengo una base de datos con latitud, longitud y mas datos. y no tengo ni la menor idea de como mostrar los puntos en un mapa de google, espero tu ayuda por favor.
    de antemano, gracias.

    ResponderEliminar
  7. hola

    pero si la idea es mostrar en el mapa deberias analiza el otro articulo:

    [Dynamic CRM] Integrar Google Maps – PopUp Mapa Web (1/2)

    alli ve al javascript que utilizo, donde defino:

    google.maps.Marker()

    se espcifica el posicionamiento de cada punto

    saludos

    ResponderEliminar
  8. Hola Leandro, primero que todo muchas gracias por toda la ayuda que tus publicaciones entregan. Ahora a lo que vinimos. Tengo un problema al intentar crear una solucion web con MVC y EF6, la cosa es que tengo una entidad con una propiedad definida como DbGeography y por el lado en la vista no logro asignar correctamente los valores de longitud y latitud, para entregarlo al controlador y poder hacer la grabacion en mi base de datos.

    ¿Cual seria la forma correcta de asignar en mi vista los datos de ubicacion obtenidos en la geolocalizacion?

    Muchas gracias por la ayuda

    ResponderEliminar
    Respuestas
    1. hola
      Como defines en la View los @Html.TextBoxFor() para mapear la propiedad del model. Esa parte es clave para que al realizar el post al action reciba los valores.
      Si necesitas publicar codigo sugiero que plantes el problema en el foro de msdn
      ASP.NET MVC foro
      En este es mas simple publicar codigo
      saludos

      Eliminar
    2. Leandro, muchas gracias por la respuesta finalmente lo que hice fue crear propiedades distintas para la longitud y la latitud que las muestro en la vista, excluyendo las propiedades dbgeography, ya en el controlador convierto las coordenadas al formato geography y las grabo sin necesidad de tener que presentar en la vista todas las caracteristicas de este tipo de dato.

      Salu2

      Eliminar