“Geocoding” inverso en el iPhone/iPod touch
Según la Wikipedia:
Geocoding es el proceso de encontrar las coordenadas geográficas asociadas … desde otros datos geográficos, como una dirección, o un código postal …
Geocoding inverso es lo opuesto: encontrar la localización textual asociada como una dirección partiendo de las coordenadas geográficas.
Core Location está disponible tanto en el iPhone como el iPod touch (en este último utilizando datos de router WiFi cercanos), pero, obviamente, únicamente proporciona al desarrollador las coordenadas númericas aproximadas del dispositivo.
Si bien es cierto que el usuario es más que posible que sepa donde está (o quizá no, depende del usuario), es mucho más agradable presentarle en pantalla “Cupertino, United States” que “N 37.331689 – W 122.030731″.
Para realizar esa tarea he programado una pequeña biblioteca orientada para ser utilizada en el iPhoneOS (sin necesidad de conexión a Internet) y un script para generar la base de datos necesaria. El proyecto es open source y está alojado en GitHub. Utilizar y forkear como gustéis.
Más explicaciones tras el salto.
La historia completa es complicada, así que será mejor que os sentéis alrededor de la hoguera mientras comienzo mi relato.
Una de las primeras cosas que instalé después de comprarme el iPod touch fue un cliente de Twitter, simplemente porque no puedo vivir ni un momento sin saber lo que hace @aplusk. De entre las posibilidades gratuitas estába TwitterFon, del que había leido buenos análisis, así que lo instalé. Realiza perfectamente su función como cliente de Twitter (aunque debo decir que no he probado otro para el iPod).
A mitad de Febrero Lluch y yo nos apuntamos a un viaje en tren para mitad de Abril que nos llevaría por Suecia, Noruega (hasta Trodheim) y Dinamarca. Me pareció que sería genial ir actualizando mi página de Twitter con mi localización geográfica durante esa semana, y recordé que TwitterFon tenía una función para hacer justo eso. Sin embargo en vez de enviar la localización como un “tweet”, TwitterFon utiliza el campo “Location” de Twitter (aparece en los perfiles de usuario de Twitter, en la columna derecha). Totalmente comprensible, pero poco útil para darle un poco de visibilidad al viaje. En ese momento decidí que lo más sencillo sería simplemente escribir yo mismo dónde estaba.
Pero me di cuenta de que la localización que enviaba TwitterFon era una cadena que, a pesar de que mi dispositivo no era un iPhone, decía “iPhone” al principio. Así que busqué algún correo de contacto en la página del programa para enviarle esa propuesta y algunas otras (como que el la búsqueda se detuviese en el último “tweet” del propio usuario, en vez del último “tweet” visto en el dispositivo, ideal si utilizas varios clientes). Mirando por la página encontré un enlace al repositorio de Subversion del programa y vi que era open source, por lo que en vez de enviar la propuesta, decidí que enviaría el pequeño parche para resolver el problema del campo “Location”.
Me descargué el código, busqué donde se enviaba la localización, hice un cambio de un par de líneas y preparé un parche. Sobre la marcha se me ocurrió que sería genial que en vez de aparecer los números de la coordenada apareciese algo más descriptivo, como la ciudad y el país que correspondiesen a esas coordenadas, aunque eso obviamente iba a llevar más tiempo y más líneas de código.
Hice una búsqueda rápida en Google y en primer lugar estaba la solución: GeoNames, un servicio web gratuito para hacer geocoding inverso. La idea inicial era utilizar el servicio (total, el usuario ya tenía que estar conectado a Internet para utilizar TwitterFon, y una petición más no iba a ser para tanto), pero cuando me pasé por su el blog de GeoNames me encontré que otra aplicación para el iPhone había decidido utilizar el servicio y les había “tumbado” los servidores hacía unos meses. El primer comentario proponía utilizar la base de datos de GeoNames incluida dentro de la propia aplicación, por lo que la idea ahora era hacer una base de datos tan pequeña como fuera posible, tan eficiente como se pudiera en el pequeño dispositivo, y con la información suficiente para ser interesante. Todo ello aderezado con una fachada que ocultase todo el proceso al programador con problemas de fechas de entrega.
Envié un correo electrónico al creador de TwitterFon con el pequeño parche y las propuestas bien redactadas (o todo lo bien que pueda escribir yo). El proyecto del geocoding inverso parecía lo suficientemente interesante para otros proyectos, por lo que decidí separarlo de TwitterFon, y le propuse al desarrollador de TwitterFon que elegiría una licencia que le permitiese utilizarlo. Desgraciadamente no he recibido respuesta de él hasta ahora, pero yo me puse a trabajar.
Lo primero fué descargar y estudiar la base de datos de GeoNames. La base de datos completa son 171 MB comprimida, así que estaba fuera de las posibilidades del iPhone. Afortunadamente GeoNames ofrece bases de datos más reducidas de las poblaciones con más de 1000, 5000 y 15000 habitantes. En principio la idea era utilizar la de más de 1000 habitantes, pero despues de quitarle columnas innecesarias, transformarla a una base de datos SQLite, y comprimirla el tamaño se quedaba sobre 2,6 MB, un poco más grande de lo que yo esperaba. Por suerte la versión de 5000 habitantes con el mismo proceso se queda en 1,2 MB, mucho más aceptable para una aplicación que ocupa 0,5 MB.
Al final de entre todas las columnas de las bases de datos de GeoNames únicamente se mantiene el nombre de la población, la latitud, la longitud y una referencia al país (otra pequeña tabla incluida también en la base de datos). Sin embargo sabía que SQLite (y sobre todo el iPhone) se “morirían” si les pedía que me calculasen la distancia de todas las poblaciones a una coordenada determinada (son unas 40.000 poblaciones, así que imaginad). La siguiente solución “tonta” era filtrar la consulta alrededor de la coordenada del usuario (algo así como WHERE latitude > userLat-1 AND latitude < userLat+1 AND longitude > userLon-1 AND longitude < userLon+1) y luego ordenar por distancia. Desde luego eso reduciría los cálculos, pero leyendo la documentación sobre el optimizador de SQLite me dió la sensación de que a pesar de indexar la tabla esa búsqueda tendría que recorrer todas las poblaciones en la franja [userLat-1, userLat+1] y comprobar la longitud una a una. Decidí que ese no era el camino a seguir.
Y entonces recordé que había leído hace mucho tiempo que Google utilizaba curvas recubridoras del plano (o del espacio) como la Curva de Hilbert para reducir un problema de búsqueda en varias dimensiones a una única dimensión (para lo que su BigTable está optimizado, como comentaba en la entrada sobre Google App Engine). La idea es simple y, como dirían los matemáticos, hermosa; sacar los algoritmos… imposible, aunque con un par de búsquedas en Internet la cosa se solucionó.
Con esta ingeniona optimización la solución consiste en calcular en que “distancia” de la curva de Hilbert está el usuario y buscar poblaciones a esa misma distancia (en el código final se busca también en “sectores” adyancentes, para evitar problemas con situaciones cercanas a los bordes de los sectores). Además, en vez de realizar los calculos en SQLite, se recuperan todas las poblaciones del sector en cuestión y la distancia a la localización del usuario se calcula en el propio código en Objective-C.
Pero quería que la gente que tuviese que utilizarlo no tuviese que entender todo el proceso, encontrar sus propios datos y demás, por lo que decidí añadir a la mezcla un pequeño script de Thor que incluyese todo el código para transformar los archivos de GeoNames en una base de datos SQLite, que se descargase el código de la biblioteca y de los archivos de GeoNames, en resumen, que hiciese todo el trabajo. Además la propia biblioteca sabe donde encontrar la base de datos comprimida en la aplicación, descomprimirla en un directorio y empezar a utilizarla. Lo cierto es que en mi impresión, se llevó mucho más trabajo la preparación de todo el código para que fuera terriblemente sencillo de utilizar, que el código que hace el trabajo en sí. Y al final pasándome un único día (y algunas horas de la madrugada) de mi estimación de tiempo inicial (“para finales de la semana lo tendré”).
Pero el resultado merece la pena. Un desarrollador que quiera utilizar la biblioteca simplemente tiene que pasarse por la página en GitHub del proyecto, teclear tres líneas (o cortar y pegar) en el terminal, incluir los archivos y en su proyecto, e incluir dos líneas en su código, y ¡presto!
Y como todo buen chiste ahora viene la gracia: el Sábado pasado el viaje en tren que ibamos a realizar se canceló y nos quedamos sin viaje, pero con un billete de solo ida a Berlín (y cancelar el cambio de moneda que habíamos pedido en el banco). A pesar de ello me decidí a terminar el proyecto, primero porque le había dicho al desarrollador de TwitterFon que lo iba a hacer, y segundo porque me pareció que era un proyecto que podría ser de utilidad para la comunidad desarrolladora de iPhone. Pero hoy, cuando me disponía a integrar el proyecto en TwitterFon, el svn update me ha fallado, y cuando me he paseado por la página he visto que el desarrollador ha eliminado el enlace al repositorio y la referencia de que el código era GPL. Aún no le he enviando ningún correo, pero el código está integrado en mi versión de TwitterFon, y estoy por sustituir su versión por la mía en mí iPod touch.
Así que, resumiendo, a mí no me va a servir de nada porque no me voy de viaje (bueno, el fin de semana a Berlín, nada más) y el desarrollador de TwitterFon parece que no lo quiere; de forma que espero que a alguién le sea útil el proyecto, ya sea integrado en su aplicación para iPhone o como base para otro proyecto.


