Saturday, 5 September 2015

Geotags from JPEG images

Most modern camera's and telephones can geotag their pictures. Kind of neat that you can use your camera as GPS logger. Even better if we could do something useful with it in R. In this post I will show how GPS coordinates can be extracted from jpeg images and utilized in R. Although it sounds pretty simple, and it is, it is not very straight forward to achieve.

In this example I will use pictures I made on a day out. We made a small round trip by steamtram from Hoorn to Medemblik where we visited castle Radboud. I will show how to plot the locations where I took the pictures can be plotted onto a map. For privacy reasons I will not share the pictures. However, the example should also work with your own geotagged pictures.

# required packages
require(rgdal)
require(OpenStreetMap)
# select files for which geotags need to
# be extracted and store filenames in data.frame
file_info <- data.frame(file.name = choose.files(filters = Filters["jpeg",]))
view raw geotag01.r hosted with ❤ by GitHub

Geotags and other meta-information is stored in jpeg files in the so-called exchangeable image file format (or EXIF). Luckily it can be easily obtained using the function GDALinfo. This is where things get messy. The function returns the EXIF codes as character strings in the attributes of the object that is returned. So, we need to fetch the relevant attributes; find the relevant EXIF codes; and extract the information from the code and convert it into something workable.

The EXIF codes for GPS information will look something like this ‘EXIF_GPSLatitude=(52) (40) (57.45)’. I use regular expressions to extract the coordinate information from between the brackets. I'm no expert in the field of regular expressions, so there may be more efficient regular expressions to get the right information. If you have improvements, please feel free to leave them in the comments.

I've wrapped the routines to isolate the respective EXIF code and to extract the numerical coordinates from the literal EXIF character string in slick functions (‘get_EXIF_value’ for getting the literal string and ‘get_EXIF_coordinate’ to get the numerical coordinate from that string).

# function to get a parameter value from EXIF code,
# x = a vector of character strings representing the EXIF codes of a file
# parameter = character string representing the parameter
# for which you want to extract the value
# the literal character string is returned for the requested parameter
get_EXIF_value <- function(x, parameter)
{
line <- x[grepl(paste("EXIF_", parameter, "=", sep = ""), x)]
if (!is.null(line) && length(line) > 0)
{
pos <- gregexpr("=", line, fixed = T)[[1]]
pos <- pos[length(pos)]
line <- substr(line, pos + 1, nchar(line))
line <- gsub("[(]|[)]", "", unlist(strsplit(line, "([)]\\s+[(])|[)][()]")))
}
return(line)
}
# function to get the longitude and latitude from the
# character string obtained with the function above
# x = a character string with the coordinates
get_EXIF_coordinate <- function(x)
{
lat <- get_EXIF_value(x, "GPSLatitude")
lat <- as.numeric(lat)
lat <- lat[1] + lat[2]/60 + lat[3]/(60*60)
latref <- get_EXIF_value(x, "GPSLatitudeRef")
lat <- ifelse(toupper(latref) == "S", -lat, lat)
if (length(lat) == 0) lat <- NA
lon <- get_EXIF_value(x, "GPSLongitude")
lon <- as.numeric(lon)
lon <- lon[1] + lon[2]/60 + lon[3]/(60*60)
lonref <- get_EXIF_value(x, "GPSLongitudeRef")
lon <- ifelse(toupper(lonref) == "W", -lon, lon)
if (length(lon) == 0) lon <- NA
return (c(lon, lat))
}
# for each file obtain the EXIF codes and extract the coordinates
GPStags <- apply(array(file_info$file.name), 1, function(x) {
EXIF_code <- NULL
try (EXIF_code <- attr(GDALinfo(x), "mdata"), silent = T)
if (is.null(EXIF_code)) return(c(NA, NA)) else return(get_EXIF_coordinate(EXIF_code))
})
view raw geotag02.r hosted with ❤ by GitHub

After some rearranging of the data, we have a data.frame holding the filename and the longitude and latitude in decimal WGS84 degrees. I noticed that not all my pictures had a geotag (even though I had geotagging turned on, on my device). Apparently taking pictures is not a full proof method for logging locations (at least not on my device).

# rearrang the data
GPStags <- t(matrix(unlist(GPStags), 2))
# store in the data.frame
file_info$lon <- GPStags[,1]
file_info$lat <- GPStags[,2]
# check if any files lack GPS longitudes
table(is.na(file_info$lon))
# clean up after us
rm(GPStags)
view raw geotag03.r hosted with ❤ by GitHub

Let's plot the coordinates on a map. I download some OpenStreetMap-based map tiles using the function ‘openmap’. This function conveniently allows you to specify the upperleft and lowerright coordinates of your map. That way, you can be certain that your locations will fit on the map.

As we cannot change the projection of the downloaded maptiles. We therefore have to transform the coordinates of our locations to the same projection as the maptiles. We do this by first creating a SpatialPoints object from our coordinates and then transform those coordinates to the proper projection by applying spTransform. Plotting is now simply a matter of calling some plot routines.

# Download nice-looking map-tiles for the relevant location
photo_map <- openmap(upperLeft = c(max(file_info$lat, na.rm = T) + 0.2, min(file_info$lon, na.rm = T) - 0.2),
lowerRight = c(min(file_info$lat, na.rm = T) - 0.2, max(file_info$lon, na.rm = T) + 0.2),
type = "stamen-watercolor")
# we need to turn our coordinates into an sp object in order to
# properly project the points onto the map.
photo_points <- SpatialPoints(na.omit(file_info[,c("lon", "lat")]),
CRS("+proj=longlat +ellps=WGS84 +datum=WGS84"))
# transform the lon lat coordinates to the same projection as the map tiles.
photo_points <- spTransform(photo_points, photo_map$tiles[[1]]$projection)
# plot the map and locations of the pictures
png("jpg_geotags01.png", photo_map$tiles[[1]]$yres/3, photo_map$tiles[[1]]$xres/3, type = "cairo")
plot(photo_map)
plot(photo_points, add = T, pch = 19, cex = 0.8)
plot(photo_points, add = T, pch = 19, cex = 0.5, col = rainbow(length(photo_points)))
dev.off()
view raw geotag04.r hosted with ❤ by GitHub

To make things a bit more fancy, I used the rainbow palette to color the locations in the order in which the pictures were taken. Note that the use of geotagged images is limited. Proper GPS loggers for instance also log the precision of the position and your speed. But I will save that for a future post.

No comments:

Post a Comment