Georreferenciando de manera fácil y simple con GeoNames utilizando C#/Mono

Introducción.

Últimamente he tenido ganas de desarrollar en C# para retomar este lenguaje que tengo un poco abandonado y al mismo tiempo probar los avances que ha tenido el proyecto Mono.  Que mejor inicio que desarrollar una aplicación muy simple para georreferenciar ciudades aprovechando el servicio web de Geonames, esta a su vez me servirá para complementarla y solucionar un problema de información que tengo para otro proyecto.

Como se podrá apreciar, el código es muy sencillo ya que no utilizo ninguna librería adicional a las estándar, sin embargo es interesante y puede serle de utilidad a alguien mas.

Cuál es la necesidad ?

El problema que se tiene es la necesidad de georreferenciar ubicaciones, es decir, conociendo su nombre obtener su ubicación en términos de latitud y longitud.

Si estoy en Manizales, Caldas, Colombia estaré realmente en latitud 5.07, longitud -75.52056.

Es necesario tener en cuenta que muchas veces el servicio de georreferenciación retorna múltiples resultados ya que pueden haberse referenciado varios sitios con el mismo nombre, haberse cambiado el nombre de la ubicación o tenerse varios hitos de diferente nivel administrativo.  En Geonames esto último se identifica mediante los campos de características.  Consulte el sitio web de Geonames para mas información acerca de los feature codes.

Cómo es el servicio web ?

El servicio web de búsquedas de Geonames recibe una cadena de texto con la ubicación que se desea georreferenciar y retorna una cadena de texto en formato XML (en este caso) con los resultados obtenidos.

Por ejemplo, la consulta de Manizales, Caldas, Colombia se realizaría a través del siguiente URL.

http://ws.geonames.org/search?lang=es&type=xml&style=FULL&q=Manizales,Caldas,Colombia

Retornará el siguiente resultado.

<geonames style="FULL">
    <totalResultsCount>2</totalResultsCount>
    <geoname>
        <toponymName>Manizales</toponymName>
        <name>Manizales</name>
        <lat>5.07</lat>
        <lng>-75.52056</lng>
        <geonameId>3675443</geonameId>
        <countryCode>CO</countryCode>
        <countryName>Colombia</countryName>
        <fcl>P</fcl>
        <fcode>PPLA</fcode>
        <fclName>city, village,...</fclName>
        <fcodeName>seat of a first-order administrative division</fcodeName>
        <population>357814</population>
        <alternateNames>Manisales,Manisalesas,Manizales,Манисалес</alternateNames>
        <elevation/>
        <continentCode>SA</continentCode>
        <adminCode1>37</adminCode1>
        <adminName1>Caldas</adminName1>
        <adminCode2/>
        <adminName2/>
        <alternateName lang="en">Manizales</alternateName>
        <alternateName lang="pl">Manizales</alternateName>
        <alternateName lang="lt">Manisalesas</alternateName>
        <alternateName lang="bg">Манисалес</alternateName>
        <alternateName lang="cs">Manizales</alternateName>
        <alternateName lang="link">http://en.wikipedia.org/wiki/Manizales</alternateName>
        <alternateName lang="de">Manizales</alternateName>
        <alternateName lang="sv">Manizales</alternateName>
        <alternateName lang="es">Manizales</alternateName>
        <alternateName lang="no">Manizales</alternateName>
        <alternateName lang="pt">Manizales</alternateName>
        <timezone dstOffset="-5.0" gmtOffset="-5.0">America/Bogota</timezone>
        <score>1.0</score>
    </geoname>
    <geoname>
        <toponymName>Manizales</toponymName>
        <name>Manizales</name>
        <lat>5.08333</lat>
        <lng>-75.5</lng>
        <geonameId>3675444</geonameId>
        <countryCode>CO</countryCode>
        <countryName>Colombia</countryName>
        <fcl>A</fcl>
        <fcode>ADM2</fcode>
        <fclName>country, state, region,...</fclName>
        <fcodeName>second-order administrative division</fcodeName>
        <population/>
        <alternateNames/>
        <elevation/>
        <continentCode>SA</continentCode>
        <adminCode1>37</adminCode1>
        <adminName1>Caldas</adminName1>
        <adminCode2>3675444</adminCode2>
        <adminName2>Manizales</adminName2>
        <timezone dstOffset="-5.0" gmtOffset="-5.0">America/Bogota</timezone>
        <score>0.3448537290096283</score>
    </geoname>