11 de Abril de 2009 a las 21:41
Un servidor reconoce que ha tenido a Ruido Blanco bastante abandonado últimamente, así que ya va siendo hora de empezar a leerte un poco más no vaya a terminar convirtiéndome en un gañán tecnológico.
En principio, siendo un cliente de Twitter, entiendo que la geocodificación inversa la puedes realizar online en vez de local. Hace unos meses que Google permite hacerlo en la API de maps, y no solo desde JavaScript, tienen un servicio web HTTP al que le pasas los parámetros por GET y recibes el resultado en un bonito JSON o un XML. Documentación aquí.
15 de Abril de 2009 a las 00:35
Lo primero que miré fue el reverse geocoding de Google, que recordaba haber leido que habían publicado un servicio de reverse geocoding, pero lo primero que leí al comenzar la página que envias es esto:
Luego, como comento, me cruzé con el otro, y la idea era utilizarlo online, pero como leí el problemilla que habían tenido con la otra aplicación y supose que TwitterFon podría ser similar, pensé que hacerlo offline era más reto (jeje) y al final más útil (puedes utilizar el GPS del iPhone sin tener que estar conectado a Internet y te puede decir, pues estás cerca de Villaconejos De Enmedio).
Quizá si se lo pidas a Google expresamente puedas utilizar su API de la forma que propones y que yo pensaba utilizar, firmando un contrato especial o lo que sea.