¿Cómo monto mis páginas web de viajes?

Algunos lectores me han preguntado cómo elaboro mis páginas de viajes, con sus tracks GPS y sus fotos geolocalizadas. Un ejemplo.

Lo primero que tengo que decir es que cuando viajo me llevo un data logger. Se trata de una pequeña unidad autónoma que va grabando mi posición todo el tiempo, cada par de segundos, y lo almacena en una memoria interna. Hoy en día eso se podría hacer con un teléfono, si no fuera por los problemas de la multitarea de iOS y, sobre todo, por el enorme consumo de batería que ello supone. Mi unidad autónoma tiene muy poca capacidad (2MB) pero la batería le dura 14 horas y puedo configurarlo de diferentes maneras.

Lo segundo que hay que decir es que mi iPhone está configurado para que incluya la posición GPS en las fotografías, lo que simplifica el problema bastante. Pero cuando viajo con más gente que desactiva esa funcionalidad por privacidad o consumo de batería o uso fotografías tomadas por cámaras tradicionales, tener datos GPS en paralelo resulta muy útil. Además te permite almacenar datos detallados de posición aunque no saques fotos constantemente.

Lo tercero es que mucha de la tecnología que uso es propia, fundamentalmente disponible a través de mi proyecto PyTrek. Parece un proyecto abandonado, pero no :-). El repositorio está vivo :-).