</geonames>

Finalmente la tarea de la aplicación que consuma el servicio será procesar el documento XML y obtener la información que necesite.

Cómo es la implementación ?

Las librerías (namespaces) utilizados son los siguientes.

using System;
using System.Net;
using System.Text;
using System.Xml;
using System.Collections.Generic;

Se prepara el URL para consumir el servicio con la ubicación a geocodificar.  En este caso, especifico una por defecto si no suministra una.

string location = (args.Length == 0) ? "Manizales, Caldas, Colombia" : args[0];
string url = "http://ws.geonames.org/search?lang=es&type=xml&style=FULL&q=" + location;

Se consume el servicio web y se obtiene la cadena de texto con la respuesta en formato XML.

WebClient client = new WebClient();
string xml = client.DownloadString(url);

Se crea un objeto para manipular el documento XML recién recibido, se obtienen los nodos relacionados con la respuesta de la georreferenciación (geoname)  y se verifica que haya registros, de lo contrario no habrá nada que hacer.

XmlDocument doc = new XmlDocument();
doc.LoadXml(xml);

XmlNodeList geonames = doc.GetElementsByTagName("geoname");

if(geonames.Count == 0)
 throw new Exception("There is no geonames in the result!");
else
 Console.WriteLine("I found " + geonames.Count + " places!n");

Se recorren los resultados obtenidos extrayéndoles la información georreferenciada que se requiera.

foreach(XmlElement place in geonames)
{
 Console.WriteLine("* LUGAR: " + place["name"].InnerText + "n");

 Console.WriteLine("tNombre: "          + place["name"].InnerText);
 Console.WriteLine("tDepartamento: "    + place["adminName1"].InnerText);
 Console.WriteLine("tLatitud: "         + place["lat"].InnerText);
 Console.WriteLine("tLongitud: "        + place["lng"].InnerText);
 Console.WriteLine("tPaís: "            + place["countryName"].InnerText +
                   " (" + place["countryCode"].InnerText + ")");
 Console.WriteLine("tPoblación: "       + place["population"].InnerText + " habitantes");
 Console.WriteLine("tPaís: "            + place["countryName"].InnerText +
                   " (" + place["countryCode"].InnerText + ")");
 Console.WriteLine("tCaracterísticas: " + place["fcl"].InnerText + "/" + place["fcode"].InnerText +
                   "> " + place["fclName"].InnerText + " (" + place["fcodeName"].InnerText + ")");

 Console.WriteLine();
}

Cómo construyo el programa ?

Si se utiliza VisualStudio/MonoDevelop sólo es necesario abrir la solución (incluída en el paquete de demostración) y ejecutar el comando de Build o Run del menú.

Si se desea realizar manualmente deberá ejecutarse un comando como el siguiente.

$ gmcs “/out:GeoCoderSimple.exe” “/r:/usr/lib/mono/2.0/System.dll” “/r:/usr/lib/mono/2.0/System.Xml.dll” /t:exe “geonames_search_raw/Main.cs

Donde geonames_search_raw/Main.cs es el código fuente de la aplicación y GeoCoderSimple.exe será el archivo binario resultante.

Cómo ejecuto el ejemplo ?

Ya sea invocándolo desde el IDE o desde línea de comando, el programa recibe un argumento.  En caso de obviarse este argumento se utilizará el valor por defecto.

$ ./GeoCoderSimple.exe

I found 2 places!

* LUGAR: Manizales

 Nombre: Manizales
 Departamento: Caldas
 Latitud: 5.07
 Longitud: -75.52056
 País: Colombia (CO)
 Población: 357814 habitantes
 País: Colombia (CO)
 Características: P/PPLA> city, village,... (seat of a first-order administrative division)

* LUGAR: Manizales

 Nombre: Manizales
 Departamento: Caldas
 Latitud: 5.08333
 Longitud: -75.5
 País: Colombia (CO)
 Población:  habitantes
 País: Colombia (CO)
 Características: A/ADM2> country, state, region,... (second-order administrative division)

Enlaces.

Convertir coordenadas geográficas grados-minutos-segundos a notacional decimal (2): versión simple

Introducción.

