Tuesday, May 04, 2021

A Long Walk to Image-Based-Map Story Telling

I did a bit of Yak Shaving this morning. See, I wanted to write a post about a recently discovered app feature that does an amazing job of identifying wildflowers. But, to give that post context I thought it would be helpful to put the wildflowers pics on a map. But to get photos on a map, I figured I needed to extract the lat/long from each of the images' metadata. And to extract the image metadata, I needed re-learn how to pull exif data using ImageMagick.

So while I had intended to write about wildflower identification, I found myself writing the shell script below that extracts the latitude and longitude from an image.

But my Yack Shaving didn't stop there.

My Maps Awesomeness

With the latitude and longitude derived, I then went over to Google's My Maps to figure out how I could render the photos on a map.

I opened up My Maps, clicked Import and was shocked to see that there's a Google Photos option:

I selected a bunch of wildflower pics from our recent hike and to my delight, Google precisely placed them on the map. All the work I did to extract geo location data from images was unnecessary (but I'm confident it may be handy in the future).

Wrestling with Garmin Connect

To complete the wildflower map I needed to export our route from connect.garmin.com and import it into a new layer on My Maps. Garmin, to their credit, includes GPX and KML export options and My Maps reads both these formats. This was going to be a breeze!

The export from Garmin went smoothly, but the import into My Maps failed. The export file was over 5 Megs, the size limit for My Maps. Ugh.

Looking at the export file, I noticed at least two optimizations I could make to shrink the file:

<trkpt lat="38.72506211511790752410888671875" lon="-77.3301221989095211029052734375">
  <ele>78.40000152587890625</ele>
  <time>2021-05-02T11:00:34.000Z</time>
  <extensions>
    <ns3:TrackPointExtension>
      <ns3:hr>105</ns3:hr>
    </ns3:TrackPointExtension>
  </extensions>
</trkpt>
<trkpt lat="38.725062198936939239501953125" lon="-77.33012236654758453369140625">
  <ele>78.40000152587890625</ele>
  <time>2021-05-02T11:00:35.000Z</time>
  <extensions>
    <ns3:TrackPointExtension>
      <ns3:hr>105</ns3:hr>
    </ns3:TrackPointExtension>
  </extensions>
</trkpt>
...

Those lat/lon values are crazy. There's no reason to have that many decimal points of precision. Also, the tags prefixed with n3: appear to be heartrate metadata which My Maps won't use. Running the following unix commands brought the 9.2 meg GPX file down to 4.9 meg, perfect for My Maps:

$ sed -E 's/([0-9]{6})[0-9]+/\1/g' bort.gpx |  grep -v ns3 > bort.smaller.gpx

A BROT Wildflower Map

And check it out, here's my wildflower map of the Bull Run Occoquan Trail (BROT). Pretty sweet, right?

Image Based Maps for Storytelling

The more I think about this My Maps capability, the more I'm both impressed by it and amazed I hadn't considered searching for it in the past. Viewing pictures on a map adds an impressive amount of context. It turns what may be an un-inspiring one-off picture into a fascinating collection.

Consider the difference between a photo of a meal you took while on vacation, and a map showing every meal you ate as you traveled the country. While none of the pictures themselves may be particularly special, as a whole, they would be a fun and unique way to visualize your entire trip.

I'm glad to add map based image collections to my blogging toolbox.

Bonus: Image Metadata Extraction

Here's the script I wrote to extract latitude and longitude metadata from images. It was inspired by this discussion.

#!/bin/bash

##
## help out with manipulating images
##

usage() {
  cmd=$(basename $0)
  echo "Usage: $cmd -a lat -f <img>"
  echo "Usage: $cmd -a lng -f <img>"
  echo "Usage: $cmd -a latlng -f <img>"
  echo "Usage: $cmd -a exif -f <img>"
  exit
}


while getopts ":a:f:h" o; do
  case "${o}" in
    a)
      action=$OPTARG
      ;;
    f)
      file=$OPTARG
      ;;
    * | h)
      usage
      ;;
  esac
done

case "$action" in
  lat | lng)
    if [ -z "$file" ] ; then
      usage
    fi
    
    case $action in
      lat)
        prop=GPSLatitude
        ;;
      lng)
        prop=GPSLongitude
        ;;
    esac


    raw=$(identify -format "%[exif:$prop]" $file)
    if [ -z "$raw" ] ; then
      echo "exif:$prop not found in image"
      exit
    fi

    ref=$(identify -format "%[exif:${prop}Ref]" $file)
    if [ -z "$ref" ] ; then
      echo "exif:${prop}Ref not found in image"
      exit
    fi

    deg_n=$(echo $raw | cut -d, -f1 | cut -d/ -f1)
    deg_d=$(echo $raw | cut -d, -f1 | cut -d/ -f2)
    min_n=$(echo $raw | cut -d, -f2 | cut -d/ -f1)
    min_d=$(echo $raw | cut -d, -f2 | cut -d/ -f2)
    sec_n=$(echo $raw | cut -d, -f3 | cut -d/ -f1)
    sec_d=$(echo $raw | cut -d, -f3 | cut -d/ -f2)

    loc=$(convert xc: -format "%[fx:($deg_n/$deg_d) + ($min_n/$min_d)/60 + ($sec_n/$sec_d)/3600]" info:)


    if [ "$ref" = "S" -o "$ref" = "W" ] ; then
      echo -n "-"
    fi
    
    echo $loc
  ;;

  latlng)
    if [ -z "$file" ] ; then
      usage
    fi
    
    echo -n $(imgassist -a lat -f $file)
    echo -n ","
    echo -n $(imgassist -a lng -f $file)
    echo
    ;;

  exif)
    if [ -z "$file" ]; then
      usage
    fi
    
    identify -verbose $file
    ;;
  *)
    usage
esac

Oh, and I really do plan on posting about wildflower identification. That is if I can avoid the Yak.

No comments:

Post a Comment