Photos, EXIF, GPS and Go
When you take a picture with any modern smartphone (iPhone, Android) it injects GPS data into resulting photo file unless owner changed default setting. GPS data is actually a part of Exif format, which represents a lot more information about photo itself. Some of its attributes include brand, device manufacturer, exposure, timestamp and geographical location. Photo sharing websites such as Flickr and 500px make a good use of that information and allow users to see photo specs, like which camera was used to make the shot.
I like to travel and i take a lot of pictures in the process. Over years i've accumulated an archive of photos made with various versions of iPhone (3-5). Sharing photos is usually not a problem, even Dropbox works fine. At some point i wanted to see the map of visited places. Most photo-sharing app dont have any good tools to achieve that. So after some experiments i've decided to give it a try and scan all my photo library and extract geolocation data. In fact i've done something like that with Ruby in the past and since i'm new to Go i though it would be good cool to try doing that in new language.
Install Dependencies
Initially i looked for any good exif libraries for Go and found a pretty decent one: github.com/gosexy/exif. Works great but it relies on libexif so its not 100% cross platform. On OSX you can install it pretty easity with homebrew:
brew install libexif
Now after the exif library is installed lets install Go package:
go get github.com/gosexy/exif
Parse EXIF
Lets parse out exif data from the photo:
package main
import(
"log"
"os"
"fmt"
"github.com/gosexy/exif"
)
func main() {
path := "/path/to/photo.jpg"
parser := exif.New()
err := parser.Open(path)
if err == nil {
log.Fatalf("Error parsing exif:", err)
}
for k, v := range parser.Tags {
fmt.Println(k, "=", v)
}
}
Example output:
Color Space = sRGB
Manufacturer = Apple
GPS Image Direction = 353.081
Altitude Reference = Sea level
Latitude = 36, 8, 41.71
White Balance = Auto white balance
User Comment = Processed with VSCOcam with m3 preset
Compression = JPEG compression
YCbCr Positioning = Centered
Image Description = Processed with VSCOcam with m3 preset
Orientation = Right-top
GPS Date = 2014:01:01
Sensing Method = One-chip color area sensor
Sub-second Time (Original) = 419
Shutter Speed = 11.14 EV (1/2262 sec.)
Subject Area = Within rectangle (width 1795, height 1077) around (x,y) = (1631,1223)
Focal Length = 4.1 mm
...
The example above will iterate over all available exif tags and print them out as key-value pairs in the terminal. Here's the list of attributes related to GPS data:
Latitude
North or South Latitude
Longitude
East or West Longitude
Convert Coordinates
You cant just grab lat,lng pair from exif data. Exif stores coordinates in four separate tags. Latitude and longitude are expressed as three rational values giving the degrees, minutes, and seconds. Other tags specify reference, N/S for latitude and E/W for longitude. We need to make a function that converts them into regular floating point coordinates:
import (
"strings"
"strconv"
)
type Coordinate struct {
Latitude float64
Longitude float64
}
func parseCoordString(val string) float64 {
chunks := strings.Split(val, ",")
hours, _ := strconv.ParseFloat(strings.TrimSpace(chunks[0]), 64)
minutes, _ := strconv.ParseFloat(strings.TrimSpace(chunks[1]), 64)
seconds, _ := strconv.ParseFloat(strings.TrimSpace(chunks[2]), 64)
return hours + (minutes / 60) + (seconds / 3600)
}
func parseCoord(latVal, latRef, lngVal, lngRef string) *Coordinate {
lat := parseCoordString(latVal)
lng := parseCoordString(lngVal)
if latRef == "S" { // N is "+", S is "-"
lat *= -1
}
if lngRef == "W" { // E is "+", W is "-"
lng *= -1
}
return &Coordinate{lat, lng}
}
The code above will parse geo data from exif and convert it into coordinate structure.