El método que utilizo habitualmente para realizar la conversión entre notaciones de coordenadas se basa en el método descrito anteriormente y es mas simple aunque aparentemente tiene un ligeramente mayor margen de pérdida de decimales en los cálculos.

A continuación se describe rápidamente las fórmulas matemáticas utilizadas para estos cálculos y se presentan los enlaces para la aplicación de deomstración y el formulario en Excel para pruebas.

Procedimiento.

Notación Grados-Minutos-Segundos a notación decimal.

latitude_sign = (latitude_dir == “N”) ? 1 : -1;
latitude  = (latitude_deg  + (latitude_min / 60.0)  + (latitude_sec / 60.0 / 60.0))  * latitude_sign;

longitude_sign = (longitude_dir == “E”) ? 1 : -1;
longitude = (longitude_deg + (longitude_min / 60.0) + (longitude_sec / 60.0 / 60.0)) * longitude_sign;

Notación Decimal a notación Grados-Minutos-Segundos.

latitude_deg = Math.floor(Math.abs(latitude));
latitude_min = Math.floor((Math.abs(latitude) – latitude_deg) * 60);
latitude_sec = Math.ceil(((Math.abs(latitude) – latitude_deg) * 60 – latitude_min) * 60);
latitude_dir = (latitude_sign > 0) ? “N” : “S”;

longitude_deg = Math.floor(Math.abs(longitude));
longitude_min = Math.floor((Math.abs(longitude) – longitude_deg) * 60);
longitude_sec = Math.ceil(((Math.abs(longitude) – longitude_deg) * 60 – longitude_min) * 60);
longitude_dir = (longitude_sign > 0) ? “E” : “W”;

Enlaces.

Convertir coordenadas geográficas grados-minutos-segundos a notacional decimal

Historial.

Versión 0.2 Versión mejorada apropiando el código de  Anthony T. Holdener.
(mayor precisión en los cálculos) 
Versión 0.1a Simplificación de los cálculos realizados.
Versión 0.1 Basado en la implementación de la aplicación de FCC.
(este artículo) 

Introducción.

Finalmente pude encontrar el método para convertir las coordenadas geográficas desde la notación grados-minutos-segundos a la notación decimal y viceversa.  La aplicación de la FCC para esta conversión me ayudó bastante.

La utilidad de esta conversión radica en que muchas veces se manejan las coordenadas en el formato GMS (latitud 5o4’59” norte y longitud 75o30’0″ oeste) mientras que muchas páginas web y aplicaciones (incluído Google Maps) requieren la información en la notación decimal (latitud 5.083056 y longitud -75.5).

Durante los experimentación se creó una clase en Javascript que expone dos métodos simplificados para realizar estas conversiones.  Los cálculos pueden ser fácilmente migrados a otros lenguajes de programación ya que sólo utilizan funciones matemáticas como floor, abs y round además de los operadores aritméticos.

Demostración.

Para la prueba y demostración de la clase Javascript se desarrolló un formulario muy sencillo que permite realizar la transformación de coordenadas geográficas entre las dos notaciones mencionadas.

Demostración de la conversión de coordenadas
Demostración de la conversión de coordenadas

En la demostración puede verificar las coordenadas enunciadas anteriormente las cuales corresponden con la ciudad de Manizales/Caldas/Colombia.

Notación GMS.

  • latitud: 5o4’59” N
  • longitud: 75o30’0″ W

Notación decimal.

  • latitud: 5.083056
  • longitud: -75.5

Enlaces.

Introducción a StaticMaps de Google

Introducción

StaticMaps es una herramienta de Google que nos permite integrar en nuestras aplicaciones a imagenes estáticas de su cartografía de una manera muy simple.  Como se puede inferir, para utilizarlos es necesario que la aplicación tenga acceso a Internet y su acceso se realiza a través de un requerimiento HTTP convencional con ciertos parámetros que se verán a continuación y que permiten refinar el contenido de la imagen del mapa producido.

Esta herramienta la he utilizado varias veces en diferentes tipos de proyectos como Mi Primer Moblet (móviles), GeoReferenciación con Java (escritorio) y la demostración de MandarinaSocial (agentes de software).

Para su uso se requiere que el desarrollador registre de manera gratuita el dominio desde donde se va a utilizar el servicio.  El registro se materializa a través de una llave de API particular.  Esta llave se solicita a través del siguiente enlace.

http://code.google.com/apis/maps/signup.html

