Extracting Geolocation Image Data with Carrierwave and RMagick on Heroku
Many smartphones embed latitude and longitude EXIF data relative to where the photo was taken. So why not extract that data and store it to the database when a user uploads an image?
I am currently using this technique in an API I built which allows users to post a picture of an item they would like to sell. Next an ad is created for the item with the location populated by the image’s embedded geolocation data.
On Heroku we would like the exif data extracted from the temporary image file before it is uploaded to S3. To do this we can call a method to extract the exif date before_save. In the model file:
class ItemImage < ActiveRecord::Base belongs_to :item mount_uploader :image, ImageUploader before_save :extract_geolocation def extract_geolocation img = Magick::Image.read(image)[0] rescue nil return unless img img_lat = img.get_exif_by_entry('GPSLatitude')[0][1].split(', ') rescue nil img_lng = img.get_exif_by_entry('GPSLongitude')[0][1].split(', ') rescue nil lat_ref = img.get_exif_by_entry('GPSLatitudeRef')[0][1] rescue nil lng_ref = img.get_exif_by_entry('GPSLongitudeRef')[0][1] rescue nil return unless img_lat && img_lng && lat_ref && lng_ref latitude = to_frac(img_lat[0]) + (to_frac(img_lat[1])/60) + (to_frac(img_lat[2])/3600) longitude = to_frac(img_lng[0]) + (to_frac(img_lng[1])/60) + (to_frac(img_lng[2])/3600) latitude = latitude * -1 if lat_ref == 'S' # (N is +, S is -) longitude = longitude * -1 if lng_ref == 'W' # (W is -, E is +) self.lat = latitude self.lng = longitude end def to_frac(strng) numerator, denominator = strng.split('/').map(&:to_f) denominator ||= 1 numerator/denominator end end
The extract_geolocation method uses RMagick to access the image EXIF data. If it contains latitude and longitude data expressed as linear units (degree, minute, second), but we would like it in decimal format so that it can be fed into the Google Maps API and returned as a city, state and zipcode. This is what the calculations in the method are handling.
With a gem like Geocoder we can easily wire in geocode look up and store the city, state, and zipcode in the image record.
if geo = Geocoder.search("#{latitude},#{longitude}").first self.city = geo.city self.state = geo.state self.zipcode = geo.postal_code end
Putting it all together:
Feedback on how to improve the process is welcome.