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.