El servicio se restringe a un máximo de 1000 requerimientos únicos diarios, siendo este límite aplicado por solicitante (ubicación) y no por llave lo que disminuye el impacto de esta restricción, mas aún cuando las solicitudes repetidas de la misma imagen no son tomadas en cuenta para la estadística de la cuota.

Un primer acercamiento a la herramienta consiste en jugar un poco con ella utilizando el Static Map Wizard.

Forma de acceso

Como se mencionó anteriormente, la solicitud se inicia requiriendo un URL con ciertos parámetros a través de GET y el servicio retorna una imagen en un formato compatible con web que puede ser insertada en una página web con una etiqueta <img> o puede ser manipulada por una aplicación desarrollada en cualquier lenguaje de programación como Java o C#.

Los siguientes aspectos del StaticMap pueden ser parametrizados a través de los parámetros del URL.

  • La ubicación del mapa.
  • El tamaño de la imagen generada.
  • El nivel de acercamiento.
  • El tipo de mapa.
  • El lugar de los marcadores.
  • El lugar de las rutas trazadas.
Estructura general del URL

Esta es la estructura básica del URL sobre la cual se estructura el requerimiento del servicio.

http://maps.google.com/staticmap?PARÁMETROS

La lista de PARÁMETROS consiste en varias secciones separadas entre sí por & y que se encuentran construídas con un formato especial y que serán descritas a continuación.

Estos son los parámetros del servicio suceptibles de personalizarse.

  1. center.
  2. zoom.
  3. size.
  4. format.
  5. maptype.
  6. markers.
  7. path.
  8. frame.
  9. key.
Ubicaciones

Las ubicaciones georreferenciadas son especificadas de la forma latitud y longitud como dos valores reales con 6 dígitos decimales de precisión y separados por una coma: 5.07,-75.521.

Los valores válidos de una latitud varían desde -90 hasta 90 grados mientras que los valores válidos para una longitud varía desde -180 hasta 180 grados.

Parámetro Key

Es obligatorio y se utiliza para especificar la llave del API que se obtuvo inicialmente.  Sin esto, es imposible acceder al servicio.

http://maps.google.com/staticmap?center=5.07,-75.521&zoom=5&size=500×300&key=ABQIAAAAAa_xc3cplYGMwVbx_zW2chTWZSld1Wm-gV17JxrvtQa7WXWbRRTOL98qj5hk-yEw1n5LrYmEoAwzOg

Parámetro Center

Es obligatorio pero se convierte en opcional bajo ciertas circunstancias.  Determina la ubicación al rededor de la cual se centrará el mapa generado.

http://maps.google.com/staticmap?center=5.07,-75.521

El mapa anterior se encuentra centrado al rededor de las coordenadas de Manizales, Caldas.

Parámetro Zoom

Es obligatorio pero se convierte en opcional bajo ciertas circunstancias.  Determina el nivel de acercamiento al mapa.

Es definido por un número entero que varía entre 0 (menor) hasta 19 (máxima).  Debe tenerse en cuenta que no todos los niveles de acercamiento están disponibles para todos los tipos de mapas.

http://maps.google.com/staticmap?center=5.07,-75.521&zoom=11

El mapa anterior determina un nivel 11 de acercamiento sobre Manizales.

Parámetro Size.

Es obligatorio.  Especifica el tamaño de la imagen generada.

Su valor se especifica por un ancho y un alto separados por la letra ‘x‘ y su unidad es en pixels: 500×300.  El valor máximo de estas dimensiones es 640×480.

http://maps.google.com/staticmap?center=5.07,-75.521&zoom=11&size=500×300

La imagen generada a partir del requerimiento anterior tiene un ancho de 500px y un alto de 300px.

Parámetro Format.

Es opcional y determina el tipo de formato de la imagen generada.  Su selección depende de la relación tamaño/calidad deseada.  Si se omite, su valor por defecto es GIF.

Los posibles valores para este parámetro son los siguientes.

  • gif (por defecto).
  • jpg
  • jpg-baseline (no progresivo)
  • png8 (8 bits)
  • png32 (32 bits)

http://maps.google.com/staticmap?center=5.07,-75.521&zoom=11&size=500×300&format=png8

El ejemplo anterior genera una imagen con formato PNG de 8 bits.

Parámetro MapType.