Mis pasos habituales son los siguientes:

  1. Antes de iniciar el viaje, estimo la densidad de puntos que puedo tener en el data logger. El problema fundamental es que tiene muy poca memoria, 2MB de flash. Yo estimo que lleno esos 2 MB en 24 horas de uso continuo, grabando un punto cada 2 segundos. Si voy a viajar una semana y estimo que me voy a mover 12 horas diarias (cuando paro para comer o cenar apago el data logger), pues puedo configurar el data logger para que guarde una posición cada 8 segundos, aceptable si nos movemos caminando.

    La configuración se cambia a través del script gps-mtk-setinterval.py de PyTrek. Podemos ver la configuración actual con gps-mtk-getconfig.py.

  2. Lo siguiente es borrar la memoria del data logger y cargar su batería a tope :-). Uso el script gps-mtk-datamemoryclearing.py de PyTrek.

  3. Cuando iniciamos el viaje, encendemos el data logger. La batería le dura bastante, todo el día. Para ahorrar un poco de batería y de memoria, podemos apagarlo cuando nos detenemos para comer y cenar, pero hay que tener cuidado con que no se nos olvide encenderlo de nuevo. Lo más fácil para mí, como el data logger es pequeño, es ponerlo encima de la mesa a modo de recordatorio.

    Por las noches lo apagamos y nos aseguramos de cargar la batería para el día siguiente.

  4. Terminado el viaje, de vuelta en casa, volcamos la memoria del data logger al ordenador mediante el script gps-mtk-datadownload.py de PyTrek. La borramos con gps-mtk-datamemoryclearing.py y, si es necesario, reconfiguramos el data logger para grabar puntos de forma frecuente con gps-mtk-setinterval.py.

  5. Estos tracks GPS los guardo eternamente. Ocupan muy poco, un par de megas a lo sumo. Tengo todo el histórico en el ordenador. Y sus backups, claro :).

  6. Estos tracks tienen el formato binario MTK, pero se pueden convertir fácilmente con programas como GPSBabel. Convertimos el track a GPX:

    gpsbabel -t -w -i mtk-bin -o gpx -f SOURCE.dump -x discard,hdop=10 -F DESTINATION.gpx
    
    • Si tenemos varios ficheros con los tracks, se pueden procesar simultaneamente y juntarlos en un único fichero destino simplemente repitiendo el parámetro -f por cada fichero fuente.
  7. Cargamos el GPX en el Google Earth. Lo cargamos también en un editor de textos. Eliminamos puntos claramente incorrectos, la posición precisa de mi casa, etc. Vamos modificando el fichero y vamos viendo el resultado con Google Earth.

  8. Hay que conseguir las fotos de todo el mundo, lo que no siempre es sencillo :-). No, Whatsapp no sirve para esto, amigo mío. Quiero fotos con un mínimo aceptable de resolución.

  9. Giramos las fotos para que estén bien orientadas, y las comprimimos a un tamaño razonable comprometiendo su calidad lo mínimo posible. Todo esto se describe con detalle en mi artículo Procesado de fotografías de forma automatizada.

  10. Sobre las fotografías... Como ya he dicho, mi iPhone graba la información GPS en las fotografías. Pero la primera foto de una serie, si la saco rápido, puede tener la posición GPS mal porque todavía no le ha dado tiempo a centrarse correctamente. Además, mis compañeros de viaje pueden no tener esa opción (o tenerla desactivada), o emplear cámaras tradicionales sin soporte GPS.

    PyTrek se desarrolló precisamente para eso: guardar tu movimiento y geolocalizar las fotografías.

    Lo crítico en este caso es que la fecha y hora de la cámara estén bien. Si es un móvil, seguramente no hay problema. Pero si la fuente es una cámara tradicional, es importante que su fecha y hora sean correctas. Es algo en lo que insisto a mis compañeros de viaje, con éxito desigual :-P.

    PyTrek tiene tres scripts para geolocalizar fotos:

    • time2gps_closest.py nos muestra en pantalla la posición GPS con la fecha y hora más cercana posible a las de la foto.

    • time2gps_previous.py hace lo mismo, pero con la fecha y hora más cercana pero anterior al de la cámara

    • Los dos scripts anteriores son históricos y ya no los uso. El script PyTrek que uso ahora es exiftag.py, que modifica el EXIF de las fotografías con la posición GPS con la fecha y hora más cercana posible a las de la foto.

      Aunque la foto tenga ya datos GPS en el EXIF, el script da prioridad a los datos GPS externos almacenados en el data logger, salvo que la fecha y hora de los puntos que tenemos tengan más de un minuto de antigüedad. Por ejemplo, si hemos salido de noche a dar un paseo y nos hemos dejado el data logger en el hotel.

      Si una cámara no tiene bien puesta la fecha y hora, podemos compensarlo un poco si hay fotos del mismo momento que sí estén bien etiquetadas (tomadas con otros dispositivos). Calculamos el offset y se lo indicamos al script.

  11. Creo una estructura de directorios apropiada. Típicamente utilizaré objetos ordered folders de ZOPE, para poder controlar en qué orden se muestran las fotos sin que tenga que preocuparme con sus nombres.

  12. Subo las fotografías publicables (no todas lo son, por diversos motivos) a mi servidor ZOPE. Normalmente lo hago por WebDAV. Como uso ordered folders de ZOPE, el orden de subida es importante, aunque se puede modificar posteriormente a mano.

    Las fotos se almacenarán como objetos ExtImage, porque tengo un método Python externo llamado PUT_factory con el siguiente contenido:

     from OFS.Image import getImageInfo
     from Products.ExtFile.ExtImage import ExtImage
     import string
    
     def _PUT_factory(self, name, typ, body,permiso_download=0):
         '''Creates ExtImage instead of plain Image.'''
         ct, w, h = getImageInfo(body)
         if ct:
             major, minor = string.split(ct, '/')
             if major == 'image':
                 return ExtImage(name, '', '',permiso_download)
         major, minor = string.split(typ, '/')
         if major == 'image':
             return ExtImage(name, '', '',permiso_download)
         return None
    
     def PUT_factory(self, name, typ, body):
       return _PUT_factory(self, name, typ, body)
    
     def PUT_factory_no_download(self, name, typ, body):
       return _PUT_factory(self, name, typ, body,1)
    

    En concreto se crean objetos ExtImage configurados para que solo se pueda descargar la versión "grande", si estamos autenticados en la página web. Si no, solo se nos mostrarán las versiones pequeñas.

  13. Una vez subidas, creo las versiones pequeñas de las fotos y la gestión de sus metadatos llamando al método Python Generar_preview, con el siguiente contenido:

     # Example code:
    
     # Import a standard function, and get the HTML request and response objects.
     from Products.PythonScripts.standard import html_quote
     request = container.REQUEST
     RESPONSE =  request.RESPONSE
    
     j=context.objectValues(["ExtImage"])
     for i in j :
         scale = max(i.height(), i.width())/461.0
         h,w=round(i.height()/scale),round(i.width()/scale)
         i.manage_create_prev(maxx=w, maxy=h, ratio=True)
         print "<br>"+i.Generar_gps()+ " <font size=-2>"+i.Generar_datetimeoriginal()+"</font>"
    
     print "<br><br>Convertimos %d im&aacute;genes" %len(j)
     return printed
    

    Este script genera las versiones pequeñas de las imágenes que, en este momento, incluyen una pequeña firma personalizada. Eso se explica con detalle en mi artículo Firma de imágenes.

    Vemos que para cada ExtImage se llaman a dos métodos.

    Generar_gps() es un método externo ZOPE con el siguiente contenido:

     import EXIF
     def generar_gps(self) :
         f = open(self._get_fsname(self.filename), "rb")
         tags = EXIF.process_file(f, details=False)
         latitud = longitud = ""
         if "GPS GPSLongitude" in tags :
             lat = tags["GPS GPSLatitude"].values
             lon = tags["GPS GPSLongitude"].values
             if (lat[0].den==1) and (lon[0].den==1) :
                 minseg = lat[2].num/60.0/lat[2].den if lat[2].num else 0
                 latitud  = "%s %d&deg; %.3f" %(tags["GPS GPSLatitudeRef"].values,
                           lat[0].num, lat[1].num/float(lat[1].den)+minseg)
                 minseg = lon[2].num/60.0/lon[2].den if lon[2].num else 0
                 longitud = "%s %d&deg; %.3f" %(tags["GPS GPSLongitudeRef"].values,
                           lon[0].num, lon[1].num/float(lon[1].den)+minseg)
             else :
                 longitud = latitud = "XXXXXXXXX"
         if not self.hasProperty('longitud') :
             self.manage_addProperty('longitud', "", 'string')
             self.manage_addProperty('latitud', "", 'string')
         self.manage_changeProperties({"longitud":longitud,"latitud":latitud})
         if latitud == "" :
             self.manage_delProperties(("longitud", "latitud"))
         return latitud+" "+longitud
    

    Este código extrae la información GPS de la imagen, si existe, y la añade como metadatos del propio objeto ExtImage.

    Generar_datetimeoriginal() es otro método externo ZOPE:

     import EXIF
     def generar_datetimeoriginal(self) :
         f = open(self._get_fsname(self.filename), "rb")
         tags = EXIF.process_file(f, details=False)
         datetimeoriginal = ""
         if "EXIF DateTimeOriginal" in tags :
             datetimeoriginal = tags["EXIF DateTimeOriginal"].values
             datetimeoriginal = datetimeoriginal.replace(":", "-", 2)
         if not self.hasProperty('exif_datetimeoriginal') :
             self.manage_addProperty('exif_datetimeoriginal', "", 'string')
         if datetimeoriginal == "" :
             self.manage_delProperties(("exif_datetimeoriginal",))
         else :
             self.manage_changeProperties({"exif_datetimeoriginal":datetimeoriginal})
     return datetimeoriginal
    

    Este método extrae la fecha y hora de la imagen original, si existe, y crea un atributo del objeto ExtImage.

  14. Para visualizar las fotos en sí, uso código ZOPE de este estilo, como documentos DTML:

    1
    2
    3
    4
    5
    6
    7
    8
    9
     <dtml-in "REQUEST.PARENTS[0]['elena'].objectValues(['ExtImage'])">
       <p><a href="elena/&dtml-getId;"><dtml-var "tag(preview=1,align='left')"></a>
       <font size=+1><dtml-var title_or_id></font>
       <br><dtml-var "size()">
       <dtml-if exif_datetimeoriginal><br><dtml-var exif_datetimeoriginal></dtml-if>
       <dtml-if latitud><br><i><dtml-var latitud> <dtml-var longitud></i></dtml-if>
       <p><dtml-var descr>
       <br clear="all">
     </dtml-in>
    

    Esto muestra las imágenes en formato pequeño, junto a su nombre, tamaño, comentario, fecha y hora y posición GPS, si existe.

  15. Ahora revisamos las fotos y les ponemos comentarios.

  16. Para generar la imagen del track antes empleaba el script gpx2plot.py o el prehistórico gps2gnuplot.py, pero ahora he programado gpx2matplotlib.py. La gran ventaja de esta versión es que se pueden generar resultados vectoriales en formato SVG. Puedo hacer zoom y quedarme con las partes que me interesan.

    Grabamos los diferentes trozos en formato SVG, y los montamos como queremos usando Inkscape. El resultado final lo renderizamos a formato PNG, lo cargamos con GIMP, lo pasamos a dos colores usando "threshold" y ajustando su valor para que quede bien. Y, por fin, lo compactamos lo mejor posible usando:

    $ pngcrush -brute
    

    Subimos el resultado al servidor.

  17. Por último, convertimos el GPX a formato KML, usado por Google Maps o Google Earth:

    $ gpsbabel -t -w -i gpx -o kml,points=0,line_color=ff00ff40,line_width=6 -f SOURCE.gpx -x discard,hdop=10 -F DESTINATION.kml
    

    Lo pasamos a KMZ, que es KML comprimido en ZIP:

    $ zip -9v DESTINATION.kmz DESTINATION.kml
    

    Subimos el KMZ al servidor.

  18. Nos aseguramos de que todo se ve bien y de que Google Maps gestiona correctamente el KMZ.

  19. Puffff, casi nada. ¡Hemos terminado!.