Es opcional y determina el tipo de cartografía a utilizarse para la generación de la imagen.  Si se omite, su valor por defecto es roadmap.

Los posibles valores para este parámetro son los siguientes.

  • roadmap (por defecto).
  • mobile.  Presenta mejoras gráficas para la visualización en dispositivos móviles.
  • satellite. Muestra las imagenes de satélite.
  • terrain. Muesta el mapa del relieve y la vegetación.
  • hybrid. Mezcla el contenido de los tipos roadmap y satellite.

http://maps.google.com/staticmap?center=5.07,-75.521&zoom=11&size=500×300&format=png8&maptype=hybrid

El ejemplo anterior modifica el tipo de mapa visto hasta ahora (roadmap) y presenta un mapa estilo híbrido de la misma ubicación.

Parámetro Markers.

Es opcional y permite especificar y personalizar los marcadores que referenciarán ubicaciones importantes en el mapa.  Es posible especificar hasta 50 marcadores diferentes por mapa.

Cuando se especifica el parámetro markers no es necesario especificar a los parámetros center y zoom ya que estos se pueden calcular automáticamente.

La información de los marcadores se especifica con una cadena separada por ‘|‘ (%7C) de la siguiente manera.

markers=infoMarcador1|infoMarcador2|infoMarcador3|…

La información de los marcadores incluye datos tanto de su ubicación como de su presentación.  Cada marcador se crea con la siguiente estructura.

{latitud},{longitud},{tamaño}{color}{identificador}

El siginificado de cada uno de estos campos se describe a continuación.

  • latitud (requerido).  Latitud de la ubicación del marcador.
  • longitud (requerido).  Longitud de la ubicación del marcador.
  • tamaño (opcional).  Tamaño de la imagen del marcador.  Puede tomar uno de los siguientes valores.
    • tiny
    • mid (por defecto)
    • small
  • color (opcional).  Determina el color de la imagen del marcador.  Puede tomar una de las siguientes constantes.
    • black
    • brown
    • green
    • purple
    • yellow
    • blue
    • gray
    • orange
    • red (por defecto)
    • white
  • identificador (opcional).  Permite especificar un carácter alfanumérico y en minúsculas para identificar al marcador.  Debe tenerse en cuenta que este identificador sólo aplica para marcadores tamaño mid.  Su valor por defecto es el punto.

http://maps.google.com/staticmap?size=500×300&format=png8&maptype=hybrid&markers=5.07,-75.521,midyellowm|4.813,-75.696,midbluep|4.534,-75.681,midreda

El ejemplo anterior muestra el mapa del eje cafetero colombiano resaltando sus ciudades con marcadores de la siguiente manera: Manizales (amarillo), Pereira (azul) y Armenia (rojo).

Parámetro Path.

Es opcional y permite especificar y personalizar rutas sobre el mapa que conectan a dos o mas puntos georreferenciados.  El límite es el de máximo 100 puntos por ruta.

El parámetro se define de manera similar a markers siguiendo el siguiente formato.

path=tipoColor:#color,weight:pesoRuta|punto1|punto2|punto3|…

El siginificado de cada uno de estos campos se describe a continuación.

  • tipoColor.  Especifica el tipo de formato del color.  Puede tomar uno de los siguientes valores.
    • rgb.  Esquema RGB estándar con valores de 24 bits de la forma 0xffffff.  Su nivel de opacidad por defecto es de 50%.
    • rgba.  Utiliza valores de 32 bits de la forma 0xffffffff.  El nivel de opacidad está dado por el cuarto byte (últimos dos carácteres) que conforman el canal alfa de transparencia.
  • weight.  Especifica el grosor de la ruta en pixels.

http://maps.google.com/staticmap?size=500×300&format=png8&maptype=hybrid&markers=5.07,-75.521,midyellowm|4.813,-75.696,midbluep|4.534,-75.681,midreda&path=rgb:0x0000ff,weight:5|5.07,-75.521|4.813,-75.696|4.534,-75.681

En este ejemplo se agrega una ruta azul entre Armenia – Pereira – Manizales sobre el ejemplo inmediatamente anterior.

Parámetro Frame.

Es opcional y especifica si la imagen resultante se debe o no mostrar con un borde azul de 5px y con 55% de opacidad a su alrededor.  Por defecto no se muestra.

http://maps.google.com/staticmap?size=500×300&format=png8&maptype=hybrid&markers=5.07,-75.521,midyellowm|4.813,-75.696,midbluep|4.534,-75.681,midreda&path=rgb:0x0000ff,weight:5|5.07,-75.521|4.813,-75.696|4.534,-75.681&frame=true

Enlaces:

Departamentos y municipios de Colombia (Actualización 20080915)

He venido actualizando la información de los departamentos y municipios de Colombia publicada anteriormente.

Ahora se incluye a los municipios creados desde entonces (1102 registros actualmente).  Además me he dado a la tarea de georreferenciar (latitud/longitud) esta información para ser utilizada en futuros proyectos.  De momento me faltan 19 municipios cuyos nombres no coinciden.

La información se puede descargar de manera gratuita teniendo en cuenta que este es un trabajo en progreso por lo que no es necesariamente preciso.  Agradecería me informen las correcciones si alguien nota algún error en los datos.

Los archivos a continuación se encuentran en formato CSV y su codificación es UTF-8 lo que debe tenerse en cuenta para su correcta visualización.

La información se puede de la base de datos se puede consultar de manera gráfica utilizando el siguiente mapplet de mi autoría: Mapa de departamentos y municipios colombianos.

Enlace: Sistema de consulta de la división pollítico-administrativa de Colombia.

Obtener lecturas de un GPS USB desde Java/C#

La tarea del día de hoy era obtener la posición actual desde un GPS utilizando Java o C#.  Para esto contaba con un GPS Garmin eTrex Summit HC que por supuesto cuenta con un puerto USB.

Extrañamente concluyo que no hay un método confiable ni portable para leer información desde los puertos USB para ninguno de los dos lenguajes.  Para Java encontré varias librerías pero aún inmaduras o con soporte para sólo un sistema operativo, prefiero el soporte para Windows y Linux por lo menos.  Para C# igual horizonte, nativamente aún no es soportado como si lo es el SerialPort y la mejor opción parece ser #usblib de los mismos creadores de SharpDevelop, sin embargo no pude encontrar un ejemplo que indicara su viabilidad.

Con este oscuro panorama terminé explorando un estilo de solución que no es de mi mayor agrado: el ejecutar aplicaciones de terceros para obtener y procesar su salida estándar.

Para esto instalé un software muy útil llamado GPSBabel el cual se puede descargar para Windows, Linux y Mac, incluyendo su código fuente escrito en lenguaje C.  La distribución de Linux está basada en RPM los cuales no fueron instalables en mi estación de trabajo basada en Ubuntu, sin embargo se encuentra disponible el paquete para instalarse desde la distribución de paquetes de Ubuntu.

El comando gpsbabel permite obtener desde línea de comando la información contenida en el GPS, incluyendo por supuesto, su ubicación actual.

$ sudo /usr/bin/gpsbabel.exe -i garmin,get_posn -f usb:

El resultado es algo de este estilo: (probado desde Armenia/Qundío)

4.536692N 75.669346W Position/Position

Extrañamente la distribución de Windows incluye además la fecha/hora de la ejecución, hecho que en Linux no sucede.

Wed Sep 10 22:42:46 2008
4.536692N 75.669346W Position/Position

La aplicación intermedia fue desarrollada en C# debido a la afinad de mi compañero de desarrollo con ese lenguaje.  Fue interesante para aprender algunos detalles particulares del lenguaje a los cuales nos vimos enfrentados durante el desarrollo.

Lo que hice fue crear un método estático (AccesoGPSBabel.obtUbicacionActual) que basado en la ubicación del comando gpsbabel retorna un Hashtable con la latitud/longitud actual o null en caso de suceder algún problema.

La ejecución del programa externo se realiza mediante un objeto System.Diagnostics.Process al cual se le indica la ubicación del archivo ejecutable, los argumentos de línea de comando y se le informa que estamos interesados en obtener su salida estándar.  La aplicación (o un enlace a) gpsbabel deberá estar ubicada en el mismo directorio del ejecutable de la aplicación principal, incluyendo el archivo libexpat.dll para la versión de Windows.

            System.Diagnostics.Process proc = new System.Diagnostics.Process();

            proc.EnableRaisingEvents = false;

            proc.StartInfo.FileName = aplicacion;

            proc.StartInfo.Arguments = "-i " + protocolo + ",get_posn -f usb:";

            proc.StartInfo.RedirectStandardOutput = true;

            proc.StartInfo.UseShellExecute = false;

            proc.Start();

Para recibir la información proveniente de la salida estándar se obtiene su StreamReader de la siguiente manera.

            String datos = proc.StandardOutput.ReadToEnd().Trim();

Su procesamiento no se aleja de la carpintería normal del manejo de cadenas con String.Split junto con un detalle adicional, la información aparece con el posfijo N/S, W/E; yo prefería que fuera un solo valor numérico siendo positivo o negativo según su ubicación con respecto a la referencia, tal y como lo maneja GoogleMaps y otros paquetes similares.

Para esto, al procesar las líneas recibidas de la salida estándar recorté la subcadena de manera que no tomara el último carácter de las coordenadas.

            latitud     = secciones[0].Trim().Substring(0, secciones[0].Length - 1);

            latitudDir  = secciones[0].Trim().Substring(secciones[0].Length - 1, 1);

            longitud    = secciones[1].Trim().Substring(0, secciones[1].Length - 1);

            longitudDir = secciones[1].Trim().Substring(secciones[1].Length - 1, 1);

Y después las multipliqué por su inverso aditivo en los casos en que correspondiera.

            Double Latitud = Double.Parse(latitud) * ((latitudDir.Equals("S")) ? -1 : 1);

            Double Longitud = Double.Parse(longitud) * ((longitudDir.Equals("W")) ? -1 : 1);

Sucedió un problema.  El método Double.Parse estaba convirtiendo erróneamente los valores porque el GPS los enviaba con el punto como separador decimal y la Configuración Regional de la máquina Windows que estabamos utilizando usaba la coma.

Como la intención era que la aplicación no sólo funcionara entre diferentes configuraciones regionales sino que lo hiciera también entre diferentes sistemas operativos (Linux con Mono) me dediqué a buscar una solución portable.

Para esto se obtiene al separador decimal basado en la configuración regional del sistema operativo.

            String separadorDecimal = System.Globalization.CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator;

Se realiza la corrección de separadores (de punto al utilizado por la configuración regional local) antes de manipular los datos con el Double.parse.

            latitud = secciones[0].Trim().Substring(0, secciones[0].Length - 1).Replace('.', separadorDecimal[0]);
            longitud = secciones[1].Trim().Substring(0, secciones[1].Length - 1).Replace('.', separadorDecimal[0]);

Con esto se solucionó el problema.  Para terminar, se utilizó un Hashtable para almacenar la información y retornársela al objeto que la solicitó.

            resultado.Add("latitud", Latitud);
            resultado.Add("latitudDireccion", latitudDir);
            resultado.Add("longitud", Longitud);
            resultado.Add("longitudDireccion", longitudDir);

Esta no es la solución que pensaba darle al problema, sin embargo la implementación fue interesante y gracias a la flexibilidad que provee gpsbabel, espero que sea funcional con una amplia gama de dispositivos, no solamente con los de la marca Garmin.

De todas formas me queda pendiente la tarea de buscar la forma de acceder directamente al puerto USB para leer su informacion en forma de cadenas NMEA, la cual me parece la solución menos dependiente posible de su entorno.

Enlace: Distribución con las fuentes del proyecto (C# basado en MonoDevelop).

Demostración de georreferenciación al estilo web 2.0

La demostración de georreferenciación al estilo web 2.0 que permite visualizar el mapa de cualquier ubicación del mundo y ubicar sobre él marcadores georreferenciados.

Para esto utiliza los servicios de Google Maps para la generación de las imágenes y GeoNames para la georreferenciación, es decir, para convertir las ubicaciones (Manizales, Caldas, Colombia) en sus respectivas duplas latitud/longitud para poder ser relacionadas geográficamente.

En la parte izquierda se aprecia la lista con las ubicaciones seleccionadas que corresponden con las marcas rojas en el mapa, en la parte derecha está el mapa junto con tres barras de desplazamiento que regulan su presentación y en la parte inferior se encuentran los botones de opciones.

El demo le permite al usuario Agregar y Remover ubicaciones como puntos seleccionados.  Como se mencionó, estas se ingresan con los nombres de las ubicaciones y el sistema obtiene su ubicación geográfica a través de la consulta de un servicio web.  Es posible en cualquier momento, Centrar el mapa al rededor de cualquiera de los puntos seleccionados almacendos.

La manipulación del mapa se realiza con las barras de desplazamiento.  La naranja (derecha) corresponde con el zoom: hacia abajo aumenta, haciendo mayor el acercamiento del mapa.  La horizontal, azul, corresponde con la longitud y la vertical, verde, corresponde con la latitud.  Cuando se modifica cualquiera de estos valores se deberá solicitar la actualización del mapa presionando el botón Refrescar.

La aplicación ha sido desarrollada en Java por lo que su código es muy claro y modular.  En términos de la implementación, el acceso al webservice de GeoNames utiliza su propio API para el cual se descargaron dos archivos: geonames-1.0-java5.jar y jdom-1.0.jar.

La georrefernciación no podría ser más fácil.

    public static GeoLocation locate(String location) throws Exception
    {
        GeoLocation result = new GeoLocation();

        // Creates the toponym searcher

        ToponymSearchCriteria searchCriteria = new ToponymSearchCriteria();

        // Sets the criteria based on the specified location

        searchCriteria.setQ(location);

        // Request the geolocalization to the webserver

        ToponymSearchResult searchResult = WebService.search(searchCriteria);

        // Gets the results

        List toponyms = searchResult.getToponyms();

        // Checks if there were results

        if(toponyms == null || toponyms.size() == 0)
            return null;

        // Gets the first result of all (could be many)

        Toponym first = toponyms.get(0);

        // Prepares the result with its information

        result.put("geoNameId",   first.getGeoNameId()   + "");
        result.put("name",        first.getName()        + "");
        result.put("latitude",    first.getLatitude()    + "");
        result.put("longitude",   first.getLongitude()   + "");
        result.put("countryCode", first.getCountryCode() + "");
        result.put("countryName", first.getCountryName() + "");

        return result;
    }

La clase GeoLocation que utilizo para manejar el resultado no es mas que un Hashtable<String, String> con algunos adendos para facilitar su uso.

Por otro lado, la generación del mapa requiere aún menos ciencia aunque la realizo en dos pasos discretos que en general no incluyen nada extraño.

    public String prepareUrl()
    {
		String markers = "";

		// Walks thru all the points to create its markers

		for(int i=0; i

En el primer paso preparo el URL del consulta al servicio basado en información como la coordenada centro del mapa, el nivel de zoom, el tamaño de la imagen, los puntos seleccionados (marcadores) y la llave del API que debe ser privada y es única para cada sitio web (FQDN), aunque para el caso específico de aplicaciones de escritorio no es muy relevante a pesar de ser obligatoria.  La llave del API puede ser obtenida de manera gratuita por cualquier desarrollador desde esta dirección.

    public Image prepareImage(String url) throws Exception
    {
        Image image = ImageIO.read(new URL(url));

        return image;
    }

El segundo paso se relaciona con consultar el servicio de Google utilizando el URL recién generado, leer los bytes que conforman la imagen y crear con ellos un objeto Image para ser mostrado posteriormente en la interfaz de usuario.  Con Java, este procedimiento es extremadamente sencillo: 1 línea de código.

Para su uso se deberá tener cuidado en el manejo de las posibles excepciones que pueda lanzar el requerimiento como por ejemplo, producto de un fallo de red.

        // Gets the map image

        Image imageMap = mapService.getMap();

        // Checks if was received

        if(imageMap == null)
        {
            JOptionPane.showMessageDialog(this, "El mapa no se pudo recuperar.",
                    "Error recuperando mapa", JOptionPane.ERROR_MESSAGE);
            return;
        }

        // Puts the map on the gui

        map.setIcon(new ImageIcon(imageMap));

Teniendo el objeto Image lo podemos poner en cualquier componente de AWT/Swing.  Por facilidad, yo utilizo un JLabel que incluye el soporte de íconos (ImageIcon) los cuales se basan en objetos de imagen.

Como se puede apreciar, la complejidad de la aplicación es muy baja por lo que reitero: la imaginación es el límite.

Es posible acceder a la aplicación desde web sin instalar ningún archivo local desde el siguiente enlace o ejecutando el siguiente comando en una consola/símbolo del sistema operativo:

   $ javaws http://demo.jorgeivanmeza.com/Java/DemoGeoReferenceMap/0.1/DemoGeoReferenceMap.jnlp

Enlace: